Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PowerMeter Class + SDM PowerMeter support #140

Merged
merged 2 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,20 @@ struct CONFIG_T {

bool Mqtt_Hass_Expire;

bool PowerMeter_Enabled;
uint32_t PowerMeter_Interval;
uint32_t PowerMeter_Source;
char PowerMeter_MqttTopicPowerMeter1[MQTT_MAX_TOPIC_STRLEN + 1];
char PowerMeter_MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1];
char PowerMeter_MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1];
uint32_t PowerMeter_SdmBaudrate;
uint32_t PowerMeter_SdmAddress;


bool PowerLimiter_Enabled;
bool PowerLimiter_SolarPassTroughEnabled;
uint8_t PowerLimiter_BatteryDrainStategy;
uint32_t PowerLimiter_Interval;
char PowerLimiter_MqttTopicPowerMeter1[MQTT_MAX_TOPIC_STRLEN + 1];
char PowerLimiter_MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1];
char PowerLimiter_MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1];
bool PowerLimiter_IsInverterBehindPowerMeter;
uint8_t PowerLimiter_InverterId;
uint8_t PowerLimiter_InverterChannelId;
Expand Down
1 change: 0 additions & 1 deletion include/PowerLimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class PowerLimiterClass {
void loop();
plStates getPowerLimiterState();
int32_t getLastRequestedPowewrLimit();
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);

private:
uint32_t _lastCommandSent;
Expand Down
48 changes: 48 additions & 0 deletions include/PowerMeter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include "Configuration.h"
#include <espMqttClient.h>
#include <Arduino.h>
#include <Hoymiles.h>
#include <memory>
#include "SDM.h"

#ifndef SDM_RX_PIN
#define SDM_RX_PIN 13
#endif

#ifndef SDM_TX_PIN
#define SDM_TX_PIN 32
#endif

class PowerMeterClass {
public:
void init();
void mqtt();
void loop();
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
float getPowerTotal();

private:
uint32_t _interval;
uint32_t _lastPowerMeterUpdate;

float _powerMeter1Power = 0.0;
float _powerMeter2Power = 0.0;
float _powerMeter3Power = 0.0;
float _powerMeterTotalPower = 0.0;
float _powerMeter1Voltage = 0.0;
float _powerMeter2Voltage = 0.0;
float _powerMeter3Voltage = 0.0;
float _PowerMeterImport = 0.0;
float _PowerMeterExport = 0.0;

bool mqttInitDone = false;
char PowerMeter_MqttTopicPowerMeter1old[MQTT_MAX_TOPIC_STRLEN + 1];
char PowerMeter_MqttTopicPowerMeter2old[MQTT_MAX_TOPIC_STRLEN + 1];
char PowerMeter_MqttTopicPowerMeter3old[MQTT_MAX_TOPIC_STRLEN + 1];

};

extern PowerMeterClass PowerMeter;
18 changes: 18 additions & 0 deletions include/WebApi_powermeter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ESPAsyncWebServer.h>


class WebApiPowerMeterClass {
public:
void init(AsyncWebServer* server);
void loop();

private:
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);

AsyncWebServer* _server;
};
7 changes: 7 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@
#define VEDIRECT_UPDATESONLY true
#define VEDIRECT_POLL_INTERVAL 5

#define POWERMETER_ENABLED false
#define POWERMETER_INTERVAL 10
#define POWERMETER_SOURCE 2
#define POWERMETER_SDMBAUDRATE 9600
#define POWERMETER_SDMADDRESS 1


