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)
This commit is contained in:
Theo Arends 2019-09-01 17:51:25 +02:00
parent 7edcb84eab
commit f4b5e565ef
5 changed files with 322 additions and 20 deletions

View File

@ -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 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 '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 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 * 6.6.0.8 20190827
* Add Tuya Energy monitoring by Shantur Rathore * Add Tuya Energy monitoring by Shantur Rathore

View File

@ -429,8 +429,9 @@
#define USE_PZEM_AC // Add support for PZEM014,016 Energy monitor (+1k1 code) #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_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_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 SDM120_SPEED 2400 // SDM120-Modbus RS485 serial speed (default: 2400 baud)
#define USE_SDM220 // Add extra parameters for SDM220 (+0k1 code) #define USE_SDM220 // Add extra parameters for SDM220 (+0k1 code)
//#define USE_SDM630 // Add support for Eastron SDM630-Modbus energy meter (+2k code) //#define USE_SDM630 // Add support for Eastron SDM630-Modbus energy meter (+2k code)

View File

@ -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 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 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 CMDSZ = 24; // Max number of characters in command
const uint16_t TOPSZ = 100; // Max number of characters in topic string const uint16_t TOPSZ = 100; // Max number of characters in topic string
const uint16_t LOGSZ = 520; // Max number of characters in log 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_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_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA,
FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR, 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_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}; FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS};

View File

@ -76,6 +76,7 @@ struct ENERGY {
float reactive_power = NAN; // 123.1 VAr float reactive_power = NAN; // 123.1 VAr
float power_factor = NAN; // 0.12 float power_factor = NAN; // 0.12
float frequency = NAN; // 123.1 Hz float frequency = NAN; // 123.1 Hz
float start_energy = 0; // 12345.12345 kWh total previous float start_energy = 0; // 12345.12345 kWh total previous
float daily = 0; // 123.123 kWh float daily = 0; // 123.123 kWh
@ -114,7 +115,6 @@ struct ENERGY {
uint8_t mplr_counter = 0; uint8_t mplr_counter = 0;
uint8_t max_energy_state = 0; uint8_t max_energy_state = 0;
#endif // USE_ENERGY_POWER_LIMIT #endif // USE_ENERGY_POWER_LIMIT
#endif // USE_ENERGY_MARGIN_DETECTION #endif // USE_ENERGY_MARGIN_DETECTION
} Energy; } Energy;
@ -384,9 +384,14 @@ void EnergyOverTempCheck()
Energy.voltage = 0; Energy.voltage = 0;
Energy.current = 0; Energy.current = 0;
Energy.active_power = 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.frequency)) { Energy.frequency = 0; }
if (!isnan(Energy.power_factor)) { Energy.power_factor = 0; } if (!isnan(Energy.power_factor)) { Energy.power_factor = 0; }
Energy.start_energy = 0; Energy.start_energy = 0;
XnrgCall(FUNC_ENERGY_RESET);
} }
} }
} }
@ -450,11 +455,11 @@ void CmndEnergyReset(void)
RtcSettings.energy_usage.usage1_kWhtotal = Settings.energy_kWhtotal; 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); 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); 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); 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}}"), Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s}}"),
@ -704,16 +709,15 @@ const char HTTP_ENERGY_SNS2[] PROGMEM =
void EnergyShow(bool json) void EnergyShow(bool json)
{ {
char speriod[20]; char speriod[20];
// char sfrequency[20];
bool show_energy_period = (0 == tele_period); bool show_energy_period = (0 == tele_period);
float power_factor = Energy.power_factor; float power_factor = Energy.power_factor;
char apparent_power_chr[33]; char apparent_power_chr[FLOATSZ];
char reactive_power_chr[33]; char reactive_power_chr[FLOATSZ];
char power_factor_chr[33]; char power_factor_chr[FLOATSZ];
char frequency_chr[33]; char frequency_chr[FLOATSZ];
if (!Energy.type_dc) { if (!Energy.type_dc) {
if (Energy.current_available && Energy.voltage_available) { if (Energy.current_available && Energy.voltage_available) {
float apparent_power = Energy.apparent_power; 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); 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); 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); 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); 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); 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); dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr);
float energy = 0; float energy = 0;
char energy_period_chr[33]; char energy_period_chr[FLOATSZ];
if (show_energy_period) { if (show_energy_period) {
if (Energy.period) energy = (float)(Energy.kWhtoday - Energy.period) / 100; if (Energy.period) energy = (float)(Energy.kWhtoday - Energy.period) / 100;
Energy.period = Energy.kWhtoday; Energy.period = Energy.kWhtoday;
@ -789,6 +793,7 @@ void EnergyShow(bool json)
if (Energy.current_available) { if (Energy.current_available) {
ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), current_chr); ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), current_chr);
} }
XnrgCall(FUNC_JSON_APPEND);
ResponseJsonEnd(); ResponseJsonEnd();
#ifdef USE_DOMOTICZ #ifdef USE_DOMOTICZ
@ -797,7 +802,7 @@ void EnergyShow(bool json)
DomoticzSensorPowerEnergy((int)Energy.active_power, energy_total_chr); // PowerUsage, EnergyToday DomoticzSensorPowerEnergy((int)Energy.active_power, energy_total_chr); // PowerUsage, EnergyToday
dtostrfd((Energy.total - Energy.total1) * 1000, 1, energy_total_chr); // Tariff2 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 dtostrfd(Energy.total1 * 1000, 1, energy_total1_chr); // Tariff1
char energy_non[2] = "0"; char energy_non[2] = "0";
DomoticzSensorP1SmartMeter(energy_total1_chr, energy_total_chr, energy_non, energy_non, (int)Energy.active_power, 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); WSContentSend_PD(HTTP_ENERGY_SNS2, energy_daily_chr, energy_yesterday_chr, energy_total_chr);
XnrgCall(FUNC_WEB_SENSOR);
#endif // USE_WEBSERVER #endif // USE_WEBSERVER
} }
} }

293
sonoff/xnrg_09_sdm120.ino Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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.h>
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