PowerMeter Class + SDM PowerMeter support
This commit is contained in:
parent
dbb548e731
commit
05a5b2367b
@ -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;
|
||||
|
||||
@ -26,7 +26,6 @@ public:
|
||||
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;
|
||||
|
||||
48
include/PowerMeter.h
Normal file
48
include/PowerMeter.h
Normal file
@ -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
include/WebApi_powermeter.h
Normal file
18
include/WebApi_powermeter.h
Normal file
@ -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;
|
||||
};
|
||||
@ -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
|
||||
|
||||
254
lib/SdmEnergyMeter/SDM.cpp
Normal file
254
lib/SdmEnergyMeter/SDM.cpp
Normal file
@ -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)
|
||||
}
|
||||
299
lib/SdmEnergyMeter/SDM.h
Normal file
299
lib/SdmEnergyMeter/SDM.h
Normal file
@ -0,0 +1,299 @@
|
||||
/* 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/)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
#ifndef SDM_h
|
||||
#define SDM_h
|
||||
//------------------------------------------------------------------------------
|
||||
#include <Arduino.h>
|
||||
#include <SDM_Config_User.h>
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#include <HardwareSerial.h>
|
||||
#else
|
||||
#include <SoftwareSerial.h>
|
||||
#endif
|
||||
//------------------------------------------------------------------------------
|
||||
//DEFAULT CONFIG (DO NOT CHANGE ANYTHING!!! for changes use SDM_Config_User.h):
|
||||
//------------------------------------------------------------------------------
|
||||
#if !defined ( SDM_UART_BAUD )
|
||||
#define SDM_UART_BAUD 4800 // default baudrate
|
||||
#endif
|
||||
|
||||
#if !defined ( DERE_PIN )
|
||||
#define DERE_PIN NOT_A_PIN // default digital pin for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
#endif
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
|
||||
#if !defined ( SDM_UART_CONFIG )
|
||||
#define SDM_UART_CONFIG SERIAL_8N1 // default hardware uart config
|
||||
#endif
|
||||
|
||||
#if defined ( ESP8266 ) && !defined ( SWAPHWSERIAL )
|
||||
#define SWAPHWSERIAL 0 // (only esp8266) when hwserial used, then swap uart pins from 3/1 to 13/15 (default not swap)
|
||||
#endif
|
||||
|
||||
#if defined ( ESP32 )
|
||||
#if !defined ( SDM_RX_PIN )
|
||||
#define SDM_RX_PIN -1 // use default rx pin for selected port
|
||||
#endif
|
||||
#if !defined ( SDM_TX_PIN )
|
||||
#define SDM_TX_PIN -1 // use default tx pin for selected port
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
#if !defined ( SDM_UART_CONFIG )
|
||||
#define SDM_UART_CONFIG SWSERIAL_8N1 // default softwareware uart config for esp8266/esp32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// #if !defined ( SDM_RX_PIN ) || !defined ( SDM_TX_PIN )
|
||||
// #error "SDM_RX_PIN and SDM_TX_PIN must be defined in SDM_Config_User.h for Software Serial option)"
|
||||
// #endif
|
||||
|
||||
#if !defined ( SDM_RX_PIN )
|
||||
#define SDM_RX_PIN -1
|
||||
#endif
|
||||
#if !defined ( SDM_TX_PIN )
|
||||
#define SDM_TX_PIN -1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined ( WAITING_TURNAROUND_DELAY )
|
||||
#define WAITING_TURNAROUND_DELAY 200 // time in ms to wait for process current request
|
||||
#endif
|
||||
|
||||
#if !defined ( RESPONSE_TIMEOUT )
|
||||
#define RESPONSE_TIMEOUT 500 // time in ms to wait for return response from all devices before next request
|
||||
#endif
|
||||
|
||||
#if !defined ( SDM_MIN_DELAY )
|
||||
#define SDM_MIN_DELAY 20 // minimum value (in ms) for WAITING_TURNAROUND_DELAY and RESPONSE_TIMEOUT
|
||||
#endif
|
||||
|
||||
#if !defined ( SDM_MAX_DELAY )
|
||||
#define SDM_MAX_DELAY 5000 // maximum value (in ms) for WAITING_TURNAROUND_DELAY and RESPONSE_TIMEOUT
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#define SDM_ERR_NO_ERROR 0 // no error
|
||||
#define SDM_ERR_CRC_ERROR 1 // crc error
|
||||
#define SDM_ERR_WRONG_BYTES 2 // bytes b0,b1 or b2 wrong
|
||||
#define SDM_ERR_NOT_ENOUGHT_BYTES 3 // not enough bytes from sdm
|
||||
#define SDM_ERR_TIMEOUT 4 // timeout
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#define FRAMESIZE 9 // size of out/in array
|
||||
#define SDM_REPLY_BYTE_COUNT 0x04 // number of bytes with data
|
||||
|
||||
#define SDM_B_01 0x01 // BYTE 1 -> slave address (default value 1 read from node 1)
|
||||
#define SDM_B_02 0x04 // BYTE 2 -> function code (default value 0x04 read from 3X input registers)
|
||||
#define SDM_B_05 0x00 // BYTE 5
|
||||
#define SDM_B_06 0x02 // BYTE 6
|
||||
// BYTES 3 & 4 (BELOW)
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR SDM DEVICES |
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | SDM630 | SDM230 | SDM220 | SDM120CT| SDM120 | SDM72D | SDM72 V2|
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
#define SDM_PHASE_1_VOLTAGE 0x0000 // V | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_VOLTAGE 0x0002 // V | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_VOLTAGE 0x0004 // V | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_CURRENT 0x0006 // A | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_CURRENT 0x0008 // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_CURRENT 0x000A // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_POWER 0x000C // W | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_POWER 0x000E // W | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_POWER 0x0010 // W | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_APPARENT_POWER 0x0012 // VA | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_APPARENT_POWER 0x0014 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_APPARENT_POWER 0x0016 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_REACTIVE_POWER 0x0018 // VAr | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_REACTIVE_POWER 0x001A // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_REACTIVE_POWER 0x001C // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_POWER_FACTOR 0x001E // | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_POWER_FACTOR 0x0020 // | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_POWER_FACTOR 0x0022 // | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_ANGLE 0x0024 // Degrees | 1 | 1 | 1 | 1 | | | |
|
||||
#define SDM_PHASE_2_ANGLE 0x0026 // Degrees | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_ANGLE 0x0028 // Degrees | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_L_TO_N_VOLTS 0x002A // V | 1 | | | | | | 1 |
|
||||
#define SDM_AVERAGE_LINE_CURRENT 0x002E // A | 1 | | | | | | 1 |
|
||||
#define SDM_SUM_LINE_CURRENT 0x0030 // A | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_POWER 0x0034 // W | 1 | | | | | 1 | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_APPARENT_POWER 0x0038 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_REACTIVE_POWER 0x003C // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_FACTOR 0x003E // | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_PHASE_ANGLE 0x0042 // Degrees | 1 | | | | | | |
|
||||
#define SDM_FREQUENCY 0x0046 // Hz | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_IMPORT_ACTIVE_ENERGY 0x0048 // kWh/MWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_EXPORT_ACTIVE_ENERGY 0x004A // kWh/MWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_IMPORT_REACTIVE_ENERGY 0x004C // kVArh/MVArh | 1 | 1 | 1 | 1 | 1 | | |
|
||||
#define SDM_EXPORT_REACTIVE_ENERGY 0x004E // kVArh/MVArh | 1 | 1 | 1 | 1 | 1 | | |
|
||||
#define SDM_VAH_SINCE_LAST_RESET 0x0050 // kVAh/MVAh | 1 | | | | | | |
|
||||
#define SDM_AH_SINCE_LAST_RESET 0x0052 // Ah/kAh | 1 | | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_DEMAND 0x0054 // W | 1 | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND 0x0056 // W | 1 | 1 | | | | | |
|
||||
#define SDM_CURRENT_SYSTEM_POSITIVE_POWER_DEMAND 0x0058 // W | | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_SYSTEM_POSITIVE_POWER_DEMAND 0x005A // W | | 1 | | | | | |
|
||||
#define SDM_CURRENT_SYSTEM_REVERSE_POWER_DEMAND 0x005C // W | | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_SYSTEM_REVERSE_POWER_DEMAND 0x005E // W | | 1 | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_VA_DEMAND 0x0064 // VA | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND 0x0066 // VA | 1 | | | | | | |
|
||||
#define SDM_NEUTRAL_CURRENT_DEMAND 0x0068 // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_NEUTRAL_CURRENT 0x006A // A | 1 | | | | | | |
|
||||
#define SDM_LINE_1_TO_LINE_2_VOLTS 0x00C8 // V | 1 | | | | | | 1 |
|
||||
#define SDM_LINE_2_TO_LINE_3_VOLTS 0x00CA // V | 1 | | | | | | 1 |
|
||||
#define SDM_LINE_3_TO_LINE_1_VOLTS 0x00CC // V | 1 | | | | | | 1 |
|
||||
#define SDM_AVERAGE_LINE_TO_LINE_VOLTS 0x00CE // V | 1 | | | | | | 1 |
|
||||
#define SDM_NEUTRAL_CURRENT 0x00E0 // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_LN_VOLTS_THD 0x00EA // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_2_LN_VOLTS_THD 0x00EC // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_LN_VOLTS_THD 0x00EE // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_1_CURRENT_THD 0x00F0 // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_2_CURRENT_THD 0x00F2 // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_CURRENT_THD 0x00F4 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_TO_NEUTRAL_VOLTS_THD 0x00F8 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_CURRENT_THD 0x00FA // % | 1 | | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_FACTOR_INV 0x00FE // | 1 | | | | | | |
|
||||
#define SDM_PHASE_1_CURRENT_DEMAND 0x0102 // A | 1 | 1 | | | | | |
|
||||
#define SDM_PHASE_2_CURRENT_DEMAND 0x0104 // A | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_CURRENT_DEMAND 0x0106 // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND 0x0108 // A | 1 | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND 0x010A // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND 0x010C // A | 1 | | | | | | |
|
||||
#define SDM_LINE_1_TO_LINE_2_VOLTS_THD 0x014E // % | 1 | | | | | | |
|
||||
#define SDM_LINE_2_TO_LINE_3_VOLTS_THD 0x0150 // % | 1 | | | | | | |
|
||||
#define SDM_LINE_3_TO_LINE_1_VOLTS_THD 0x0152 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_TO_LINE_VOLTS_THD 0x0154 // % | 1 | | | | | | |
|
||||
#define SDM_TOTAL_ACTIVE_ENERGY 0x0156 // kWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_TOTAL_REACTIVE_ENERGY 0x0158 // kVArh | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_L1_IMPORT_ACTIVE_ENERGY 0x015A // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_IMPORT_ACTIVE_ENERGY 0x015C // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_IMPORT_ACTIVE_ENERGY 0x015E // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_EXPORT_ACTIVE_ENERGY 0x0160 // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_EXPORT_ACTIVE_ENERGY 0x0162 // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_EXPORT_ACTIVE_ENERGY 0x0164 // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_TOTAL_ACTIVE_ENERGY 0x0166 // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_TOTAL_ACTIVE_ENERGY 0x0168 // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_TOTAL_ACTIVE_ENERGY 0x016a // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_IMPORT_REACTIVE_ENERGY 0x016C // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_IMPORT_REACTIVE_ENERGY 0x016E // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_IMPORT_REACTIVE_ENERGY 0x0170 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L1_EXPORT_REACTIVE_ENERGY 0x0172 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_EXPORT_REACTIVE_ENERGY 0x0174 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_EXPORT_REACTIVE_ENERGY 0x0176 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L1_TOTAL_REACTIVE_ENERGY 0x0178 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_TOTAL_REACTIVE_ENERGY 0x017A // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_TOTAL_REACTIVE_ENERGY 0x017C // kVArh | 1 | | | | | | |
|
||||
#define SDM_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY 0x0180 // kWh | | 1 | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_TOTAL_REACTIVE_ENERGY 0x0182 // kVArh | | 1 | | | | | |
|
||||
#define SDM_CURRENT_RESETTABLE_IMPORT_ENERGY 0x0184 // kWh | | | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_EXPORT_ENERGY 0x0186 // kWh | | | | | | 1 | 1 |
|
||||
#define SDM_NET_KWH 0x018C // kWh | | | | | | | 1 |
|
||||
#define SDM_IMPORT_POWER 0x0500 // W | | | | | | 1 | 1 |
|
||||
#define SDM_EXPORT_POWER 0x0502 // W | | | | | | 1 | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR DDM DEVICE |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | DDM18SD |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
#define DDM_PHASE_1_VOLTAGE 0x0000 // V | 1 |
|
||||
#define DDM_PHASE_1_CURRENT 0x0008 // A | 1 |
|
||||
#define DDM_PHASE_1_POWER 0x0012 // W | 1 |
|
||||
#define DDM_PHASE_1_REACTIVE_POWER 0x001A // VAr | 1 |
|
||||
#define DDM_PHASE_1_POWER_FACTOR 0x002A // | 1 |
|
||||
#define DDM_FREQUENCY 0x0036 // Hz | 1 |
|
||||
#define DDM_IMPORT_ACTIVE_ENERGY 0x0100 // kWh | 1 |
|
||||
#define DDM_IMPORT_REACTIVE_ENERGY 0x0400 // kVArh | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR DEVNAME DEVICE |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | DEVNAME |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
//#define DEVNAME_VOLTAGE 0x0000 // V | 1 |
|
||||
//#define DEVNAME_CURRENT 0x0002 // A | 1 |
|
||||
//#define DEVNAME_POWER 0x0004 // W | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class SDM {
|
||||
public:
|
||||
#if defined ( USE_HARDWARESERIAL ) // hardware serial
|
||||
#if defined ( ESP8266 ) // on esp8266
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, bool swapuart = SWAPHWSERIAL);
|
||||
#elif defined ( ESP32 ) // on esp32
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, int8_t rx_pin = SDM_RX_PIN, int8_t tx_pin = SDM_TX_PIN);
|
||||
#else // on avr
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG);
|
||||
#endif
|
||||
#else // software serial
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 ) // on esp8266/esp32
|
||||
SDM(SoftwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, int8_t rx_pin = SDM_RX_PIN, int8_t tx_pin = SDM_TX_PIN);
|
||||
#else // on avr
|
||||
SDM(SoftwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN);
|
||||
#endif
|
||||
#endif
|
||||
virtual ~SDM();
|
||||
|
||||
void begin(void);
|
||||
float readVal(uint16_t reg, uint8_t node = SDM_B_01); // read value from register = reg and from deviceId = node
|
||||
uint16_t getErrCode(bool _clear = false); // return last errorcode (optional clear this value, default flase)
|
||||
uint32_t getErrCount(bool _clear = false); // return total errors count (optional clear this value, default flase)
|
||||
uint32_t getSuccCount(bool _clear = false); // return total success count (optional clear this value, default false)
|
||||
void clearErrCode(); // clear last errorcode
|
||||
void clearErrCount(); // clear total errors count
|
||||
void clearSuccCount(); // clear total success count
|
||||
void setMsTurnaround(uint16_t _msturnaround = WAITING_TURNAROUND_DELAY); // set new value for WAITING_TURNAROUND_DELAY (ms), min=SDM_MIN_DELAY, max=SDM_MAX_DELAY
|
||||
void setMsTimeout(uint16_t _mstimeout = RESPONSE_TIMEOUT); // set new value for RESPONSE_TIMEOUT (ms), min=SDM_MIN_DELAY, max=SDM_MAX_DELAY
|
||||
uint16_t getMsTurnaround(); // get current value of WAITING_TURNAROUND_DELAY (ms)
|
||||
uint16_t getMsTimeout(); // get current value of RESPONSE_TIMEOUT (ms)
|
||||
|
||||
private:
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
HardwareSerial& sdmSer;
|
||||
#else
|
||||
SoftwareSerial& sdmSer;
|
||||
#endif
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
int _config = SDM_UART_CONFIG;
|
||||
#if defined ( ESP8266 )
|
||||
bool _swapuart = SWAPHWSERIAL;
|
||||
#elif defined ( ESP32 )
|
||||
int8_t _rx_pin = -1;
|
||||
int8_t _tx_pin = -1;
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
int _config = SDM_UART_CONFIG;
|
||||
#endif
|
||||
int8_t _rx_pin = -1;
|
||||
int8_t _tx_pin = -1;
|
||||
#endif
|
||||
long _baud = SDM_UART_BAUD;
|
||||
int _dere_pin = DERE_PIN;
|
||||
uint16_t readingerrcode = SDM_ERR_NO_ERROR; // 4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 or b2 wrong, 1 = crc error
|
||||
uint16_t msturnaround = WAITING_TURNAROUND_DELAY;
|
||||
uint16_t mstimeout = RESPONSE_TIMEOUT;
|
||||
uint32_t readingerrcount = 0; // total errors counter
|
||||
uint32_t readingsuccesscount = 0; // total success counter
|
||||
uint16_t calculateCRC(uint8_t *array, uint8_t len);
|
||||
void flush(unsigned long _flushtime = 0); // read serial if any old data is available or for a given time in ms
|
||||
void dereSet(bool _state = LOW); // for control MAX485 DE/RE pins, LOW receive from SDM, HIGH transmit to SDM
|
||||
};
|
||||
#endif // SDM_h
|
||||
93
lib/SdmEnergyMeter/SDM_Config_User.h
Normal file
93
lib/SdmEnergyMeter/SDM_Config_User.h
Normal file
@ -0,0 +1,93 @@
|
||||
/* 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/)
|
||||
*/
|
||||
|
||||
/*
|
||||
* USER CONFIG:
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define or undefine USE_HARDWARESERIAL (uncomment only one or none)
|
||||
*/
|
||||
//#undef USE_HARDWARESERIAL
|
||||
#define USE_HARDWARESERIAL
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user baudrate
|
||||
*/
|
||||
#define SDM_UART_BAUD 9600
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user SDM_RX_PIN and SDM_TX_PIN for esp/avr Software Serial option
|
||||
* or ESP32 with Hardware Serial if default core pins are not suitable
|
||||
*/
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#if defined ( ESP32 )
|
||||
#define SDM_RX_PIN 13
|
||||
#define SDM_TX_PIN 32
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
#define SDM_RX_PIN 13
|
||||
#define SDM_TX_PIN 15
|
||||
#else
|
||||
#define SDM_RX_PIN 10
|
||||
#define SDM_TX_PIN 11
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user DERE_PIN for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
*/
|
||||
//#define DERE_PIN NOT_A_PIN
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
|
||||
/*
|
||||
* define user SDM_UART_CONFIG for hardware serial
|
||||
*/
|
||||
//#define SDM_UART_CONFIG SERIAL_8N1
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user SWAPHWSERIAL, if true(1) then swap uart pins from 3/1 to 13/15 (only ESP8266)
|
||||
*/
|
||||
//#define SWAPHWSERIAL 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* define user SDM_UART_CONFIG for software serial
|
||||
*/
|
||||
//#define SDM_UART_CONFIG SWSERIAL_8N1
|
||||
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user WAITING_TURNAROUND_DELAY time in ms to wait for process current request
|
||||
*/
|
||||
//#define WAITING_TURNAROUND_DELAY 200
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user RESPONSE_TIMEOUT time in ms to wait for return response from all devices before next request
|
||||
*/
|
||||
//#define RESPONSE_TIMEOUT 500
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@ -115,14 +115,21 @@ bool ConfigurationClass::write()
|
||||
vedirect["updates_only"] = config.Vedirect_UpdatesOnly;
|
||||
vedirect["poll_interval"] = config.Vedirect_PollInterval;
|
||||
|
||||
JsonObject powermeter = doc.createNestedObject("powermeter");
|
||||
powermeter["enabled"] = config.PowerMeter_Enabled;
|
||||
powermeter["interval"] = config.PowerMeter_Interval;
|
||||
powermeter["source"] = config.PowerMeter_Source;
|
||||
powermeter["mqtt_topic_powermeter_1"] = config.PowerMeter_MqttTopicPowerMeter1;
|
||||
powermeter["mqtt_topic_powermeter_2"] = config.PowerMeter_MqttTopicPowerMeter2;
|
||||
powermeter["mqtt_topic_powermeter_3"] = config.PowerMeter_MqttTopicPowerMeter3;
|
||||
powermeter["sdmbaudrate"] = config.PowerMeter_SdmBaudrate;
|
||||
powermeter["sdmaddress"] = config.PowerMeter_SdmAddress;
|
||||
|
||||
JsonObject powerlimiter = doc.createNestedObject("powerlimiter");
|
||||
powerlimiter["enabled"] = config.PowerLimiter_Enabled;
|
||||
powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter_SolarPassTroughEnabled;
|
||||
powerlimiter["battery_drain_strategy"] = config.PowerLimiter_BatteryDrainStategy;
|
||||
powerlimiter["interval"] = config.PowerLimiter_Interval;
|
||||
powerlimiter["mqtt_topic_powermeter_1"] = config.PowerLimiter_MqttTopicPowerMeter1;
|
||||
powerlimiter["mqtt_topic_powermeter_2"] = config.PowerLimiter_MqttTopicPowerMeter2;
|
||||
powerlimiter["mqtt_topic_powermeter_3"] = config.PowerLimiter_MqttTopicPowerMeter3;
|
||||
powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter;
|
||||
powerlimiter["inverter_id"] = config.PowerLimiter_InverterId;
|
||||
powerlimiter["inverter_channel_id"] = config.PowerLimiter_InverterChannelId;
|
||||
@ -281,14 +288,22 @@ bool ConfigurationClass::read()
|
||||
config.Vedirect_UpdatesOnly = vedirect["updates_only"] | VEDIRECT_UPDATESONLY;
|
||||
config.Vedirect_PollInterval = vedirect["poll_interval"] | VEDIRECT_POLL_INTERVAL;
|
||||
|
||||
JsonObject powermeter = doc["powermeter"];
|
||||
config.PowerMeter_Enabled = powermeter["enabled"] | POWERMETER_ENABLED;
|
||||
config.PowerMeter_Interval = POWERMETER_INTERVAL;
|
||||
config.PowerMeter_Source = POWERMETER_SOURCE;
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter1, powermeter["mqtt_topic_powermeter_1"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter2, powermeter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter3, powermeter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter3));
|
||||
config.PowerMeter_SdmBaudrate = POWERMETER_SDMBAUDRATE;
|
||||
config.PowerMeter_SdmAddress = POWERMETER_SDMADDRESS;
|
||||
|
||||
|
||||
JsonObject powerlimiter = doc["powerlimiter"];
|
||||
config.PowerLimiter_Enabled = powerlimiter["enabled"] | POWERLIMITER_ENABLED;
|
||||
config.PowerLimiter_SolarPassTroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTROUGH_ENABLED;
|
||||
config.PowerLimiter_BatteryDrainStategy = powerlimiter["battery_drain_strategy"] | POWERLIMITER_BATTERY_DRAIN_STRATEGY;
|
||||
config.PowerLimiter_Interval = POWERLIMITER_INTERVAL;
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter1, powerlimiter["mqtt_topic_powermeter_1"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, powerlimiter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, powerlimiter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter3));
|
||||
config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER;
|
||||
config.PowerLimiter_InverterId = powerlimiter["inverter_id"] | POWERLIMITER_INVERTER_ID;
|
||||
config.PowerLimiter_InverterChannelId = powerlimiter["inverter_channel_id"] | POWERLIMITER_INVERTER_CHANNEL_ID;
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
#include "Battery.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
@ -16,60 +17,18 @@ PowerLimiterClass PowerLimiter;
|
||||
|
||||
void PowerLimiterClass::init()
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::placeholders::_3;
|
||||
using std::placeholders::_4;
|
||||
using std::placeholders::_5;
|
||||
using std::placeholders::_6;
|
||||
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
// Zero export power limiter
|
||||
if (strlen(config.PowerLimiter_MqttTopicPowerMeter1) != 0) {
|
||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter1, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
if (strlen(config.PowerLimiter_MqttTopicPowerMeter2) != 0) {
|
||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter2, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
if (strlen(config.PowerLimiter_MqttTopicPowerMeter3) != 0) {
|
||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter3, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
_lastCommandSent = 0;
|
||||
_lastCommandSent = 0;
|
||||
_lastLoop = 0;
|
||||
_lastPowerMeterUpdate = 0;
|
||||
_lastRequestedPowerLimit = 0;
|
||||
}
|
||||
|
||||
void PowerLimiterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (strcmp(topic, config.PowerLimiter_MqttTopicPowerMeter1) == 0) {
|
||||
_powerMeter1Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerLimiter_MqttTopicPowerMeter2) == 0) {
|
||||
_powerMeter2Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerLimiter_MqttTopicPowerMeter3) == 0) {
|
||||
_powerMeter3Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
|
||||
void PowerLimiterClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (!config.PowerLimiter_Enabled
|
||||
|| !MqttSettings.getConnected()
|
||||
|| !config.PowerMeter_Enabled
|
||||
|| !Hoymiles.getRadio()->isIdle()
|
||||
|| (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000)
|
||||
|| (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) {
|
||||
|
||||
142
src/PowerMeter.cpp
Normal file
142
src/PowerMeter.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "PowerMeter.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "SDM.h"
|
||||
#include <ctime>
|
||||
|
||||
PowerMeterClass PowerMeter;
|
||||
|
||||
SDM sdm(Serial2, 9600, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN);
|
||||
|
||||
void PowerMeterClass::init()
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::placeholders::_3;
|
||||
using std::placeholders::_4;
|
||||
using std::placeholders::_5;
|
||||
using std::placeholders::_6;
|
||||
|
||||
_lastPowerMeterUpdate = 0;
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
//if(!mqttInitDone){
|
||||
if (strlen(config.PowerMeter_MqttTopicPowerMeter1) != 0 && !strcmp(PowerMeter_MqttTopicPowerMeter1old, config.PowerMeter_MqttTopicPowerMeter1)) {
|
||||
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter1, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
strlcpy(PowerMeter_MqttTopicPowerMeter1old, config.PowerMeter_MqttTopicPowerMeter1, sizeof(config.PowerMeter_MqttTopicPowerMeter3));
|
||||
}
|
||||
|
||||
if (strlen(config.PowerMeter_MqttTopicPowerMeter2) != 0 && !strcmp(PowerMeter_MqttTopicPowerMeter2old, config.PowerMeter_MqttTopicPowerMeter2)) {
|
||||
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter2, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
strlcpy(PowerMeter_MqttTopicPowerMeter2old, config.PowerMeter_MqttTopicPowerMeter2, sizeof(config.PowerMeter_MqttTopicPowerMeter3));
|
||||
}
|
||||
|
||||
if (strlen(config.PowerMeter_MqttTopicPowerMeter3) != 0 && !strcmp(PowerMeter_MqttTopicPowerMeter3old, config.PowerMeter_MqttTopicPowerMeter3)) {
|
||||
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter3, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
strlcpy(PowerMeter_MqttTopicPowerMeter3old, config.PowerMeter_MqttTopicPowerMeter3, sizeof(config.PowerMeter_MqttTopicPowerMeter3));
|
||||
}
|
||||
mqttInitDone = true;
|
||||
//}
|
||||
sdm.begin();
|
||||
/*if(config.PowerMeter_Source != 0){
|
||||
if (strlen(config.PowerMeter_MqttTopicPowerMeter1) != 0) {
|
||||
MqttSettings.unsubscribe(config.PowerMeter_MqttTopicPowerMeter1);
|
||||
}
|
||||
|
||||
if (strlen(config.PowerMeter_MqttTopicPowerMeter2) != 0) {
|
||||
MqttSettings.unsubscribe(config.PowerMeter_MqttTopicPowerMeter2);
|
||||
}
|
||||
|
||||
if (strlen(config.PowerMeter_MqttTopicPowerMeter3) != 0) {
|
||||
MqttSettings.unsubscribe(config.PowerMeter_MqttTopicPowerMeter3);
|
||||
}
|
||||
Hoymiles.getMessageOutput()->printf("PowerMeterClass: MQTT unsubscribed\n");
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
void PowerMeterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if(config.PowerMeter_Enabled && config.PowerMeter_Source == 0){
|
||||
Hoymiles.getMessageOutput()->printf("PowerMeterClass: Received MQTT message on topic: %s\n", topic);
|
||||
|
||||
if (strcmp(topic, config.PowerMeter_MqttTopicPowerMeter1) == 0) {
|
||||
_powerMeter1Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerMeter_MqttTopicPowerMeter2) == 0) {
|
||||
_powerMeter2Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerMeter_MqttTopicPowerMeter3) == 0) {
|
||||
_powerMeter3Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
Hoymiles.getMessageOutput()->printf("PowerMeterClass: TotalPower: %5.2f\n", getPowerTotal());
|
||||
}
|
||||
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
|
||||
float PowerMeterClass::getPowerTotal(){
|
||||
return _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
||||
}
|
||||
|
||||
void PowerMeterClass::mqtt(){
|
||||
if (!MqttSettings.getConnected()){
|
||||
return;
|
||||
}else{
|
||||
String topic = "powermeter";
|
||||
MqttSettings.publish(topic + "/power1", String(_powerMeter1Power));
|
||||
MqttSettings.publish(topic + "/power2", String(_powerMeter2Power));
|
||||
MqttSettings.publish(topic + "/power3", String(_powerMeter3Power));
|
||||
MqttSettings.publish(topic + "/powertotal", String(getPowerTotal()));
|
||||
MqttSettings.publish(topic + "/voltage1", String(_powerMeter1Voltage));
|
||||
MqttSettings.publish(topic + "/voltage2", String(_powerMeter2Voltage));
|
||||
MqttSettings.publish(topic + "/voltage3", String(_powerMeter3Voltage));
|
||||
MqttSettings.publish(topic + "/import", String(_PowerMeterImport));
|
||||
MqttSettings.publish(topic + "/export", String(_PowerMeterExport));
|
||||
}
|
||||
}
|
||||
|
||||
void PowerMeterClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if(config.PowerMeter_Enabled && millis() - _lastPowerMeterUpdate >= (config.PowerMeter_Interval * 1000)){
|
||||
uint8_t _address = config.PowerMeter_SdmAddress;
|
||||
if(config.PowerMeter_Source == 1){
|
||||
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
|
||||
_powerMeter2Power = 0.0;
|
||||
_powerMeter3Power = 0.0;
|
||||
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
||||
_powerMeter2Voltage = 0.0;
|
||||
_powerMeter3Voltage = 0.0;
|
||||
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
}
|
||||
if(config.PowerMeter_Source == 2){
|
||||
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
|
||||
_powerMeter2Power = static_cast<float>(sdm.readVal(SDM_PHASE_2_POWER, _address));
|
||||
_powerMeter3Power = static_cast<float>(sdm.readVal(SDM_PHASE_3_POWER, _address));
|
||||
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
||||
_powerMeter2Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_2_VOLTAGE, _address));
|
||||
_powerMeter3Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_3_VOLTAGE, _address));
|
||||
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
}
|
||||
|
||||
Hoymiles.getMessageOutput()->printf("PowerMeterClass: TotalPower: %5.2f\n", getPowerTotal());
|
||||
|
||||
mqtt();
|
||||
|
||||
_lastPowerMeterUpdate = millis();
|
||||
|
||||
}
|
||||
}
|
||||
@ -38,9 +38,6 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
|
||||
root[F("enabled")] = config.PowerLimiter_Enabled;
|
||||
root[F("solar_passtrough_enabled")] = config.PowerLimiter_SolarPassTroughEnabled;
|
||||
root[F("battery_drain_strategy")] = config.PowerLimiter_BatteryDrainStategy;
|
||||
root[F("mqtt_topic_powermeter_1")] = config.PowerLimiter_MqttTopicPowerMeter1;
|
||||
root[F("mqtt_topic_powermeter_2")] = config.PowerLimiter_MqttTopicPowerMeter2;
|
||||
root[F("mqtt_topic_powermeter_3")] = config.PowerLimiter_MqttTopicPowerMeter3;
|
||||
root[F("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter;
|
||||
root[F("inverter_id")] = config.PowerLimiter_InverterId;
|
||||
root[F("inverter_channel_id")] = config.PowerLimiter_InverterChannelId;
|
||||
@ -122,9 +119,6 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
config.PowerLimiter_Enabled = root[F("enabled")].as<bool>();
|
||||
config.PowerLimiter_SolarPassTroughEnabled = root[F("solar_passtrough_enabled")].as<bool>();
|
||||
config.PowerLimiter_BatteryDrainStategy= root[F("battery_drain_strategy")].as<uint8_t>();
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter1, root[F("mqtt_topic_powermeter_1")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter3));
|
||||
config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>();
|
||||
config.PowerLimiter_InverterId = root[F("inverter_id")].as<uint8_t>();
|
||||
config.PowerLimiter_InverterChannelId = root[F("inverter_channel_id")].as<uint8_t>();
|
||||
|
||||
123
src/WebApi_powermeter.cpp
Normal file
123
src/WebApi_powermeter.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_powermeter.h"
|
||||
#include "VeDirectFrameHandler.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "WebApi.h"
|
||||
#include "helper.h"
|
||||
|
||||
void WebApiPowerMeterClass::init(AsyncWebServer* server)
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
|
||||
_server = server;
|
||||
|
||||
_server->on("/api/powermeter/status", HTTP_GET, std::bind(&WebApiPowerMeterClass::onStatus, this, _1));
|
||||
_server->on("/api/powermeter/config", HTTP_GET, std::bind(&WebApiPowerMeterClass::onAdminGet, this, _1));
|
||||
_server->on("/api/powermeter/config", HTTP_POST, std::bind(&WebApiPowerMeterClass::onAdminPost, this, _1));
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::loop()
|
||||
{
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("enabled")] = config.PowerMeter_Enabled;
|
||||
root[F("source")] = config.PowerMeter_Source;
|
||||
root[F("interval")] = config.PowerMeter_Interval;
|
||||
root[F("mqtt_topic_powermeter_1")] = config.PowerMeter_MqttTopicPowerMeter1;
|
||||
root[F("mqtt_topic_powermeter_2")] = config.PowerMeter_MqttTopicPowerMeter2;
|
||||
root[F("mqtt_topic_powermeter_3")] = config.PowerMeter_MqttTopicPowerMeter3;
|
||||
root[F("sdmbaudrate")] = config.PowerMeter_SdmBaudrate;
|
||||
root[F("sdmaddress")] = config.PowerMeter_SdmAddress;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::onAdminGet(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->onStatus(request);
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("enabled") && root.containsKey("source"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
config.PowerMeter_Enabled = root[F("enabled")].as<bool>();
|
||||
config.PowerMeter_Source = root[F("source")].as<uint8_t>();
|
||||
config.PowerMeter_Interval = root[F("interval")].as<uint32_t>();
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter1, root[F("mqtt_topic_powermeter_1")].as<String>().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as<String>().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as<String>().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter3));
|
||||
config.PowerMeter_SdmBaudrate = root[F("sdmbaudrate")].as<uint32_t>();
|
||||
config.PowerMeter_SdmAddress = root[F("sdmaddress")].as<uint8_t>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
PowerMeter.init();
|
||||
MqttSettings.performReconnect();
|
||||
MqttHandleHass.forceUpdate();
|
||||
|
||||
}
|
||||
@ -20,6 +20,7 @@
|
||||
#include "SunPosition.h"
|
||||
#include "Utils.h"
|
||||
#include "WebApi.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "defaults.h"
|
||||
#include <Arduino.h>
|
||||
@ -146,6 +147,8 @@ void setup()
|
||||
} else {
|
||||
MessageOutput.println(F("Invalid pin config"));
|
||||
}
|
||||
// Power meter
|
||||
PowerMeter.init();
|
||||
|
||||
// Dynamic power limiter
|
||||
PowerLimiter.init();
|
||||
@ -165,6 +168,8 @@ void loop()
|
||||
{
|
||||
NetworkSettings.loop();
|
||||
yield();
|
||||
PowerMeter.loop();
|
||||
yield();
|
||||
PowerLimiter.loop();
|
||||
yield();
|
||||
InverterSettings.loop();
|
||||
|
||||
@ -51,6 +51,9 @@
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/vedirect">{{ $t('menu.VedirectSettings') }}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/powermeter">{{ $t('menu.PowerMeterSettings') }}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/powerlimiter">Dynamic Power Limiter</router-link>
|
||||
</li>
|
||||
|
||||
@ -452,6 +452,24 @@
|
||||
"UpdatesOnly": "Nur Änderungen senden:",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powermeteradmin":{
|
||||
"PowerMeterSettings": "Stromzähler Einstellungen",
|
||||
"PowerMeterConfiguration": "Stromzähler Konfiguration",
|
||||
"PowerMeterEnable": "Aktiviere Stromzähler",
|
||||
"PowerMeterParameter": "Power Meter Parameter",
|
||||
"PowerMeterSource": "Stromzählertyp",
|
||||
"MQTT": "MQTT Konfiguration",
|
||||
"typeMQTT": "MQTT",
|
||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",
|
||||
"SDM": "SDM-Stromzähler Konfiguration",
|
||||
"sdmbaudrate": "Baudrate",
|
||||
"sdmaddress": "Modbus Adresse",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powerlimiteradmin": {
|
||||
"PowerLimiterSettings": "Power Limiter Einstellungen",
|
||||
"PowerLimiterConfiguration": "Power Limiter Konfiguration",
|
||||
@ -473,9 +491,6 @@
|
||||
"LowerPowerLimit": "Unteres Leistungslimit",
|
||||
"UpperPowerLimit": "Oberes Leistungslimit",
|
||||
"PowerMeters": "Leistungsmesser - MQTT",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2 (Optional)",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3 (Optional)",
|
||||
"BatterySocStartThreshold": "Akku SOC - Start",
|
||||
"BatterySocStopThreshold": "Akku SOC - Stop",
|
||||
"VoltageStartThreshold": "DC Spannung - Start",
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"DTUSettings": "DTU Settings",
|
||||
"DeviceManager": "Device-Manager",
|
||||
"VedirectSettings": "Ve.direct Settings",
|
||||
"PowerMeterSettings": "Power Meter Settings",
|
||||
"BatterySettings": "@:batteryadmin.BatterySettings",
|
||||
"ConfigManagement": "Config Management",
|
||||
"FirmwareUpgrade": "Firmware Upgrade",
|
||||
@ -452,6 +453,24 @@
|
||||
"UpdatesOnly": "Send only updates:",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powermeteradmin":{
|
||||
"PowerMeterSettings": "Power Meter Settings",
|
||||
"PowerMeterConfiguration": "Power Meter Configuration",
|
||||
"PowerMeterEnable": "Enable Power Meter",
|
||||
"PowerMeterParameter": "Power Meter Parameter",
|
||||
"PowerMeterSource": "Power Meter type",
|
||||
"MQTT": "MQTT Parameter",
|
||||
"typeMQTT": "MQTT",
|
||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",
|
||||
"SDM": "SDM-Power Meter Parameter",
|
||||
"sdmbaudrate": "Baudrate",
|
||||
"sdmaddress": "Modbus Address",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powerlimiteradmin": {
|
||||
"PowerLimiterSettings": "Power Limiter Settings",
|
||||
"PowerLimiterConfiguration": "Power Limiter Configuration",
|
||||
|
||||
@ -7,6 +7,7 @@ import DtuAdminView from '@/views/DtuAdminView.vue';
|
||||
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
||||
import HomeView from '@/views/HomeView.vue';
|
||||
import VedirectAdminView from '@/views/VedirectAdminView.vue'
|
||||
import PowerMeterAdminView from '@/views/PowerMeterAdminView.vue'
|
||||
import PowerLimiterAdminView from '@/views/PowerLimiterAdminView.vue'
|
||||
import VedirectInfoView from '@/views/VedirectInfoView.vue'
|
||||
import InverterAdminView from '@/views/InverterAdminView.vue';
|
||||
@ -86,6 +87,11 @@ const router = createRouter({
|
||||
name: 'Ve.direct Settings',
|
||||
component: VedirectAdminView
|
||||
},
|
||||
{
|
||||
path: '/settings/powermeter',
|
||||
name: 'Power meter Settings',
|
||||
component: PowerMeterAdminView
|
||||
},
|
||||
{
|
||||
path: '/settings/powerlimiter',
|
||||
name: 'Power limiter Settings',
|
||||
|
||||
@ -2,9 +2,6 @@ export interface PowerLimiterConfig {
|
||||
enabled: boolean;
|
||||
solar_passtrough_enabled: boolean;
|
||||
battery_drain_strategy: number;
|
||||
mqtt_topic_powermeter_1: string;
|
||||
mqtt_topic_powermeter_2: string;
|
||||
mqtt_topic_powermeter_3: string;
|
||||
is_inverter_behind_powermeter: boolean;
|
||||
inverter_id: number;
|
||||
inverter_channel_id: number;
|
||||
|
||||
10
webapp/src/types/PowerMeterConfig.ts
Normal file
10
webapp/src/types/PowerMeterConfig.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface PowerMeterConfig {
|
||||
enabled: boolean;
|
||||
source: number;
|
||||
interval: number;
|
||||
mqtt_topic_powermeter_1: string;
|
||||
mqtt_topic_powermeter_2: string;
|
||||
mqtt_topic_powermeter_3: string;
|
||||
sdmbaudrate: number;
|
||||
sdmaddress: number;
|
||||
}
|
||||
@ -114,36 +114,6 @@
|
||||
<CardElement :text="$t('powerlimiteradmin.PowerMeters')" textVariant="text-bg-primary" add-space
|
||||
v-show="powerLimiterConfigList.enabled"
|
||||
>
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter1" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter1') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter1"
|
||||
placeholder="shellies/shellyem3/emeter/0/power" v-model="powerLimiterConfigList.mqtt_topic_powermeter_1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter2" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter2') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter2"
|
||||
placeholder="shellies/shellyem3/emeter/1/power" v-model="powerLimiterConfigList.mqtt_topic_powermeter_2" required/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter3" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter3') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter3"
|
||||
placeholder="shellies/shellyem3/emeter/2/power" v-model="powerLimiterConfigList.mqtt_topic_powermeter_3" required/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 form-check-label" for="inputRetain">{{ $t('powerlimiteradmin.InverterIsBehindPowerMeter') }}</label>
|
||||
<div class="col-sm-10">
|
||||
|
||||
160
webapp/src/views/PowerMeterAdminView.vue
Normal file
160
webapp/src/views/PowerMeterAdminView.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<BasePage :title="$t('powermeteradmin.PowerMeterSettings')" :isLoading="dataLoading">
|
||||
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
|
||||
{{ alertMessage }}
|
||||
</BootstrapAlert>
|
||||
|
||||
<form @submit="savePowerMeterConfig">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-primary">{{ $t('powermeteradmin.PowerMeterConfiguration') }}</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 form-check-label" for="inputPowerMeterEnable">{{ $t('powermeteradmin.PowerMeterEnable') }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="inputPowerMeterEnable"
|
||||
v-model="powerMeterConfigList.enabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputTimezone" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.PowerMeterSource') }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-select" v-model="powerMeterConfigList.source">
|
||||
<option v-for="source in powerMeterSourceList" :key="source.key" :value="source.key">
|
||||
{{ source.value }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" v-if="powerMeterConfigList.source === 0" >
|
||||
<div class="card-header text-bg-primary">{{ $t('powermeteradmin.MQTT') }}</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter1" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.MqttTopicPowerMeter1') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter1"
|
||||
placeholder="shellies/shellyem3/emeter/0/power" v-model="powerMeterConfigList.mqtt_topic_powermeter_1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter2" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.MqttTopicPowerMeter2') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter2"
|
||||
placeholder="shellies/shellyem3/emeter/1/power" v-model="powerMeterConfigList.mqtt_topic_powermeter_2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter3" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.MqttTopicPowerMeter3') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter3"
|
||||
placeholder="shellies/shellyem3/emeter/2/power" v-model="powerMeterConfigList.mqtt_topic_powermeter_3" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card" v-if="powerMeterConfigList.source === 1 || powerMeterConfigList.source === 2" >
|
||||
<div class="card-header text-bg-primary">{{ $t('powermeteradmin.SDM') }}</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<label for="sdmbaudrate" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.sdmbaudrate') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="sdmbaudrate"
|
||||
placeholder="9600" v-model="powerMeterConfigList.sdmbaudrate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="sdmaddress" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.sdmaddress') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="sdmaddress"
|
||||
placeholder="1" v-model="powerMeterConfigList.sdmaddress" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mb-3">{{ $t('powermeteradmin.Save') }}</button>
|
||||
</form>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import type { PowerMeterConfig } from "@/types/PowerMeterConfig";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BasePage,
|
||||
BootstrapAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataLoading: true,
|
||||
powerMeterConfigList: {} as PowerMeterConfig,
|
||||
powerMeterSourceList: [
|
||||
{ key: 0, value: this.$t('powermeteradmin.typeMQTT') },
|
||||
{ key: 1, value: this.$t('powermeteradmin.typeSDM1ph') },
|
||||
{ key: 2, value: this.$t('powermeteradmin.typeSDM3ph') },
|
||||
],
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
showAlert: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getPowerMeterConfig();
|
||||
},
|
||||
methods: {
|
||||
getPowerMeterConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/powermeter/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.powerMeterConfigList = data;
|
||||
this.dataLoading = false;
|
||||
});
|
||||
},
|
||||
savePowerMeterConfig(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(this.powerMeterConfigList));
|
||||
|
||||
fetch("/api/powermeter/config", {
|
||||
method: "POST",
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
this.alertType = response.type;
|
||||
this.showAlert = true;
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user