From f4b5e565ef5f3409f6fd1a2cdc943ee1b7cf5d50 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 1 Sep 2019 17:51:25 +0200
Subject: [PATCH] Add driver USE_SDM120_2 with Domoticz P1 Smart Meter
functionality as future replacement for USE_SDM120 - Pls test and report
Add driver USE_SDM120_2 with Domoticz P1 Smart Meter functionality as future replacement for USE_SDM120 - Pls test and report (#6282)
---
sonoff/_changelog.ino | 1 +
sonoff/my_user_config.h | 3 +-
sonoff/sonoff.h | 3 +-
sonoff/xdrv_03_energy.ino | 42 +++---
sonoff/xnrg_09_sdm120.ino | 293 ++++++++++++++++++++++++++++++++++++++
5 files changed, 322 insertions(+), 20 deletions(-)
create mode 100644 sonoff/xnrg_09_sdm120.ino
diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino
index 11520b0a2..06bd9d956 100644
--- a/sonoff/_changelog.ino
+++ b/sonoff/_changelog.ino
@@ -5,6 +5,7 @@
* Add compile time define USE_WS2812_HARDWARE to select hardware type WS2812, WS2812X, WS2813, SK6812, LC8812 or APA106 (DMA mode only)
* Add 'sonoff-ir' pre-packaged IR-dedicated firmware and 'sonoff-ircustom' to customize firmware with IR Full protocol support
* Add Zigbee support phase 2 - cc2530 initialization and basic ZCL decoding
+ * Add driver USE_SDM120_2 with Domoticz P1 Smart Meter functionality as future replacement for USE_SDM120 - Pls test and report
*
* 6.6.0.8 20190827
* Add Tuya Energy monitoring by Shantur Rathore
diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h
index 3ae3d9574..f55fc774f 100644
--- a/sonoff/my_user_config.h
+++ b/sonoff/my_user_config.h
@@ -429,8 +429,9 @@
#define USE_PZEM_AC // Add support for PZEM014,016 Energy monitor (+1k1 code)
#define USE_PZEM_DC // Add support for PZEM003,017 Energy monitor (+1k1 code)
#define USE_MCP39F501 // Add support for MCP39F501 Energy monitor as used in Shelly 2 (+3k1 code)
+//#define USE_SDM120_2 // Add support for Eastron SDM120-Modbus energy meter (+1k4 code)
-//#define USE_SDM120 // Add support for Eastron SDM120-Modbus energy meter (+1k7 code)
+//#define USE_SDM120 // Add support for Eastron SDM120-Modbus energy meter (+2k4 code)
#define SDM120_SPEED 2400 // SDM120-Modbus RS485 serial speed (default: 2400 baud)
#define USE_SDM220 // Add extra parameters for SDM220 (+0k1 code)
//#define USE_SDM630 // Add support for Eastron SDM630-Modbus energy meter (+2k code)
diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h
index 1ce925355..6b0c52ce4 100644
--- a/sonoff/sonoff.h
+++ b/sonoff/sonoff.h
@@ -113,6 +113,7 @@ const uint16_t SERIALLOG_TIMER = 600; // Seconds to disable SerialLog
const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching the new firmware
const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in (serial and http) command buffer
+const uint16_t FLOATSZ = 33; // Max number of characters in float result from dtostrfd
const uint16_t CMDSZ = 24; // Max number of characters in command
const uint16_t TOPSZ = 100; // Max number of characters in topic string
const uint16_t LOGSZ = 520; // Max number of characters in log
@@ -261,7 +262,7 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU
FUNC_PREP_BEFORE_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_SENSOR, FUNC_COMMAND, FUNC_COMMAND_SENSOR, FUNC_COMMAND_DRIVER,
FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA,
FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR,
- FUNC_ENERGY_EVERY_SECOND,
+ FUNC_ENERGY_EVERY_SECOND, FUNC_ENERGY_RESET,
FUNC_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM, FUNC_BUTTON_PRESSED,
FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS};
diff --git a/sonoff/xdrv_03_energy.ino b/sonoff/xdrv_03_energy.ino
index bf8e9afdc..2f8561f42 100644
--- a/sonoff/xdrv_03_energy.ino
+++ b/sonoff/xdrv_03_energy.ino
@@ -76,6 +76,7 @@ struct ENERGY {
float reactive_power = NAN; // 123.1 VAr
float power_factor = NAN; // 0.12
float frequency = NAN; // 123.1 Hz
+
float start_energy = 0; // 12345.12345 kWh total previous
float daily = 0; // 123.123 kWh
@@ -114,7 +115,6 @@ struct ENERGY {
uint8_t mplr_counter = 0;
uint8_t max_energy_state = 0;
#endif // USE_ENERGY_POWER_LIMIT
-
#endif // USE_ENERGY_MARGIN_DETECTION
} Energy;
@@ -384,9 +384,14 @@ void EnergyOverTempCheck()
Energy.voltage = 0;
Energy.current = 0;
Energy.active_power = 0;
+ if (!isnan(Energy.apparent_power)) { Energy.apparent_power = 0; }
+ if (!isnan(Energy.reactive_power)) { Energy.reactive_power = 0; }
if (!isnan(Energy.frequency)) { Energy.frequency = 0; }
if (!isnan(Energy.power_factor)) { Energy.power_factor = 0; }
Energy.start_energy = 0;
+
+ XnrgCall(FUNC_ENERGY_RESET);
+
}
}
}
@@ -450,11 +455,11 @@ void CmndEnergyReset(void)
RtcSettings.energy_usage.usage1_kWhtotal = Settings.energy_kWhtotal;
}
- char energy_total_chr[33];
+ char energy_total_chr[FLOATSZ];
dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr);
- char energy_daily_chr[33];
+ char energy_daily_chr[FLOATSZ];
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr);
- char energy_yesterday_chr[33];
+ char energy_yesterday_chr[FLOATSZ];
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s}}"),
@@ -696,7 +701,7 @@ const char HTTP_ENERGY_SNS1[] PROGMEM =
"{s}" D_POWER_FACTOR "{m}%s{e}";
const char HTTP_ENERGY_SNS2[] PROGMEM =
- "{s}" D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
+ "{s}" D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
"{s}" D_ENERGY_YESTERDAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
"{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; // {s} =
, {m} = | , {e} = |
#endif // USE_WEBSERVER
@@ -704,16 +709,15 @@ const char HTTP_ENERGY_SNS2[] PROGMEM =
void EnergyShow(bool json)
{
char speriod[20];
-// char sfrequency[20];
bool show_energy_period = (0 == tele_period);
float power_factor = Energy.power_factor;
- char apparent_power_chr[33];
- char reactive_power_chr[33];
- char power_factor_chr[33];
- char frequency_chr[33];
+ char apparent_power_chr[FLOATSZ];
+ char reactive_power_chr[FLOATSZ];
+ char power_factor_chr[FLOATSZ];
+ char frequency_chr[FLOATSZ];
if (!Energy.type_dc) {
if (Energy.current_available && Energy.voltage_available) {
float apparent_power = Energy.apparent_power;
@@ -749,21 +753,21 @@ void EnergyShow(bool json)
}
}
- char voltage_chr[33];
+ char voltage_chr[FLOATSZ];
dtostrfd(Energy.voltage, Settings.flag2.voltage_resolution, voltage_chr);
- char current_chr[33];
+ char current_chr[FLOATSZ];
dtostrfd(Energy.current, Settings.flag2.current_resolution, current_chr);
- char active_power_chr[33];
+ char active_power_chr[FLOATSZ];
dtostrfd(Energy.active_power, Settings.flag2.wattage_resolution, active_power_chr);
- char energy_daily_chr[33];
+ char energy_daily_chr[FLOATSZ];
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr);
- char energy_yesterday_chr[33];
+ char energy_yesterday_chr[FLOATSZ];
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
- char energy_total_chr[33];
+ char energy_total_chr[FLOATSZ];
dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr);
float energy = 0;
- char energy_period_chr[33];
+ char energy_period_chr[FLOATSZ];
if (show_energy_period) {
if (Energy.period) energy = (float)(Energy.kWhtoday - Energy.period) / 100;
Energy.period = Energy.kWhtoday;
@@ -789,6 +793,7 @@ void EnergyShow(bool json)
if (Energy.current_available) {
ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), current_chr);
}
+ XnrgCall(FUNC_JSON_APPEND);
ResponseJsonEnd();
#ifdef USE_DOMOTICZ
@@ -797,7 +802,7 @@ void EnergyShow(bool json)
DomoticzSensorPowerEnergy((int)Energy.active_power, energy_total_chr); // PowerUsage, EnergyToday
dtostrfd((Energy.total - Energy.total1) * 1000, 1, energy_total_chr); // Tariff2
- char energy_total1_chr[33];
+ char energy_total1_chr[FLOATSZ];
dtostrfd(Energy.total1 * 1000, 1, energy_total1_chr); // Tariff1
char energy_non[2] = "0";
DomoticzSensorP1SmartMeter(energy_total1_chr, energy_total_chr, energy_non, energy_non, (int)Energy.active_power, 0);
@@ -843,6 +848,7 @@ void EnergyShow(bool json)
}
}
WSContentSend_PD(HTTP_ENERGY_SNS2, energy_daily_chr, energy_yesterday_chr, energy_total_chr);
+ XnrgCall(FUNC_WEB_SENSOR);
#endif // USE_WEBSERVER
}
}
diff --git a/sonoff/xnrg_09_sdm120.ino b/sonoff/xnrg_09_sdm120.ino
new file mode 100644
index 000000000..e6070911c
--- /dev/null
+++ b/sonoff/xnrg_09_sdm120.ino
@@ -0,0 +1,293 @@
+/*
+ xnrg_09_sdm120.ino - Eastron SDM120-Modbus energy meter support for Sonoff-Tasmota
+
+ Copyright (C) 2019 Gennaro Tortone and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_ENERGY_SENSOR
+#ifdef USE_SDM120_2
+/*********************************************************************************************\
+ * Eastron SDM120 or SDM220 Modbus energy meter
+ *
+ * Based on: https://github.com/reaper7/SDM_Energy_Meter
+\*********************************************************************************************/
+
+#define XNRG_09 9
+
+// can be user defined in my_user_config.h
+#ifndef SDM120_SPEED
+ #define SDM120_SPEED 2400 // default SDM120 Modbus address
+#endif
+// can be user defined in my_user_config.h
+#ifndef SDM120_ADDR
+ #define SDM120_ADDR 1 // default SDM120 Modbus address
+#endif
+
+#include
+TasmotaModbus *Sdm120Modbus;
+
+const uint16_t sdm120_start_addresses[] {
+ 0x0000, // SDM120C_VOLTAGE [V]
+ 0x0006, // SDM120C_CURRENT [A]
+ 0x000C, // SDM120C_POWER [W]
+ 0x0012, // SDM120C_APPARENT_POWER [VA]
+ 0x0018, // SDM120C_REACTIVE_POWER [VAR]
+ 0x001E, // SDM120C_POWER_FACTOR
+ 0x0046, // SDM120C_FREQUENCY [Hz]
+#ifdef USE_SDM220
+ 0x0156, // SDM120C_TOTAL_ACTIVE_ENERGY [Wh]
+ 0X0024, // SDM220_PHASE_ANGLE [Degre]
+ 0X0048, // SDM220_IMPORT_ACTIVE [kWh]
+ 0X004A, // SDM220_EXPORT_ACTIVE [kWh]
+ 0X004C, // SDM220_IMPORT_REACTIVE [kVArh]
+ 0X004E, // SDM220_EXPORT_REACTIVE [kVArh]
+ 0X0158 // SDM220 TOTAL_REACTIVE [kVArh]
+#else // USE_SDM220
+ 0x0156 // SDM120C_TOTAL_ACTIVE_ENERGY [Wh]
+#endif // USE_SDM220
+};
+
+struct SDM120 {
+ uint8_t read_state = 0;
+ uint8_t send_retry = 0;
+} Sdm120;
+
+#ifdef USE_SDM220
+struct SDM220 {
+ float phase_angle = 0;
+ float import_active = 0;
+ float export_active = 0;
+ float import_reactive = 0;
+ float export_reactive = 0;
+ float total_reactive = 0;
+} Sdm220;
+#endif // USE_SDM220
+
+/*********************************************************************************************/
+
+void SDM120Every200ms(void)
+{
+ bool data_ready = Sdm120Modbus->ReceiveReady();
+
+ if (data_ready) {
+ uint8_t buffer[9];
+
+ uint32_t error = Sdm120Modbus->ReceiveBuffer(buffer, 2);
+ AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, (buffer[2]) ? buffer[2] +5 : sizeof(buffer));
+
+ if (error) {
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SDM120 response error %d"), error);
+ } else {
+ Energy.data_valid = 0;
+
+ float value;
+ ((uint8_t*)&value)[3] = buffer[3];
+ ((uint8_t*)&value)[2] = buffer[4];
+ ((uint8_t*)&value)[1] = buffer[5];
+ ((uint8_t*)&value)[0] = buffer[6];
+
+ switch(Sdm120.read_state) {
+ case 0:
+ Energy.voltage = value;
+ break;
+
+ case 1:
+ Energy.current = value;
+ break;
+
+ case 2:
+ Energy.active_power = value;
+ break;
+
+ case 3:
+ Energy.apparent_power = value;
+ break;
+
+ case 4:
+ Energy.reactive_power = value;
+ break;
+
+ case 5:
+ Energy.power_factor = value;
+ break;
+
+ case 6:
+ Energy.frequency = value;
+ break;
+
+ case 7:
+ if (!Energy.start_energy || (value < Energy.start_energy)) {
+ Energy.start_energy = value; // Init after restart and hanlde roll-over if any
+ }
+ if (value != Energy.start_energy) {
+ Energy.kWhtoday += (unsigned long)((value - Energy.start_energy) * 100);
+ Energy.start_energy = value;
+ }
+ EnergyUpdateToday();
+ break;
+
+#ifdef USE_SDM220
+ case 8:
+ Sdm220.phase_angle = value;
+ break;
+
+ case 9:
+ Sdm220.import_active = value;
+ break;
+
+ case 10:
+ Sdm220.export_active = value;
+ break;
+
+ case 11:
+ Sdm220.import_reactive = value;
+ break;
+
+ case 12:
+ Sdm220.export_reactive = value;
+ break;
+
+ case 13:
+ Sdm220.total_reactive = value;
+ break;
+#endif // USE_SDM220
+ } // end switch
+
+ Sdm120.read_state++;
+ if (sizeof(sdm120_start_addresses)/2 == Sdm120.read_state) {
+ Sdm120.read_state = 0;
+ }
+ }
+ } // end data ready
+
+ if (0 == Sdm120.send_retry || data_ready) {
+ Sdm120.send_retry = 5;
+ Sdm120Modbus->Send(SDM120_ADDR, 0x04, sdm120_start_addresses[Sdm120.read_state], 2);
+ } else {
+ Sdm120.send_retry--;
+ }
+}
+
+void Sdm120SnsInit(void)
+{
+ Sdm120Modbus = new TasmotaModbus(pin[GPIO_SDM120_RX], pin[GPIO_SDM120_TX]);
+ uint8_t result = Sdm120Modbus->Begin(SDM120_SPEED);
+ if (result) {
+ if (2 == result) { ClaimSerial(); }
+ } else {
+ energy_flg = ENERGY_NONE;
+ }
+}
+
+void Sdm120DrvInit(void)
+{
+ if (!energy_flg) {
+ if ((pin[GPIO_SDM120_RX] < 99) && (pin[GPIO_SDM120_TX] < 99)) {
+ energy_flg = XNRG_09;
+ }
+ }
+}
+
+#ifdef USE_SDM220
+
+void Sdm220Reset(void)
+{
+ Sdm220.phase_angle = 0;
+ Sdm220.import_active = 0;
+ Sdm220.export_active = 0;
+ Sdm220.import_reactive = 0;
+ Sdm220.export_reactive = 0;
+ Sdm220.total_reactive = 0;
+}
+
+#ifdef USE_WEBSERVER
+const char HTTP_ENERGY_SDM220[] PROGMEM =
+ "{s}" D_PHASE_ANGLE "{m}%s " D_UNIT_ANGLE "{e}"
+ "{s}" D_IMPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
+ "{s}" D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
+ "{s}" D_IMPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
+ "{s}" D_EXPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
+ "{s}" D_TOTAL_REACTIVE "{m}%s " D_UNIT_KWARH "{e}";
+#endif // USE_WEBSERVER
+
+void Sdm220Show(bool json)
+{
+ char phase_angle_chr[FLOATSZ];
+ dtostrfd(Sdm220.phase_angle, 2, phase_angle_chr);
+ char import_active_chr[FLOATSZ];
+ dtostrfd(Sdm220.import_active, Settings.flag2.wattage_resolution, import_active_chr);
+ char export_active_chr[FLOATSZ];
+ dtostrfd(Sdm220.export_active, Settings.flag2.wattage_resolution, export_active_chr);
+ char import_reactive_chr[FLOATSZ];
+ dtostrfd(Sdm220.import_reactive, Settings.flag2.wattage_resolution, import_reactive_chr);
+ char export_reactive_chr[FLOATSZ];
+ dtostrfd(Sdm220.export_reactive, Settings.flag2.wattage_resolution, export_reactive_chr);
+ char total_reactive_chr[FLOATSZ];
+ dtostrfd(Sdm220.total_reactive, Settings.flag2.wattage_resolution, total_reactive_chr);
+
+ if (json) {
+ ResponseAppend_P(PSTR(",\"" D_JSON_PHASE_ANGLE "\":%s,\"" D_JSON_IMPORT_ACTIVE "\":%s,\"" D_JSON_EXPORT_ACTIVE "\":%s,\"" D_JSON_IMPORT_REACTIVE "\":%s,\"" D_JSON_EXPORT_REACTIVE "\":%s,\"" D_JSON_TOTAL_REACTIVE "\":%s"),
+ phase_angle_chr, import_active_chr, export_active_chr, import_reactive_chr, export_reactive_chr, total_reactive_chr);
+#ifdef USE_WEBSERVER
+ } else {
+ WSContentSend_PD(HTTP_ENERGY_SDM220, phase_angle_chr, import_active_chr, export_active_chr, import_reactive_chr, export_reactive_chr, total_reactive_chr);
+#endif // USE_WEBSERVER
+ }
+}
+
+#endif // USE_SDM220
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+int Xnrg09(uint8_t function)
+{
+ int result = 0;
+
+ if (FUNC_PRE_INIT == function) {
+ Sdm120DrvInit();
+ }
+ else if (XNRG_09 == energy_flg) {
+ switch (function) {
+ case FUNC_INIT:
+ Sdm120SnsInit();
+ break;
+ case FUNC_EVERY_200_MSECOND:
+ if (uptime > 4) { SDM120Every200ms(); }
+ break;
+
+#ifdef USE_SDM220
+ case FUNC_ENERGY_RESET:
+ Sdm220Reset();
+ break;
+ case FUNC_JSON_APPEND:
+ Sdm220Show(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+ Sdm220Show(0);
+ break;
+#endif // USE_WEBSERVER
+#endif // USE_SDM220
+
+ }
+ }
+ return result;
+}
+
+#endif // USE_SDM120_2
+#endif // USE_ENERGY_SENSOR