#define POWERLIMITER_ENABLED false
#define POWERLIMITER_SOLAR_PASSTROUGH_ENABLED true
#define POWERLIMITER_BATTERY_DRAIN_STRATEGY 0
Expand Down
254 changes: 254 additions & 0 deletions lib/SdmEnergyMeter/SDM.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
* 2016-2022 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 esp8266 core)
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
*/
//------------------------------------------------------------------------------
#include "SDM.h"
//------------------------------------------------------------------------------
#if defined ( USE_HARDWARESERIAL )
#if defined ( ESP8266 )
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config, bool swapuart) : sdmSer(serial) {
this->_baud = baud;
this->_dere_pin = dere_pin;
this->_config = config;
this->_swapuart = swapuart;
}
#elif defined ( ESP32 )
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config, int8_t rx_pin, int8_t tx_pin) : sdmSer(serial) {
this->_baud = baud;
this->_dere_pin = dere_pin;
this->_config = config;
this->_rx_pin = rx_pin;
this->_tx_pin = tx_pin;
}
#else
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config) : sdmSer(serial) {
this->_baud = baud;
this->_dere_pin = dere_pin;
this->_config = config;
}
#endif
#else
#if defined ( ESP8266 ) || defined ( ESP32 )
SDM::SDM(SoftwareSerial& serial, long baud, int dere_pin, int config, int8_t rx_pin, int8_t tx_pin) : sdmSer(serial) {
this->_baud = baud;
this->_dere_pin = dere_pin;
this->_config = config;
this->_rx_pin = rx_pin;
this->_tx_pin = tx_pin;
}
#else
SDM::SDM(SoftwareSerial& serial, long baud, int dere_pin) : sdmSer(serial) {
this->_baud = baud;
this->_dere_pin = dere_pin;
}
#endif
#endif

SDM::~SDM() {
}

void SDM::begin(void) {
#if defined ( USE_HARDWARESERIAL )
#if defined ( ESP8266 )
sdmSer.begin(_baud, (SerialConfig)_config);
#elif defined ( ESP32 )
sdmSer.begin(_baud, _config, _rx_pin, _tx_pin);
#else
sdmSer.begin(_baud, _config);
#endif
#else
#if defined ( ESP8266 ) || defined ( ESP32 )
sdmSer.begin(_baud, (SoftwareSerialConfig)_config, _rx_pin, _tx_pin);
#else
sdmSer.begin(_baud);
#endif
#endif

#if defined ( USE_HARDWARESERIAL ) && defined ( ESP8266 )
if (_swapuart)
sdmSer.swap();
#endif
if (_dere_pin != NOT_A_PIN) {
pinMode(_dere_pin, OUTPUT); //set output pin mode for DE/RE pin when used (for control MAX485)
}
dereSet(LOW); //set init state to receive from SDM -> DE Disable, /RE Enable (for control MAX485)
}

float SDM::readVal(uint16_t reg, uint8_t node) {
uint16_t temp;
unsigned long resptime;
uint8_t sdmarr[FRAMESIZE] = {node, SDM_B_02, 0, 0, SDM_B_05, SDM_B_06, 0, 0, 0};
float res = NAN;
uint16_t readErr = SDM_ERR_NO_ERROR;

sdmarr[2] = highByte(reg);
sdmarr[3] = lowByte(reg);

temp = calculateCRC(sdmarr, FRAMESIZE - 3); //calculate out crc only from first 6 bytes

sdmarr[6] = lowByte(temp);
sdmarr[7] = highByte(temp);

#if !defined ( USE_HARDWARESERIAL )
sdmSer.listen(); //enable softserial rx interrupt
#endif

flush(); //read serial if any old data is available

dereSet(HIGH); //transmit to SDM -> DE Enable, /RE Disable (for control MAX485)

delay(2); //fix for issue (nan reading) by sjfaustino: https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272111524

sdmSer.write(sdmarr, FRAMESIZE - 1); //send 8 bytes

sdmSer.flush(); //clear out tx buffer

dereSet(LOW); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)

resptime = millis();

while (sdmSer.available() < FRAMESIZE) {
if (millis() - resptime > msturnaround) {
readErr = SDM_ERR_TIMEOUT; //err debug (4)
break;
}
yield();
}

