PowerMeter Class + SDM PowerMeter support

This commit is contained in:
Adminius 2023-03-21 23:46:54 +01:00
parent dbb548e731
commit 05a5b2367b
25 changed files with 1239 additions and 96 deletions

View File

@ -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;

View File

@ -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
View 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;

View 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;
};

View File

@ -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
View 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
View 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

View 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
//------------------------------------------------------------------------------

View File

@ -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;

View File

@ -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
View 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();
}
}

View File

@ -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
View 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();
}

View File

@ -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();

View File

@ -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>

View File

@ -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",

View File

@ -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",

View File

@ -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',

View File

@ -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;

View 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;
}

View File

@ -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">

View 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.