if (readErr == SDM_ERR_NO_ERROR) { //if no timeout...

if (sdmSer.available() >= FRAMESIZE) {

for(int n=0; n<FRAMESIZE; n++) {
sdmarr[n] = sdmSer.read();
}

if (sdmarr[0] == node && sdmarr[1] == SDM_B_02 && sdmarr[2] == SDM_REPLY_BYTE_COUNT) {

if ((calculateCRC(sdmarr, FRAMESIZE - 2)) == ((sdmarr[8] << 8) | sdmarr[7])) { //calculate crc from first 7 bytes and compare with received crc (bytes 7 & 8)
((uint8_t*)&res)[3]= sdmarr[3];
((uint8_t*)&res)[2]= sdmarr[4];
((uint8_t*)&res)[1]= sdmarr[5];
((uint8_t*)&res)[0]= sdmarr[6];
} else {
readErr = SDM_ERR_CRC_ERROR; //err debug (1)
}

} else {
readErr = SDM_ERR_WRONG_BYTES; //err debug (2)
}

} else {
readErr = SDM_ERR_NOT_ENOUGHT_BYTES; //err debug (3)
}

}

flush(mstimeout); //read serial if any old data is available and wait for RESPONSE_TIMEOUT (in ms)

if (sdmSer.available()) //if serial rx buffer (after RESPONSE_TIMEOUT) still contains data then something spam rs485, check node(s) or increase RESPONSE_TIMEOUT
readErr = SDM_ERR_TIMEOUT; //err debug (4) but returned value may be correct

if (readErr != SDM_ERR_NO_ERROR) { //if error then copy temp error value to global val and increment global error counter
readingerrcode = readErr;
readingerrcount++;
} else {
++readingsuccesscount;
}

#if !defined ( USE_HARDWARESERIAL )
sdmSer.stopListening(); //disable softserial rx interrupt
#endif

return (res);
}

uint16_t SDM::getErrCode(bool _clear) {
uint16_t _tmp = readingerrcode;
if (_clear == true)
clearErrCode();
return (_tmp);
}

uint32_t SDM::getErrCount(bool _clear) {
uint32_t _tmp = readingerrcount;
if (_clear == true)
clearErrCount();
return (_tmp);
}

uint32_t SDM::getSuccCount(bool _clear) {
uint32_t _tmp = readingsuccesscount;
if (_clear == true)
clearSuccCount();
return (_tmp);
}

void SDM::clearErrCode() {
readingerrcode = SDM_ERR_NO_ERROR;
}

void SDM::clearErrCount() {
readingerrcount = 0;
}

void SDM::clearSuccCount() {
readingsuccesscount = 0;
}

void SDM::setMsTurnaround(uint16_t _msturnaround) {
if (_msturnaround < SDM_MIN_DELAY)
msturnaround = SDM_MIN_DELAY;
else if (_msturnaround > SDM_MAX_DELAY)
msturnaround = SDM_MAX_DELAY;
else
msturnaround = _msturnaround;
}

void SDM::setMsTimeout(uint16_t _mstimeout) {
if (_mstimeout < SDM_MIN_DELAY)
mstimeout = SDM_MIN_DELAY;
else if (_mstimeout > SDM_MAX_DELAY)
mstimeout = SDM_MAX_DELAY;
else
mstimeout = _mstimeout;
}

uint16_t SDM::getMsTurnaround() {
return (msturnaround);
}

uint16_t SDM::getMsTimeout() {
return (mstimeout);
}

uint16_t SDM::calculateCRC(uint8_t *array, uint8_t len) {
uint16_t _crc, _flag;
_crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
_crc ^= (uint16_t)array[i];
for (uint8_t j = 8; j; j--) {
_flag = _crc & 0x0001;
_crc >>= 1;
if (_flag)
_crc ^= 0xA001;
}
}
return _crc;
}

void SDM::flush(unsigned long _flushtime) {
unsigned long flushstart = millis();
while (sdmSer.available() || (millis() - flushstart < _flushtime)) {
if (sdmSer.available()) //read serial if any old data is available
sdmSer.read();
delay(1);
}
}

void SDM::dereSet(bool _state) {
if (_dere_pin != NOT_A_PIN)
digitalWrite(_dere_pin, _state); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
}
Loading