diff --git a/README.md b/README.md index 45cb17c95..3feb7fc5b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Sonoff-Tasmota Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE. -Current version is **5.10.0a** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information. +Current version is **5.10.0b** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information. ### ATTENTION All versions diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index 714897f4d..2d9dd0b27 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,8 @@ -/* 5.10.0a +/* 5.10.0b + * Change Sonoff Pow Energy MQTT data JSON message and consolidate Status 8 into Status 10 + * Add optional support for PZEM004T energy sensor to enable with define USE_PZEM004T in user_config.h (#614) + * + * 5.10.0a * Add (experimental) support for sensor SHT3x * Add support for sensor MH-Z19(B) using serial interface to be enabled with define USE_MHZ19_HARD_SERIAL in user_config.h (#561, #1248) * Add (experimental) support for sensor MH-Z19(B) using SoftwareSerial to be enabled with define USE_MHZ19_SOFT_SERIAL_OBSOLETE in user_config.h (#561, #1248) @@ -12,8 +16,10 @@ * Add support for TSL2561 using adafruit library (#661, #1311) * Add alternative support for SHT3x enabled with define USE_SHT3X_V2 in user_config.h (#1314) * Add alternative support for SHT3x enabled with define USE_SHT3X_V3 in user_config.h (#1314) + * Fix PWM initialization in Dimmer/Color mode (#1321) * Fix BME680 pressure data (#1356) * Minor webpage HTML optimizations (#1358) + * Change text to Active for 3 minutes (#1364) * * 5.10.0 20171201 * Upgrade library ArduinoJson to 5.11.2 diff --git a/sonoff/language/de-DE.h b/sonoff/language/de-DE.h index 4f2b3d531..45b9d0c55 100644 --- a/sonoff/language/de-DE.h +++ b/sonoff/language/de-DE.h @@ -233,7 +233,7 @@ #define D_OSWATCH "osWatch" #define D_BLOCKED_LOOP "Blocked Loop" #define D_WPS_FAILED_WITH_STATUS "WPS fehlgeschlagen mit Status" -#define D_ACTIVE_FOR_1_MINUTE "aktiv für 1 Minute" +#define D_ACTIVE_FOR_3_MINUTES "aktiv für 3 Minuten" #define D_FAILED_TO_START "Starten fehlgeschlagen" #define D_PATCH_ISSUE_2186 "Patch-Problem 2186" #define D_CONNECTING_TO_AP "verbinden mit AP" @@ -431,7 +431,7 @@ #define D_HUE_POST_ARGS "Hue POST args" #define D_3_RESPONSE_PACKETS_SENT "3 Antwortpakete gesendet" -// xsns_03_hlw8012.ino +// xsns_03_energy.ino #define D_MAXPOWERREACHED "MaxPowerReached" #define D_MAXPOWERREACHEDRETRY "MaxPowerReachedRetry" #define D_POWERMONITOR "PowerMonitor" @@ -477,6 +477,8 @@ #define D_SENSOR_IRRECV "IRRecv" #define D_SENSOR_MHZ_RX "MHZ Rx" #define D_SENSOR_MHZ_TX "MHZ Tx" +#define D_SENSOR_PZEM_RX "PZEM Rx" +#define D_SENSOR_PZEM_TX "PZEM Tx" #define D_SENSOR_SPI_CS "SPI CS" #define D_SENSOR_SPI_DC "SPI DC" #define D_SENSOR_BACKLIGHT "BLight" @@ -678,7 +680,7 @@ #define D_CMND_RFLOW "RfLow" #define D_CMND_RFSYNC "RfSync" -// Commands xsns_03_hlw8012.ino +// Commands xsns_03_energy.ino #define D_CMND_POWERLOW "PowerLow" #define D_CMND_POWERHIGH "PowerHigh" #define D_CMND_VOLTAGELOW "VoltageLow" diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h index d08e358df..571fff023 100644 --- a/sonoff/language/en-GB.h +++ b/sonoff/language/en-GB.h @@ -233,7 +233,7 @@ #define D_OSWATCH "osWatch" #define D_BLOCKED_LOOP "Blocked Loop" #define D_WPS_FAILED_WITH_STATUS "WPSconfig FAILED with status" -#define D_ACTIVE_FOR_1_MINUTE "active for 1 minute" +#define D_ACTIVE_FOR_3_MINUTES "active for 3 minutes" #define D_FAILED_TO_START "failed to start" #define D_PATCH_ISSUE_2186 "Patch issue 2186" #define D_CONNECTING_TO_AP "Connecting to AP" @@ -431,7 +431,7 @@ #define D_HUE_POST_ARGS "Hue POST args" #define D_3_RESPONSE_PACKETS_SENT "3 response packets sent" -// xsns_03_hlw8012.ino +// xsns_03_energy.ino #define D_MAXPOWERREACHED "MaxPowerReached" #define D_MAXPOWERREACHEDRETRY "MaxPowerReachedRetry" #define D_POWERMONITOR "PowerMonitor" @@ -477,6 +477,8 @@ #define D_SENSOR_IRRECV "IRrecv" #define D_SENSOR_MHZ_RX "MHZ Rx" #define D_SENSOR_MHZ_TX "MHZ Tx" +#define D_SENSOR_PZEM_RX "PZEM Rx" +#define D_SENSOR_PZEM_TX "PZEM Tx" #define D_SENSOR_SPI_CS "SPI CS" #define D_SENSOR_SPI_DC "SPI DC" #define D_SENSOR_BACKLIGHT "BLight" @@ -678,7 +680,7 @@ #define D_CMND_RFLOW "RfLow" #define D_CMND_RFSYNC "RfSync" -// Commands xsns_03_hlw8012.ino +// Commands xsns_03_energy.ino #define D_CMND_POWERLOW "PowerLow" #define D_CMND_POWERHIGH "PowerHigh" #define D_CMND_VOLTAGELOW "VoltageLow" diff --git a/sonoff/language/nl-NL.h b/sonoff/language/nl-NL.h index 870ed1a32..13b1b49cb 100644 --- a/sonoff/language/nl-NL.h +++ b/sonoff/language/nl-NL.h @@ -233,7 +233,7 @@ #define D_OSWATCH "osWatch" #define D_BLOCKED_LOOP "Blocked Loop" #define D_WPS_FAILED_WITH_STATUS "WPSconfig mislukt met status" -#define D_ACTIVE_FOR_1_MINUTE "1 minuut actief" +#define D_ACTIVE_FOR_3_MINUTES "3 minuten actief" #define D_FAILED_TO_START "mislukt" #define D_PATCH_ISSUE_2186 "Patch issue 2186" #define D_CONNECTING_TO_AP "Verbinden met AP" @@ -431,7 +431,7 @@ #define D_HUE_POST_ARGS "Hue POST argumenten" #define D_3_RESPONSE_PACKETS_SENT "3 antwoord paketten verstuurd" -// xsns_03_hlw8012.ino +// xsns_03_energy.ino #define D_MAXPOWERREACHED "MaxPowerReached" #define D_MAXPOWERREACHEDRETRY "MaxPowerReachedRetry" #define D_POWERMONITOR "PowerMonitor" @@ -476,6 +476,8 @@ #define D_SENSOR_COUNTER "Teller" // Suffix "1" #define D_SENSOR_MHZ_RX "MHZ Rx" #define D_SENSOR_MHZ_TX "MHZ Tx" +#define D_SENSOR_PZEM_RX "PZEM Rx" +#define D_SENSOR_PZEM_TX "PZEM Tx" #define D_SENSOR_IRRECV "IRrecv" #define D_SENSOR_SPI_CS "SPI CS" #define D_SENSOR_SPI_DC "SPI DC" @@ -678,7 +680,7 @@ #define D_CMND_RFLOW "RfLow" #define D_CMND_RFSYNC "RfSync" -// Commands xsns_03_hlw8012.ino +// Commands xsns_03_energy.ino #define D_CMND_POWERLOW "PowerLow" #define D_CMND_POWERHIGH "PowerHigh" #define D_CMND_VOLTAGELOW "VoltageLow" diff --git a/sonoff/language/pl-PL.h b/sonoff/language/pl-PL.h index 4aa60da2f..4fd42d4fc 100644 --- a/sonoff/language/pl-PL.h +++ b/sonoff/language/pl-PL.h @@ -233,7 +233,7 @@ #define D_OSWATCH "osWatch" #define D_BLOCKED_LOOP "Petla zablokowana" #define D_WPS_FAILED_WITH_STATUS "Blad WPSconfig ze statusem" -#define D_ACTIVE_FOR_1_MINUTE "aktywny 1 minute" +#define D_ACTIVE_FOR_3_MINUTES "aktywny 3 minuty" #define D_FAILED_TO_START "nie udalo sie uruchomic" #define D_PATCH_ISSUE_2186 "Blad latki 2186" #define D_CONNECTING_TO_AP "Laczenie z AP" @@ -431,7 +431,7 @@ #define D_HUE_POST_ARGS "Hue POST args" #define D_3_RESPONSE_PACKETS_SENT "3 pakiety odpowiedzi wysylane" -// xsns_03_hlw8012.ino +// xsns_03_energy.ino #define D_MAXPOWERREACHED "MaksMocOsiagnieta" #define D_MAXPOWERREACHEDRETRY "MaksMocOsiagnietaPonowienie" #define D_POWERMONITOR "MonitorMocy" @@ -477,6 +477,8 @@ #define D_SENSOR_IRRECV "IRrecv" #define D_SENSOR_MHZ_RX "MHZ Rx" #define D_SENSOR_MHZ_TX "MHZ Tx" +#define D_SENSOR_PZEM_RX "PZEM Rx" +#define D_SENSOR_PZEM_TX "PZEM Tx" #define D_SENSOR_SPI_CS "SPI CS" #define D_SENSOR_SPI_DC "SPI DC" #define D_SENSOR_BACKLIGHT "BLight" @@ -678,7 +680,7 @@ #define D_CMND_RFLOW "RfLow" #define D_CMND_RFSYNC "RfSync" -// Commands xsns_03_hlw8012.ino +// Commands xsns_03_energy.ino #define D_CMND_POWERLOW "PowerLow" #define D_CMND_POWERHIGH "PowerHigh" #define D_CMND_VOLTAGELOW "VoltageLow" diff --git a/sonoff/settings.h b/sonoff/settings.h index afd6d3f5e..049d6a000 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -158,23 +158,23 @@ struct SYSCFG { unsigned long hlw_power_calibration; // 364 unsigned long hlw_voltage_calibration; // 368 unsigned long hlw_current_calibration; // 36C - unsigned long hlw_kWhtoday; // 370 - unsigned long hlw_kWhyesterday; // 374 - uint16_t hlw_kWhdoy; // 378 - uint16_t hlw_pmin; // 37A - uint16_t hlw_pmax; // 37C - uint16_t hlw_umin; // 37E - uint16_t hlw_umax; // 380 - uint16_t hlw_imin; // 382 - uint16_t hlw_imax; // 384 - uint16_t hlw_mpl; // 386 MaxPowerLimit - uint16_t hlw_mplh; // 388 MaxPowerLimitHold - uint16_t hlw_mplw; // 38A MaxPowerLimitWindow - uint16_t hlw_mspl; // 38C MaxSafePowerLimit - uint16_t hlw_msplh; // 38E MaxSafePowerLimitHold - uint16_t hlw_msplw; // 390 MaxSafePowerLimitWindow - uint16_t hlw_mkwh; // 392 MaxEnergy - uint16_t hlw_mkwhs; // 394 MaxEnergyStart + unsigned long energy_kWhtoday; // 370 + unsigned long energy_kWhyesterday; // 374 + uint16_t energy_kWhdoy; // 378 + uint16_t energy_min_power; // 37A + uint16_t energy_max_power; // 37C + uint16_t energy_min_voltage; // 37E + uint16_t energy_max_voltage; // 380 + uint16_t energy_min_current; // 382 + uint16_t energy_max_current; // 384 + uint16_t energy_max_power_limit; // 386 MaxPowerLimit + uint16_t energy_max_power_limit_hold; // 388 MaxPowerLimitHold + uint16_t energy_max_power_limit_window; // 38A MaxPowerLimitWindow + uint16_t energy_max_power_safe_limit; // 38C MaxSafePowerLimit + uint16_t energy_max_power_safe_limit_hold; // 38E MaxSafePowerLimitHold + uint16_t energy_max_power_safe_limit_window; // 390 MaxSafePowerLimitWindow + uint16_t energy_max_energy; // 392 MaxEnergy + uint16_t energy_max_energy_start; // 394 MaxEnergyStart uint16_t mqtt_retry; // 396 uint8_t poweronstate; // 398 uint8_t last_module; // 399 @@ -238,7 +238,7 @@ struct SYSCFG { byte free_542[2]; // 542 uint32_t ip_address[4]; // 544 - unsigned long hlw_kWhtotal; // 554 + unsigned long energy_kWhtotal; // 554 char mqtt_fulltopic[100]; // 558 SysBitfield2 flag2; // 5BC Add flag2 since 5.9.2 @@ -254,8 +254,8 @@ struct RTCMEM { uint16_t valid; // 000 byte oswatch_blocked_loop; // 002 uint8_t unused; // 003 - unsigned long hlw_kWhtoday; // 004 - unsigned long hlw_kWhtotal; // 008 + unsigned long energy_kWhtoday; // 004 + unsigned long energy_kWhtotal; // 008 unsigned long pulse_counter[MAX_COUNTERS]; // 00C power_t power; // 01C } RtcSettings; diff --git a/sonoff/settings.ino b/sonoff/settings.ino index 3e52fcf1b..1a2035c22 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -63,8 +63,8 @@ void RtcSettingsLoad() if (RtcSettings.valid != RTC_MEM_VALID) { memset(&RtcSettings, 0, sizeof(RTCMEM)); RtcSettings.valid = RTC_MEM_VALID; - RtcSettings.hlw_kWhtoday = Settings.hlw_kWhtoday; - RtcSettings.hlw_kWhtotal = Settings.hlw_kWhtotal; + RtcSettings.energy_kWhtoday = Settings.energy_kWhtoday; + RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal; for (byte i = 0; i < MAX_COUNTERS; i++) { RtcSettings.pulse_counter[i] = Settings.pulse_counter[i]; } @@ -182,10 +182,7 @@ void SettingsSaveAll() } else { Settings.power = 0; } - if (hlw_flg) { - HlwSaveState(); - } - CounterSaveState(); + XsnsCall(FUNC_XSNS_SAVE_STATE); SettingsSave(0); } @@ -454,23 +451,23 @@ void SettingsDefaultSet2() Settings.hlw_power_calibration = HLW_PREF_PULSE; Settings.hlw_voltage_calibration = HLW_UREF_PULSE; Settings.hlw_current_calibration = HLW_IREF_PULSE; -// Settings.hlw_kWhtoday = 0; -// Settings.hlw_kWhyesterday = 0; -// Settings.hlw_kWhdoy = 0; -// Settings.hlw_pmin = 0; -// Settings.hlw_pmax = 0; -// Settings.hlw_umin = 0; -// Settings.hlw_umax = 0; -// Settings.hlw_imin = 0; -// Settings.hlw_imax = 0; -// Settings.hlw_mpl = 0; // MaxPowerLimit - Settings.hlw_mplh = MAX_POWER_HOLD; - Settings.hlw_mplw = MAX_POWER_WINDOW; -// Settings.hlw_mspl = 0; // MaxSafePowerLimit - Settings.hlw_msplh = SAFE_POWER_HOLD; - Settings.hlw_msplw = SAFE_POWER_WINDOW; -// Settings.hlw_mkwh = 0; // MaxEnergy -// Settings.hlw_mkwhs = 0; // MaxEnergyStart +// Settings.energy_kWhtoday = 0; +// Settings.energy_kWhyesterday = 0; +// Settings.energy_kWhdoy = 0; +// Settings.energy_min_power = 0; +// Settings.energy_max_power = 0; +// Settings.energy_min_voltage = 0; +// Settings.energy_max_voltage = 0; +// Settings.energy_min_current = 0; +// Settings.energy_max_current = 0; +// Settings.energy_max_power_limit = 0; // MaxPowerLimit + Settings.energy_max_power_limit_hold = MAX_POWER_HOLD; + Settings.energy_max_power_limit_window = MAX_POWER_WINDOW; +// Settings.energy_max_power_safe_limit = 0; // MaxSafePowerLimit + Settings.energy_max_power_safe_limit_hold = SAFE_POWER_HOLD; + Settings.energy_max_power_safe_limit_window = SAFE_POWER_WINDOW; +// Settings.energy_max_energy = 0; // MaxEnergy +// Settings.energy_max_energy_start = 0; // MaxEnergyStart SettingsDefaultSet_3_2_4(); @@ -501,8 +498,8 @@ void SettingsDefaultSet2() SettingsDefaultSet_5_0_2(); // 5.0.4 -// Settings.hlw_kWhtotal = 0; - RtcSettings.hlw_kWhtotal = 0; +// Settings.energy_kWhtotal = 0; + RtcSettings.energy_kWhtotal = 0; // 5.0.5 strlcpy(Settings.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(Settings.mqtt_fulltopic)); @@ -712,8 +709,8 @@ void SettingsDelta() Settings.save_data = SAVE_DATA; } if (Settings.version < 0x05000400) { - Settings.hlw_kWhtotal = 0; - RtcSettings.hlw_kWhtotal = 0; + Settings.energy_kWhtotal = 0; + RtcSettings.energy_kWhtotal = 0; } if (Settings.version < 0x05000500) { strlcpy(Settings.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(Settings.mqtt_fulltopic)); diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index a3dda29a1..e2e1553f9 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -132,7 +132,7 @@ enum LightTypes {LT_BASIC, LT_PWM1, LT_PWM2, LT_PWM3, LT_PWM4, LT_PWM5, LT_PWM6, enum LichtSubtypes {LST_NONE, LST_SINGLE, LST_COLDWARM, LST_RGB, LST_RGBW, LST_RGBWC}; enum LichtSchemes {LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX}; -enum XsnsFunctions {FUNC_XSNS_INIT, FUNC_XSNS_PREP, FUNC_XSNS_JSON_APPEND, FUNC_XSNS_MQTT_SHOW, FUNC_XSNS_WEB}; +enum XsnsFunctions {FUNC_XSNS_INIT, FUNC_XSNS_EVERY_SECOND, FUNC_XSNS_PREP, FUNC_XSNS_JSON_APPEND, FUNC_XSNS_WEB, FUNC_XSNS_SAVE_STATE}; const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 }; diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 78f662ea7..11e642d78 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -25,8 +25,8 @@ - Select IDE Tools - Flash Size: "1M (no SPIFFS)" ====================================================*/ -#define VERSION 0x050A0001 -#define VERSION_STRING "5.10.0a" // Would be great to have a macro that fills this from VERSION ... +#define VERSION 0x050A0002 +#define VERSION_STRING "5.10.0b" // Would be great to have a macro that fills this from VERSION ... // Location specific includes #include "sonoff.h" // Enumaration used in user_config.h @@ -42,7 +42,7 @@ #if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MESSZ // If the max message size is too small, throw an error at compile time. See PubSubClient.cpp line 359 #error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 512" #endif -#include // RTC, HLW8012, OSWatch +#include // RTC, Energy, OSWatch #include // MQTT, Ota, WifiManager #include // MQTT, Ota #include // Ota @@ -177,7 +177,7 @@ power_t rel_inverted = 0; // Relay inverted flag (1 = (0 = On, uint8_t led_inverted = 0; // LED inverted flag (1 = (0 = On, 1 = Off)) uint8_t pwm_inverted = 0; // PWM inverted flag (1 = inverted) uint8_t dht_flg = 0; // DHT configured -uint8_t hlw_flg = 0; // Power monitor configured +uint8_t energy_flg = 1; // Energy monitor configured uint8_t i2c_flg = 0; // I2C configured uint8_t spi_flg = 0; // SPI configured uint8_t light_type = 0; // Light types @@ -189,7 +189,7 @@ boolean (*xsns_func_ptr[XSNS_MAX])(byte); // External Sensor Function Pointers char my_hostname[33]; // Composed Wifi hostname char mqtt_client[33]; // Composed MQTT Clientname char serial_in_buffer[INPUT_BUFFER_SIZE + 2]; // Receive buffer -char mqtt_data[MESSZ]; // MQTT publish buffer +char mqtt_data[TOPSZ + MESSZ]; // MQTT publish buffer char log_data[TOPSZ + MESSZ]; // Logging String web_log[MAX_LOG_LINES]; // Web log buffer String backlog[MAX_BACKLOG]; // Command backlog @@ -325,7 +325,7 @@ void SetDevicePower(power_t rpower) rpower >>= 1; } } - HlwSetPowerSteadyCounter(2); + EnergySetPowerSteadyCounter(2); } void SetLedPower(uint8_t state) @@ -1497,7 +1497,7 @@ void MqttDataCallback(char* topic, byte* data, unsigned int data_len) else if (Settings.flag.mqtt_enabled && MqttCommand(grpflg, type, index, dataBuf, data_len, payload, payload16)) { // Serviced } - else if (hlw_flg && HlwCommand(type, index, dataBuf, data_len, payload)) { + else if (energy_flg && EnergyCommand(type, index, dataBuf, data_len, payload)) { // Serviced } else if ((SONOFF_BRIDGE == Settings.module) && SonoffBridgeCommand(type, index, dataBuf, data_len, payload)) { @@ -1704,7 +1704,7 @@ void PublishStatus(uint8_t payload) if ((!Settings.flag.mqtt_enabled) && (6 == payload)) { payload = 99; } - if ((!hlw_flg) && ((8 == payload) || (9 == payload))) { + if (!energy_flg && (9 == payload)) { payload = 99; } @@ -1757,24 +1757,23 @@ void PublishStatus(uint8_t payload) MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "7")); } - if (hlw_flg) { - if ((0 == payload) || (8 == payload)) { - HlwMqttStatus(); - MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "8")); - } - + if (energy_flg) { if ((0 == payload) || (9 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\"" D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"), - Settings.hlw_pmin, Settings.hlw_pmax, Settings.hlw_umin, Settings.hlw_umax, Settings.hlw_imin, Settings.hlw_imax); + Settings.energy_min_power, Settings.energy_max_power, Settings.energy_min_voltage, Settings.energy_max_voltage, Settings.energy_min_current, Settings.energy_max_current); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "9")); } } - if ((0 == payload) || (10 == payload)) { + if ((0 == payload) || (8 == payload) || (10 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS10_SENSOR "\":")); MqttShowSensor(); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); - MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "10")); + if (8 == payload) { + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "8")); + } else { + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "10")); + } } if ((0 == payload) || (11 == payload)) { @@ -1884,14 +1883,10 @@ void PerformEverySecond() if (MqttShowSensor()) { MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); } - - XsnsCall(FUNC_XSNS_MQTT_SHOW); } } - if (hlw_flg) { - HlwMarginCheck(); - } + XsnsCall(FUNC_XSNS_EVERY_SECOND); if ((2 == RtcTime.minute) && latest_uptime_flag) { latest_uptime_flag = false; @@ -2627,8 +2622,6 @@ void GpioInit() } #endif // USE_IR_RECEIVE #endif // USE_IR_REMOTE - - hlw_flg = ((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)); } extern "C" { diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h index 93af8d3d6..7a16e17d0 100644 --- a/sonoff/sonoff_post.h +++ b/sonoff/sonoff_post.h @@ -58,6 +58,9 @@ void WifiWpsStatusCallback(wps_cb_status status); #ifdef USE_EMULATION #undef USE_EMULATION // Disable Wemo or Hue emulation #endif +#ifdef USE_PZEM004T +#undef USE_PZEM004T // Disable PZEM004T energy sensor +#endif #ifdef USE_DS18x20 #undef USE_DS18x20 // Disable DS18x20 sensor #endif diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 53ae6bcdb..cbe0037a7 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -78,8 +78,10 @@ enum UserSelectablePins { GPIO_LED2_INV, GPIO_LED3_INV, GPIO_LED4_INV, - GPIO_MHZ_TXD, - GPIO_MHZ_RXD, + GPIO_MHZ_TXD, // MH-Z19 Serial interface + GPIO_MHZ_RXD, // MH-Z19 Serial interface + GPIO_PZEM_TX, // PZEM004T Serial interface + GPIO_PZEM_RX, // PZEM004T Serial interface GPIO_SENSOR_END }; // Text in webpage Module Parameters and commands GPIOS and GPIO @@ -141,7 +143,9 @@ const char kSensors[GPIO_SENSOR_END][9] PROGMEM = { D_SENSOR_LED "3i", D_SENSOR_LED "4i", D_SENSOR_MHZ_TX, - D_SENSOR_MHZ_RX + D_SENSOR_MHZ_RX, + D_SENSOR_PZEM_TX, + D_SENSOR_PZEM_RX }; // Programmer selectable GPIO functionality offset by user selectable GPIOs diff --git a/sonoff/support.ino b/sonoff/support.ino index 1c1300129..dd6d6c92d 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -434,12 +434,12 @@ void WifiConfig(uint8_t type) restart_flag = 2; } else if (WIFI_SMARTCONFIG == wifi_config_type) { - AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_1_SMARTCONFIG D_ACTIVE_FOR_1_MINUTE)); + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_1_SMARTCONFIG D_ACTIVE_FOR_3_MINUTES)); WiFi.beginSmartConfig(); } else if (WIFI_WPSCONFIG == wifi_config_type) { if (WifiWpsConfigBegin()) { - AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_3_WPSCONFIG D_ACTIVE_FOR_1_MINUTE)); + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_3_WPSCONFIG D_ACTIVE_FOR_3_MINUTES)); } else { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_3_WPSCONFIG D_FAILED_TO_START)); wifi_config_counter = 3; @@ -447,7 +447,7 @@ void WifiConfig(uint8_t type) } #ifdef USE_WEBSERVER else if (WIFI_MANAGER == wifi_config_type) { - AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_2_WIFIMANAGER D_ACTIVE_FOR_1_MINUTE)); + AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_2_WIFIMANAGER D_ACTIVE_FOR_3_MINUTES)); WifiManagerBegin(); } #endif // USE_WEBSERVER diff --git a/sonoff/user_config.h b/sonoff/user_config.h index ffd4c6d39..bb73b3943 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -163,6 +163,8 @@ // -- Sensor code selection ----------------------- #define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices +//#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k3 code) + // WARNING: Select none for default one DS18B20 sensor or enable one of the following two options for multiple sensors //#define USE_DS18x20 // Optional for more than one DS18x20 sensors with id sort, single scan and read retry (+1k3 code) //#define USE_DS18x20_LEGACY // Optional for more than one DS18x20 sensors with dynamic scan using library OneWire (+1k5 code) diff --git a/sonoff/xdrv_light.ino b/sonoff/xdrv_light.ino index 478ef70be..2eff11895 100644 --- a/sonoff/xdrv_light.ino +++ b/sonoff/xdrv_light.ino @@ -339,6 +339,9 @@ void LightInit(void) if (light_type < LT_PWM6) { // PWM for (byte i = 0; i < light_type; i++) { Settings.pwm_value[i] = 0; // Disable direct PWM control + if (pin[GPIO_PWM1 +i] < 99) { + pinMode(pin[GPIO_PWM1 +i], OUTPUT); + } } if (LT_PWM1 == light_type) { Settings.light_color[0] = 255; // One PWM channel only supports Dimmer but needs max color diff --git a/sonoff/xsns_01_counter.ino b/sonoff/xsns_01_counter.ino index 0b263611f..007662aff 100644 --- a/sonoff/xsns_01_counter.ino +++ b/sonoff/xsns_01_counter.ino @@ -59,6 +59,8 @@ void CounterUpdate4() CounterUpdate(4); } +/********************************************************************************************/ + void CounterSaveState() { for (byte i = 0; i < MAX_COUNTERS; i++) { @@ -68,8 +70,6 @@ void CounterSaveState() } } -/********************************************************************************************/ - void CounterInit() { typedef void (*function) () ; @@ -143,6 +143,9 @@ boolean Xsns01(byte function) CounterShow(0); break; #endif // USE_WEBSERVER + case FUNC_XSNS_SAVE_STATE: + CounterSaveState(); + break; } return result; } diff --git a/sonoff/xsns_03_energy.ino b/sonoff/xsns_03_energy.ino new file mode 100644 index 000000000..649ea2993 --- /dev/null +++ b/sonoff/xsns_03_energy.ino @@ -0,0 +1,1171 @@ +/* + xsns_03_energy.ino - HLW8012 (Sonoff Pow) and PZEM004T energy sensor support for Sonoff-Tasmota + + Copyright (C) 2017 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 . +*/ + +#define USE_ENERGY_SENSOR + +#ifdef USE_ENERGY_SENSOR +/*********************************************************************************************\ + * HLW8012 and PZEM004T - Energy +\*********************************************************************************************/ + +#define FEATURE_POWER_LIMIT true + +enum EnergyHardware { ENERGY_NONE, ENERGY_HLW8012, ENERGY_PZEM004T }; + +enum EnergyCommands { + CMND_POWERLOW, CMND_POWERHIGH, CMND_VOLTAGELOW, CMND_VOLTAGEHIGH, CMND_CURRENTLOW, CMND_CURRENTHIGH, + CMND_HLWPCAL, CMND_HLWPSET, CMND_HLWUCAL, CMND_HLWUSET, CMND_HLWICAL, CMND_HLWISET, + CMND_ENERGYRESET, CMND_MAXENERGY, CMND_MAXENERGYSTART, + CMND_MAXPOWER, CMND_MAXPOWERHOLD, CMND_MAXPOWERWINDOW, + CMND_SAFEPOWER, CMND_SAFEPOWERHOLD, CMND_SAFEPOWERWINDOW }; +const char kEnergyCommands[] PROGMEM = + D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|" + D_CMND_HLWPCAL "|" D_CMND_HLWPSET "|" D_CMND_HLWUCAL "|" D_CMND_HLWUSET "|" D_CMND_HLWICAL "|" D_CMND_HLWISET "|" + D_CMND_ENERGYRESET "|" D_CMND_MAXENERGY "|" D_CMND_MAXENERGYSTART "|" + D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|" + D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW ; + +bool energy_power_factor_ready = false; +float energy_voltage = 0; // 123.1 V +float energy_current = 0; // 123.123 A +float energy_power = 0; // 123.1 W +float energy_power_factor = 0; // 0.12 +float energy_daily = 0; // 12.123 kWh +float energy_total = 0; // 12345.12345 kWh +float energy_start = 0; // 12345.12345 kWh total from yesterday +unsigned long energy_kWhtoday; // 1212312345 Wh * 10^-5 (deca micro Watt hours) - 5763924 = 0.05763924 kWh = 0.058 kWh = energy_daily +unsigned long energy_period = 0; // + +byte energy_min_power_flag = 0; +byte energy_max_power_flag = 0; +byte energy_min_voltage_flag = 0; +byte energy_max_voltage_flag = 0; +byte energy_min_current_flag = 0; +byte energy_max_current_flag = 0; + +byte energy_power_steady_cntr = 8; // Allow for power on stabilization +byte energy_max_energy_state = 0; + +#if FEATURE_POWER_LIMIT +byte energy_mplr_counter = 0; +uint16_t energy_mplh_counter = 0; +uint16_t energy_mplw_counter = 0; +#endif // FEATURE_POWER_LIMIT + +byte energy_startup = 1; +byte energy_fifth_second = 0; +Ticker ticker_energy; + +/*********************************************************************************************\ + * HLW8012 - Energy + * + * Based on Source: Shenzhen Heli Technology Co., Ltd +\*********************************************************************************************/ + +#define HLW_PREF 10000 // 1000.0W +#define HLW_UREF 2200 // 220.0V +#define HLW_IREF 4545 // 4.545A + +#define HLW_POWER_PROBE_TIME 10 // Number of seconds to probe for power before deciding none used + +byte hlw_select_ui_flag; +byte hlw_load_off; +byte hlw_cf1_timer; +unsigned long hlw_cf_pulse_length; +unsigned long hlw_cf_pulse_last_time; +unsigned long hlw_cf1_pulse_length; +unsigned long hlw_cf1_pulse_last_time; +unsigned long hlw_cf1_summed_pulse_length; +unsigned long hlw_cf1_pulse_counter; +unsigned long hlw_cf1_voltage_pulse_length; +unsigned long hlw_cf1_current_pulse_length; +unsigned long hlw_energy_period_counter; + +unsigned long hlw_cf1_voltage_max_pulse_counter; +unsigned long hlw_cf1_current_max_pulse_counter; + +#ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves exception +void HlwCfInterrupt() ICACHE_RAM_ATTR; +void HlwCf1Interrupt() ICACHE_RAM_ATTR; +#endif // USE_WS2812_DMA + +void HlwCfInterrupt() // Service Power +{ + unsigned long us = micros(); + + if (hlw_load_off) { // Restart plen measurement + hlw_cf_pulse_last_time = us; + hlw_load_off = 0; + } else { + hlw_cf_pulse_length = us - hlw_cf_pulse_last_time; + hlw_cf_pulse_last_time = us; + hlw_energy_period_counter++; + } +} + +void HlwCf1Interrupt() // Service Voltage and Current +{ + unsigned long us = micros(); + + hlw_cf1_pulse_length = us - hlw_cf1_pulse_last_time; + hlw_cf1_pulse_last_time = us; + if ((hlw_cf1_timer > 2) && (hlw_cf1_timer < 8)) { // Allow for 300 mSec set-up time and measure for up to 1 second + hlw_cf1_summed_pulse_length += hlw_cf1_pulse_length; + hlw_cf1_pulse_counter++; + if (10 == hlw_cf1_pulse_counter) { + hlw_cf1_timer = 8; // We need up to ten samples within 1 second (low current could take up to 0.3 second) + } + } +} + +void HlwEverySecond() +{ + unsigned long hlw_len; + unsigned long hlw_temp; + + if (hlw_energy_period_counter) { + hlw_len = 10000 / hlw_energy_period_counter; + hlw_energy_period_counter = 0; + if (hlw_len) { + hlw_temp = ((HLW_PREF * Settings.hlw_power_calibration) / hlw_len) / 36; + energy_kWhtoday += hlw_temp; + RtcSettings.energy_kWhtoday = energy_kWhtoday; + + energy_total = (float)(RtcSettings.energy_kWhtotal + (energy_kWhtoday / 1000)) / 100000; + energy_daily = (float)energy_kWhtoday / 100000000; + } + } +} + +void HlwEvery200ms() +{ + unsigned long hlw_w = 0; + unsigned long hlw_u = 0; + unsigned long hlw_i = 0; + + if (micros() - hlw_cf_pulse_last_time > (HLW_POWER_PROBE_TIME * 1000000)) { + hlw_cf_pulse_length = 0; // No load for some time + hlw_load_off = 1; + } + + if (hlw_cf_pulse_length && (power &1) && !hlw_load_off) { + hlw_w = (HLW_PREF * Settings.hlw_power_calibration) / hlw_cf_pulse_length; + energy_power = (float)hlw_w / 10; + } else { + energy_power = 0; + } + + hlw_cf1_timer++; + if (hlw_cf1_timer >= 8) { + hlw_cf1_timer = 0; + hlw_select_ui_flag = (hlw_select_ui_flag) ? 0 : 1; + digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); + + if (hlw_cf1_pulse_counter) { + hlw_cf1_pulse_length = hlw_cf1_summed_pulse_length / hlw_cf1_pulse_counter; + } else { + hlw_cf1_pulse_length = 0; + } + if (hlw_select_ui_flag) { + hlw_cf1_voltage_pulse_length = hlw_cf1_pulse_length; + hlw_cf1_voltage_max_pulse_counter = hlw_cf1_pulse_counter; + + if (hlw_cf1_voltage_pulse_length && (power &1)) { // If powered on always provide voltage + hlw_u = (HLW_UREF * Settings.hlw_voltage_calibration) / hlw_cf1_voltage_pulse_length; + energy_voltage = (float)hlw_u / 10; + } else { + energy_voltage = 0; + } + + } else { + hlw_cf1_current_pulse_length = hlw_cf1_pulse_length; + hlw_cf1_current_max_pulse_counter = hlw_cf1_pulse_counter; + + if (hlw_cf1_current_pulse_length && energy_power) { // No current if no power being consumed + hlw_i = (HLW_IREF * Settings.hlw_current_calibration) / hlw_cf1_current_pulse_length; + energy_current = (float)hlw_i / 1000; + } else { + energy_current = 0; + } + + } + hlw_cf1_summed_pulse_length = 0; + hlw_cf1_pulse_counter = 0; + } + +/* + energy_power = 0; + if (hlw_cf_pulse_length && (power &1) && !hlw_load_off) { + hlw_w = (HLW_PREF * Settings.hlw_power_calibration) / hlw_cf_pulse_length; + energy_power = (float)hlw_w / 10; + } + energy_voltage = 0; + if (hlw_cf1_voltage_pulse_length && (power &1)) { // If powered on always provide voltage + hlw_u = (HLW_UREF * Settings.hlw_voltage_calibration) / hlw_cf1_voltage_pulse_length; + energy_voltage = (float)hlw_u / 10; + } + energy_current = 0; + if (hlw_cf1_current_pulse_length && energy_power) { // No current if no power being consumed + hlw_i = (HLW_IREF * Settings.hlw_current_calibration) / hlw_cf1_current_pulse_length; + energy_current = (float)hlw_i / 1000; + } +*/ + energy_power_factor_ready = true; +} + +void HlwInit() +{ + if (!Settings.hlw_power_calibration || (4975 == Settings.hlw_power_calibration)) { + Settings.hlw_power_calibration = HLW_PREF_PULSE; + Settings.hlw_voltage_calibration = HLW_UREF_PULSE; + Settings.hlw_current_calibration = HLW_IREF_PULSE; + } + + hlw_cf_pulse_length = 0; + hlw_cf_pulse_last_time = 0; + hlw_cf1_pulse_length = 0; + hlw_cf1_pulse_last_time = 0; + hlw_cf1_voltage_pulse_length = 0; + hlw_cf1_current_pulse_length = 0; + hlw_cf1_voltage_max_pulse_counter = 0; + hlw_cf1_current_max_pulse_counter = 0; + + hlw_load_off = 1; + hlw_energy_period_counter = 0; + + hlw_select_ui_flag = 0; // Voltage; + + pinMode(pin[GPIO_HLW_SEL], OUTPUT); + digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); + pinMode(pin[GPIO_HLW_CF1], INPUT_PULLUP); + attachInterrupt(pin[GPIO_HLW_CF1], HlwCf1Interrupt, FALLING); + pinMode(pin[GPIO_HLW_CF], INPUT_PULLUP); + attachInterrupt(pin[GPIO_HLW_CF], HlwCfInterrupt, FALLING); + + hlw_cf1_timer = 0; +} + +#ifdef USE_PZEM004T +/*********************************************************************************************\ + * PZEM004T - Energy + * + * Source: Victor Ferrer https://github.com/vicfergar/Sonoff-MQTT-OTA-Arduino + * Based on: PZEM004T library https://github.com/olehs/PZEM004T +\*********************************************************************************************/ + +#define PZEM_BAUD_RATE 9600 + +/*********************************************************************************************\ + * Subset SoftwareSerial +\*********************************************************************************************/ + +#define PZEM_SERIAL_BUFFER_SIZE 20 +#define PZEM_SERIAL_WAIT { while (ESP.getCycleCount() -start < wait) optimistic_yield(1); wait += pzem_serial_bit_time; } + +uint8_t pzem_serial_rx_pin; +uint8_t pzem_serial_tx_pin; +uint8_t pzem_serial_in_pos = 0; +uint8_t pzem_serial_out_pos = 0; +uint8_t pzem_serial_buffer[PZEM_SERIAL_BUFFER_SIZE]; +unsigned long pzem_serial_bit_time; +unsigned long pzem_serial_bit_time_start; + +bool PzemSerialValidGpioPin(uint8_t pin) { + return (pin >= 0 && pin <= 5) || (pin >= 9 && pin <= 10) || (pin >= 12 && pin <= 15); +} + +bool PzemSerial(uint8_t receive_pin, uint8_t transmit_pin) +{ + if (!((PzemSerialValidGpioPin(receive_pin)) && (PzemSerialValidGpioPin(transmit_pin) || transmit_pin == 16))) { + return false; + } + pzem_serial_rx_pin = receive_pin; + pinMode(pzem_serial_rx_pin, INPUT); + attachInterrupt(pzem_serial_rx_pin, PzemSerialRxRead, FALLING); + + pzem_serial_tx_pin = transmit_pin; + pinMode(pzem_serial_tx_pin, OUTPUT); + digitalWrite(pzem_serial_tx_pin, 1); + + pzem_serial_bit_time = ESP.getCpuFreqMHz() *1000000 /PZEM_BAUD_RATE; // 8333 + pzem_serial_bit_time_start = pzem_serial_bit_time + pzem_serial_bit_time /3 -500; // 10610 ICACHE_RAM_ATTR start delay +// pzem_serial_bit_time_start = pzem_serial_bit_time; // Non ICACHE_RAM_ATTR start delay (experimental) + + return true; +} + +int PzemSerialRead() { + if (pzem_serial_in_pos == pzem_serial_out_pos) { + return -1; + } + int ch = pzem_serial_buffer[pzem_serial_out_pos]; + pzem_serial_out_pos = (pzem_serial_out_pos +1) % PZEM_SERIAL_BUFFER_SIZE; + return ch; +} + +int PzemSerialAvailable() { + int avail = pzem_serial_in_pos - pzem_serial_out_pos; + if (avail < 0) { + avail += PZEM_SERIAL_BUFFER_SIZE; + } + return avail; +} + +size_t PzemSerialTxWrite(uint8_t b) +{ + unsigned long wait = pzem_serial_bit_time; + digitalWrite(pzem_serial_tx_pin, HIGH); + unsigned long start = ESP.getCycleCount(); + // Start bit; + digitalWrite(pzem_serial_tx_pin, LOW); + PZEM_SERIAL_WAIT; + for (int i = 0; i < 8; i++) { + digitalWrite(pzem_serial_tx_pin, (b & 1) ? HIGH : LOW); + PZEM_SERIAL_WAIT; + b >>= 1; + } + // Stop bit + digitalWrite(pzem_serial_tx_pin, HIGH); + PZEM_SERIAL_WAIT; + return 1; +} + +size_t PzemSerialWrite(const uint8_t *buffer, size_t size = 1) { + size_t n = 0; + while(size--) { + n += PzemSerialTxWrite(*buffer++); + } + return n; +} + +//void PzemSerialRxRead() ICACHE_RAM_ATTR; // Add 215 bytes to iram usage +void PzemSerialRxRead() { + // Advance the starting point for the samples but compensate for the + // initial delay which occurs before the interrupt is delivered + unsigned long wait = pzem_serial_bit_time_start; + unsigned long start = ESP.getCycleCount(); + uint8_t rec = 0; + for (int i = 0; i < 8; i++) { + PZEM_SERIAL_WAIT; + rec >>= 1; + if (digitalRead(pzem_serial_rx_pin)) { + rec |= 0x80; + } + } + // Stop bit + PZEM_SERIAL_WAIT; + // Store the received value in the buffer unless we have an overflow + int next = (pzem_serial_in_pos +1) % PZEM_SERIAL_BUFFER_SIZE; + if (next != pzem_serial_out_pos) { + pzem_serial_buffer[pzem_serial_in_pos] = rec; + pzem_serial_in_pos = next; + } + // Must clear this bit in the interrupt register, + // it gets set even when interrupts are disabled + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pzem_serial_rx_pin); +} + +/*********************************************************************************************/ + +#define PZEM_VOLTAGE (uint8_t)0xB0 +#define RESP_VOLTAGE (uint8_t)0xA0 + +#define PZEM_CURRENT (uint8_t)0xB1 +#define RESP_CURRENT (uint8_t)0xA1 + +#define PZEM_POWER (uint8_t)0xB2 +#define RESP_POWER (uint8_t)0xA2 + +#define PZEM_ENERGY (uint8_t)0xB3 +#define RESP_ENERGY (uint8_t)0xA3 + +#define PZEM_SET_ADDRESS (uint8_t)0xB4 +#define RESP_SET_ADDRESS (uint8_t)0xA4 + +#define PZEM_POWER_ALARM (uint8_t)0xB5 +#define RESP_POWER_ALARM (uint8_t)0xA5 + +#define RESPONSE_SIZE sizeof(PZEMCommand) +#define RESPONSE_DATA_SIZE RESPONSE_SIZE - 2 + +#define PZEM_DEFAULT_READ_TIMEOUT 500 +#define PZEM_ERROR_VALUE -1.0 + +struct PZEMCommand { + uint8_t command; + uint8_t addr[4]; + uint8_t data; + uint8_t crc; +}; + +IPAddress pzem_ip(192, 168, 1, 1); + +float PZEM004T_voltage_rcv() +{ + uint8_t data[RESPONSE_DATA_SIZE]; + + if (!PZEM004T_recieve(RESP_VOLTAGE, data)) { + return PZEM_ERROR_VALUE; + } + return (data[0] << 8) + data[1] + (data[2] / 10.0); // 65535.x V +} + +float PZEM004T_current_rcv() +{ + uint8_t data[RESPONSE_DATA_SIZE]; + + if (!PZEM004T_recieve(RESP_CURRENT, data)) { + return PZEM_ERROR_VALUE; + } + return (data[0] << 8) + data[1] + (data[2] / 100.0); // 65535.xx A +} + +float PZEM004T_power_rcv() +{ + uint8_t data[RESPONSE_DATA_SIZE]; + + if (!PZEM004T_recieve(RESP_POWER, data)) { + return PZEM_ERROR_VALUE; + } + return (data[0] << 8) + data[1]; // 65535 W +} + +float PZEM004T_energy_rcv() +{ + uint8_t data[RESPONSE_DATA_SIZE]; + + if (!PZEM004T_recieve(RESP_ENERGY, data)) { + return PZEM_ERROR_VALUE; + } + return ((uint32_t)data[0] << 16) + ((uint16_t)data[1] << 8) + data[2]; // 16777215 Wh +} + +bool PZEM004T_setAddress_rcv() +{ + return PZEM004T_recieve(RESP_SET_ADDRESS, 0); +} + +void PZEM004T_send(uint8_t cmd) +{ + PZEMCommand pzem; + + pzem.command = cmd; + for (int i = 0; i < sizeof(pzem.addr); i++) { + pzem.addr[i] = pzem_ip[i]; + } + pzem.data = 0; + + uint8_t *bytes = (uint8_t*)&pzem; + pzem.crc = PZEM004T_crc(bytes, sizeof(pzem) - 1); + + while (PzemSerialAvailable()) { + PzemSerialRead(); + } + PzemSerialWrite(bytes, sizeof(pzem)); +} + +bool PZEM004T_isReady() +{ + return PzemSerialAvailable() >= RESPONSE_SIZE; +} + +bool PZEM004T_recieve(uint8_t resp, uint8_t *data) +{ + uint8_t buffer[RESPONSE_SIZE]; + + unsigned long startTime = millis(); + uint8_t len = 0; +// while ((len < RESPONSE_SIZE) && (millis() - startTime < PZEM_DEFAULT_READ_TIMEOUT)) { + while ((len < RESPONSE_SIZE) && (millis() - startTime < PZEM_DEFAULT_READ_TIMEOUT)) { + if (PzemSerialAvailable() > 0) { + uint8_t c = (uint8_t)PzemSerialRead(); + if (!c && !len) { + continue; // skip 0 at startup + } + buffer[len++] = c; + } +// yield(); // do background netw tasks while blocked for IO (prevents ESP watchdog trigger) - This triggers Watchdog!!! + } + + if (len != RESPONSE_SIZE) { + return false; + } + if (buffer[6] != PZEM004T_crc(buffer, len - 1)) { + return false; + } + if (buffer[0] != resp) { + return false; + } + if (data) { + for (int i = 0; i < RESPONSE_DATA_SIZE; i++) { + data[i] = buffer[1 + i]; + } + } + + return true; +} + +uint8_t PZEM004T_crc(uint8_t *data, uint8_t sz) +{ + uint16_t crc = 0; + for (uint8_t i = 0; i < sz; i++) { + crc += *data++; + } + return (uint8_t)(crc & 0xFF); +} + +/*********************************************************************************************/ + +typedef enum +{ + SET_ADDRESS, + READ_VOLTAGE, + READ_CURRENT, + READ_POWER, + READ_ENERGY, +} PZEMReadStates; + +PZEMReadStates pzem_read_state = SET_ADDRESS; + +byte pzem_sendRetry = 0; + +void PzemEvery200ms() +{ + bool dataReady = PZEM004T_isReady(); + + if (dataReady) { + float pzem_value; + switch (pzem_read_state) { + case SET_ADDRESS: + if (PZEM004T_setAddress_rcv()) { + pzem_read_state = READ_VOLTAGE; + } + break; + case READ_VOLTAGE: + pzem_value = PZEM004T_voltage_rcv(); + if (pzem_value != PZEM_ERROR_VALUE) { + energy_voltage = pzem_value; // 230.2V + pzem_read_state = READ_CURRENT; + } + break; + case READ_CURRENT: + pzem_value = PZEM004T_current_rcv(); + if (pzem_value != PZEM_ERROR_VALUE) { + energy_current = pzem_value; // 17.32A + pzem_read_state = READ_POWER; + } + break; + case READ_POWER: + pzem_value = PZEM004T_power_rcv(); + if (pzem_value != PZEM_ERROR_VALUE) { + energy_power = pzem_value; // 20W + energy_power_factor_ready = true; + pzem_read_state = READ_ENERGY; + } + break; + case READ_ENERGY: + pzem_value = PZEM004T_energy_rcv(); + if (pzem_value != PZEM_ERROR_VALUE) { + energy_total = pzem_value / 1000; // 99999Wh + if (!energy_startup) { + if (energy_total < energy_start) { + energy_start = energy_total; + Settings.hlw_power_calibration = energy_start * 1000; + } + energy_kWhtoday = (energy_total - energy_start) * 100000000; + energy_daily = (float)energy_kWhtoday / 100000000; + } + pzem_read_state = READ_VOLTAGE; + } + break; + } + } + + if (0 == pzem_sendRetry || dataReady) { + pzem_sendRetry = 5; + + switch (pzem_read_state) { + case SET_ADDRESS: + PZEM004T_send(PZEM_SET_ADDRESS); + break; + case READ_VOLTAGE: + PZEM004T_send(PZEM_VOLTAGE); + break; + case READ_CURRENT: + PZEM004T_send(PZEM_CURRENT); + break; + case READ_POWER: + PZEM004T_send(PZEM_POWER); + break; + case READ_ENERGY: + PZEM004T_send(PZEM_ENERGY); + break; + } + } + else { + pzem_sendRetry--; + } +} + +bool PzemInit() +{ + return PzemSerial(pin[GPIO_PZEM_RX], pin[GPIO_PZEM_TX]); +} + +/********************************************************************************************/ +#endif // USE_PZEM004T + +void Energy200ms() +{ + energy_fifth_second++; + if (5 == energy_fifth_second) { + energy_fifth_second = 0; + + if (ENERGY_HLW8012 == energy_flg) { + HlwEverySecond(); + } + + if (RtcTime.valid) { + if (LocalTime() == Midnight()) { + Settings.energy_kWhyesterday = energy_kWhtoday; + Settings.energy_kWhtotal += (energy_kWhtoday / 1000); + RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal; + energy_kWhtoday = 0; + RtcSettings.energy_kWhtoday = energy_kWhtoday; +#ifdef USE_PZEM004T + if (ENERGY_PZEM004T == energy_flg) { + energy_start = energy_total; + Settings.hlw_power_calibration = energy_start * 1000; + } +#endif // USE_PZEM004T + energy_max_energy_state = 3; + } + if ((RtcTime.hour == Settings.energy_max_energy_start) && (3 == energy_max_energy_state)) { + energy_max_energy_state = 0; + } + if (energy_startup && (RtcTime.day_of_year == Settings.energy_kWhdoy)) { + energy_kWhtoday = Settings.energy_kWhtoday; + RtcSettings.energy_kWhtoday = energy_kWhtoday; + energy_start = (float)Settings.hlw_power_calibration / 1000; // Used by PZEM004T to store total yesterday + energy_startup = 0; + } + } + } + + if (ENERGY_HLW8012 == energy_flg) { + HlwEvery200ms(); +#ifdef USE_PZEM004T + } + else if (ENERGY_PZEM004T == energy_flg) { + PzemEvery200ms(); +#endif // USE_PZEM004T + } + + if (energy_power_factor_ready && energy_voltage && energy_current && energy_power) { + energy_power_factor_ready = false; + float power_factor = energy_power / (energy_voltage * energy_current); + if (power_factor > 1) { + power_factor = 1; + } + energy_power_factor = power_factor; + } +} + +void EnergySaveState() +{ + Settings.energy_kWhdoy = (RtcTime.valid) ? RtcTime.day_of_year : 0; + Settings.energy_kWhtoday = energy_kWhtoday; + Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; +} + +boolean EnergyMargin(byte type, uint16_t margin, uint16_t value, byte &flag, byte &save_flag) +{ + byte change; + + if (!margin) { + return false; + } + change = save_flag; + if (type) { + flag = (value > margin); + } else { + flag = (value < margin); + } + save_flag = flag; + return (change != save_flag); +} + +void EnergySetPowerSteadyCounter(byte value) +{ + energy_power_steady_cntr = 2; +} + +void EnergyMarginCheck() +{ + uint16_t energy_daily_u; + uint16_t energy_power_u; + uint16_t energy_voltage_u; + uint16_t energy_current_u; + boolean flag; + boolean jsonflg; + + if (energy_power_steady_cntr) { + energy_power_steady_cntr--; + return; + } + + if (power && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) { + energy_power_u = (uint16_t)(energy_power); + energy_voltage_u = (uint16_t)(energy_voltage); + energy_current_u = (uint16_t)(energy_current * 1000); + +// snprintf_P(log_data, sizeof(log_data), PSTR("HLW: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u); +// AddLog(LOG_LEVEL_DEBUG); + + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{")); + jsonflg = 0; + if (EnergyMargin(0, Settings.energy_min_power, energy_power_u, flag, energy_min_power_flag)) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_POWERLOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); + jsonflg = 1; + } + if (EnergyMargin(1, Settings.energy_max_power, energy_power_u, flag, energy_max_power_flag)) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_POWERHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); + jsonflg = 1; + } + if (EnergyMargin(0, Settings.energy_min_voltage, energy_voltage_u, flag, energy_min_voltage_flag)) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_VOLTAGELOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); + jsonflg = 1; + } + if (EnergyMargin(1, Settings.energy_max_voltage, energy_voltage_u, flag, energy_max_voltage_flag)) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_VOLTAGEHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); + jsonflg = 1; + } + if (EnergyMargin(0, Settings.energy_min_current, energy_current_u, flag, energy_min_current_flag)) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_CURRENTLOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); + jsonflg = 1; + } + if (EnergyMargin(1, Settings.energy_max_current, energy_current_u, flag, energy_max_current_flag)) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_CURRENTHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); + jsonflg = 1; + } + if (jsonflg) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); + MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_MARGINS)); + EnergyMqttShow(); + } + } + +#if FEATURE_POWER_LIMIT + // Max Power + if (Settings.energy_max_power_limit) { + if (energy_power > Settings.energy_max_power_limit) { + if (!energy_mplh_counter) { + energy_mplh_counter = Settings.energy_max_power_limit_hold; + } else { + energy_mplh_counter--; + if (!energy_mplh_counter) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_MAXPOWERREACHED "\":\"%d%s\"}"), energy_power_u, (Settings.flag.value_units) ? " " D_UNIT_WATT : ""); + MqttPublishPrefixTopic_P(1, S_RSLT_WARNING); + EnergyMqttShow(); + ExecuteCommandPower(1, 0); + if (!energy_mplr_counter) { + energy_mplr_counter = Settings.param[P_MAX_POWER_RETRY] +1; + } + energy_mplw_counter = Settings.energy_max_power_limit_window; + } + } + } + else if (power && (energy_power_u <= Settings.energy_max_power_limit)) { + energy_mplh_counter = 0; + energy_mplr_counter = 0; + energy_mplw_counter = 0; + } + if (!power) { + if (energy_mplw_counter) { + energy_mplw_counter--; + } else { + if (energy_mplr_counter) { + energy_mplr_counter--; + if (energy_mplr_counter) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_POWERMONITOR "\":\"%s\"}"), GetStateText(1)); + MqttPublishPrefixTopic_P(5, PSTR(D_POWERMONITOR)); + ExecuteCommandPower(1, 1); + } else { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0)); + MqttPublishPrefixTopic_P(1, S_RSLT_WARNING); + EnergyMqttShow(); + } + } + } + } + } + + // Max Energy + if (Settings.energy_max_energy) { + energy_daily_u = (uint16_t)(energy_daily * 1000); + if (!energy_max_energy_state && (RtcTime.hour == Settings.energy_max_energy_start)) { + energy_max_energy_state = 1; + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1)); + MqttPublishPrefixTopic_P(5, PSTR(D_ENERGYMONITOR)); + ExecuteCommandPower(1, 1); + } + else if ((1 == energy_max_energy_state) && (energy_daily_u >= Settings.energy_max_energy)) { + energy_max_energy_state = 2; + dtostrfd(energy_daily, 3, mqtt_data); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_MAXENERGYREACHED "\":\"%s%s\"}"), mqtt_data, (Settings.flag.value_units) ? " " D_UNIT_KILOWATTHOUR : ""); + MqttPublishPrefixTopic_P(1, S_RSLT_WARNING); + EnergyMqttShow(); + ExecuteCommandPower(1, 0); + } + } +#endif // FEATURE_POWER_LIMIT +} + +void EnergyMqttShow() +{ +// {"Time":"2017-12-16T11:48:55","ENERGY":{"Total":0.212,"Yesterday":0.000,"Today":0.014,"Period":2.0,"Power":22.0,"Factor":1.00,"Voltage":213.6,"Current":0.100}} + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_TIME "\":\"%s\""), GetDateAndTime().c_str()); + EnergyShow(1); + MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_ENERGY), Settings.flag.mqtt_sensor_retain); +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +boolean EnergyCommand(char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload) +{ + char command [CMDSZ]; + char sunit[CMDSZ]; + boolean serviced = true; + uint8_t status_flag = 0; + uint8_t unit = 0; + unsigned long nvalue = 0; + + int command_code = GetCommandCode(command, sizeof(command), type, kEnergyCommands); + if (CMND_POWERLOW == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_min_power = payload; + } + nvalue = Settings.energy_min_power; + unit = UNIT_WATT; + } + else if (CMND_POWERHIGH == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_max_power = payload; + } + nvalue = Settings.energy_max_power; + unit = UNIT_WATT; + } + else if (CMND_VOLTAGELOW == command_code) { + if ((payload >= 0) && (payload < 501)) { + Settings.energy_min_voltage = payload; + } + nvalue = Settings.energy_min_voltage; + unit = UNIT_VOLT; + } + else if (CMND_VOLTAGEHIGH == command_code) { + if ((payload >= 0) && (payload < 501)) { + Settings.energy_max_voltage = payload; + } + nvalue = Settings.energy_max_voltage; + unit = UNIT_VOLT; + } + else if (CMND_CURRENTLOW == command_code) { + if ((payload >= 0) && (payload < 16001)) { + Settings.energy_min_current = payload; + } + nvalue = Settings.energy_min_current; + unit = UNIT_MILLIAMPERE; + } + else if (CMND_CURRENTHIGH == command_code) { + if ((payload >= 0) && (payload < 16001)) { + Settings.energy_max_current = payload; + } + nvalue = Settings.energy_max_current; + unit = UNIT_MILLIAMPERE; + } + else if ((CMND_ENERGYRESET == command_code) && (index > 0) && (index <= 3)) { + char *p; + unsigned long lnum = strtoul(dataBuf, &p, 10); + if (p != dataBuf) { + switch (index) { + case 1: + energy_kWhtoday = lnum *100000; + RtcSettings.energy_kWhtoday = energy_kWhtoday; + Settings.energy_kWhtoday = energy_kWhtoday; + break; + case 2: + Settings.energy_kWhyesterday = lnum *100000; + break; + case 3: + RtcSettings.energy_kWhtotal = lnum *100; + Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; + break; + } + } + char energy_yesterday_chr[10]; + char stoday_energy[10]; + char energy_total_chr[10]; + dtostrfd((float)Settings.energy_kWhyesterday / 100000000, Settings.flag2.energy_resolution, energy_yesterday_chr); + dtostrfd((float)RtcSettings.energy_kWhtoday / 100000000, Settings.flag2.energy_resolution, stoday_energy); + dtostrfd((float)(RtcSettings.energy_kWhtotal + (energy_kWhtoday / 1000)) / 100000, Settings.flag2.energy_resolution, energy_total_chr); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"" D_TOTAL "\":%s,\"" D_YESTERDAY "\":%s,\"" D_TODAY "\":%s}}"), + command, energy_total_chr, energy_yesterday_chr, stoday_energy); + status_flag = 1; + } + else if ((ENERGY_HLW8012 == energy_flg) && (CMND_HLWPCAL == command_code)) { + if ((payload > 0) && (payload < 32001)) { + Settings.hlw_power_calibration = (payload > 4000) ? payload : HLW_PREF_PULSE; // 12530 + } + nvalue = Settings.hlw_power_calibration; + unit = UNIT_MICROSECOND; + } + else if ((ENERGY_HLW8012 == energy_flg) && (CMND_HLWPSET == command_code)) { + if ((payload > 0) && (payload < 3601) && hlw_cf_pulse_length) { + Settings.hlw_power_calibration = (payload * 10 * hlw_cf_pulse_length) / HLW_PREF; + } + snprintf_P(command, sizeof(command), PSTR(D_CMND_HLWPCAL)); + nvalue = Settings.hlw_power_calibration; + unit = UNIT_MICROSECOND; + } + else if ((ENERGY_HLW8012 == energy_flg) && (CMND_HLWUCAL == command_code)) { + if ((payload > 0) && (payload < 32001)) { + Settings.hlw_voltage_calibration = (payload > 999) ? payload : HLW_UREF_PULSE; // 1950 + } + nvalue = Settings.hlw_voltage_calibration; + unit = UNIT_MICROSECOND; + } + else if ((ENERGY_HLW8012 == energy_flg) && (CMND_HLWUSET == command_code)) { + if ((payload > 0) && (payload < 501) && hlw_cf1_voltage_pulse_length) { + Settings.hlw_voltage_calibration = (payload * 10 * hlw_cf1_voltage_pulse_length) / HLW_UREF; + } + snprintf_P(command, sizeof(command), PSTR(D_CMND_HLWUCAL)); + nvalue = Settings.hlw_voltage_calibration; + unit = UNIT_MICROSECOND; + } + else if ((ENERGY_HLW8012 == energy_flg) && (CMND_HLWICAL == command_code)) { + if ((payload > 0) && (payload < 32001)) { + Settings.hlw_current_calibration = (payload > 1100) ? payload : HLW_IREF_PULSE; // 3500 + } + nvalue = Settings.hlw_current_calibration; + unit = UNIT_MICROSECOND; + } + else if ((ENERGY_HLW8012 == energy_flg) && (CMND_HLWISET == command_code)) { + if ((payload > 0) && (payload < 16001) && hlw_cf1_current_pulse_length) { + Settings.hlw_current_calibration = (payload * hlw_cf1_current_pulse_length) / HLW_IREF; + } + snprintf_P(command, sizeof(command), PSTR(D_CMND_HLWICAL)); + nvalue = Settings.hlw_current_calibration; + unit = UNIT_MICROSECOND; + } +#if FEATURE_POWER_LIMIT + else if (CMND_MAXPOWER == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_max_power_limit = payload; + } + nvalue = Settings.energy_max_power_limit; + unit = UNIT_WATT; + } + else if (CMND_MAXPOWERHOLD == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_max_power_limit_hold = (1 == payload) ? MAX_POWER_HOLD : payload; + } + nvalue = Settings.energy_max_power_limit_hold; + unit = UNIT_SECOND; + } + else if (CMND_MAXPOWERWINDOW == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_max_power_limit_window = (1 == payload) ? MAX_POWER_WINDOW : payload; + } + nvalue = Settings.energy_max_power_limit_window; + unit = UNIT_SECOND; + } + else if (CMND_SAFEPOWER == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_max_power_safe_limit = payload; + } + nvalue = Settings.energy_max_power_safe_limit; + unit = UNIT_WATT; + } + else if (CMND_SAFEPOWERHOLD == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_max_power_safe_limit_hold = (1 == payload) ? SAFE_POWER_HOLD : payload; + } + nvalue = Settings.energy_max_power_safe_limit_hold; + unit = UNIT_SECOND; + } + else if (CMND_SAFEPOWERWINDOW == command_code) { + if ((payload >= 0) && (payload < 1440)) { + Settings.energy_max_power_safe_limit_window = (1 == payload) ? SAFE_POWER_WINDOW : payload; + } + nvalue = Settings.energy_max_power_safe_limit_window; + unit = UNIT_MINUTE; + } + else if (CMND_MAXENERGY == command_code) { + if ((payload >= 0) && (payload < 3601)) { + Settings.energy_max_energy = payload; + energy_max_energy_state = 3; + } + nvalue = Settings.energy_max_energy; + unit = UNIT_WATTHOUR; + } + else if (CMND_MAXENERGYSTART == command_code) { + if ((payload >= 0) && (payload < 24)) { + Settings.energy_max_energy_start = payload; + } + nvalue = Settings.energy_max_energy_start; + unit = UNIT_HOUR; + } +#endif // FEATURE_POWER_LIMIT + else { + serviced = false; + } + if (!status_flag) { + if (Settings.flag.value_units) { + snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_SPACE_UNIT, command, nvalue, GetTextIndexed(sunit, sizeof(sunit), unit, kUnitNames)); + } else { + snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, nvalue); + } + } + return serviced; +} + +/********************************************************************************************/ + +void EnergyInit() +{ + energy_flg = ENERGY_NONE; + if ((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)) { + energy_flg = ENERGY_HLW8012; + HlwInit(); +#ifdef USE_PZEM004T + } else if ((pin[GPIO_PZEM_RX] < 99) && (pin[GPIO_PZEM_TX])) { + if (PzemInit()) { + energy_flg = ENERGY_PZEM004T; + } +#endif // USE_PZEM004T + } + + if (energy_flg) { + energy_kWhtoday = (RtcSettingsValid()) ? RtcSettings.energy_kWhtoday : 0; + + energy_startup = 1; + ticker_energy.attach_ms(200, Energy200ms); + } +} + +#ifdef USE_WEBSERVER +const char HTTP_ENERGY_SNS[] PROGMEM = + "{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" + "{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" + "{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}" + "{s}" D_POWER_FACTOR "{m}%s{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 + +void EnergyShow(boolean json) +{ + char energy_total_chr[10]; + char energy_daily_chr[10]; + char energy_period_chr[10]; + char energy_power_chr[10]; + char energy_voltage_chr[10]; + char energy_current_chr[10]; + char energy_power_factor_chr[10]; + char energy_yesterday_chr[10]; + char speriod[20]; + + bool show_energy_period = (0 == tele_period); + + float energy = 0; + if (show_energy_period) { + if (energy_period) { + energy = (float)(energy_kWhtoday - energy_period) / 100000; + } + energy_period = energy_kWhtoday; + } + + dtostrfd(energy_total, Settings.flag2.energy_resolution, energy_total_chr); + dtostrfd(energy_daily, Settings.flag2.energy_resolution, energy_daily_chr); + dtostrfd(energy, Settings.flag2.wattage_resolution, energy_period_chr); + dtostrfd(energy_power, Settings.flag2.wattage_resolution, energy_power_chr); + dtostrfd(energy_voltage, Settings.flag2.voltage_resolution, energy_voltage_chr); + dtostrfd(energy_current, Settings.flag2.current_resolution, energy_current_chr); + dtostrfd(energy_power_factor, 2, energy_power_factor_chr); + dtostrfd((float)Settings.energy_kWhyesterday / 100000000, Settings.flag2.energy_resolution, energy_yesterday_chr); + + if (json) { + snprintf_P(speriod, sizeof(speriod), PSTR(",\"" D_PERIOD "\":%s"), energy_period_chr); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_RSLT_ENERGY "\":{\"" D_TOTAL "\":%s,\"" D_YESTERDAY "\":%s,\"" D_TODAY "\":%s%s,\"" D_POWERUSAGE "\":%s,\"" D_POWERFACTOR "\":%s,\"" D_VOLTAGE "\":%s,\"" D_CURRENT "\":%s}"), + mqtt_data, energy_total_chr, energy_yesterday_chr, energy_daily_chr, (show_energy_period) ? speriod : "", energy_power_chr, energy_power_factor_chr, energy_voltage_chr, energy_current_chr); +#ifdef USE_DOMOTICZ + if (show_energy_period) { // Only send if telemetry + dtostrfd(energy_total * 1000, 1, energy_total_chr); + DomoticzSensorPowerEnergy((uint16_t)energy_power, energy_total_chr); // PowerUsage, EnergyToday + DomoticzSensor(DZ_VOLTAGE, energy_voltage_chr); // Voltage + DomoticzSensor(DZ_CURRENT, energy_current_chr); // Current + } +#endif // USE_DOMOTICZ +#ifdef USE_WEBSERVER + } else { + snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS, energy_voltage_chr, energy_current_chr, energy_power_chr, energy_power_factor_chr, energy_daily_chr, energy_yesterday_chr, energy_total_chr); +#endif // USE_WEBSERVER + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +#define XSNS_03 + +boolean Xsns03(byte function) +{ + boolean result = false; + + if (energy_flg) { + switch (function) { + case FUNC_XSNS_INIT: + EnergyInit(); + break; + case FUNC_XSNS_EVERY_SECOND: + EnergyMarginCheck(); + break; +// case FUNC_XSNS_PREP: +// break; + case FUNC_XSNS_JSON_APPEND: + EnergyShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_XSNS_WEB: + EnergyShow(0); + break; +#endif // USE_WEBSERVER + case FUNC_XSNS_SAVE_STATE: + EnergySaveState(); + break; + } + } + return result; +} + +#endif // USE_ENERGY_SENSOR \ No newline at end of file diff --git a/sonoff/xsns_03_hlw8012.ino b/sonoff/xsns_03_hlw8012.ino deleted file mode 100644 index 6b075e9dd..000000000 --- a/sonoff/xsns_03_hlw8012.ino +++ /dev/null @@ -1,767 +0,0 @@ -/* - xsns_03_hlw8012.ino - sonoff pow HLW8012 energy sensor support for Sonoff-Tasmota - - Copyright (C) 2017 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 . -*/ - -/*********************************************************************************************\ - * HLW8012 - Energy - * - * Based on Source: Shenzhen Heli Technology Co., Ltd -\*********************************************************************************************/ - -#define FEATURE_POWER_LIMIT true - -/*********************************************************************************************/ - -#define HLW_PREF 10000 // 1000.0W -#define HLW_UREF 2200 // 220.0V -#define HLW_IREF 4545 // 4.545A - -#define HLW_POWER_PROBE_TIME 10 // Number of seconds to probe for power before deciding none used - -enum Hlw8012Commands { - CMND_POWERLOW, CMND_POWERHIGH, CMND_VOLTAGELOW, CMND_VOLTAGEHIGH, CMND_CURRENTLOW, CMND_CURRENTHIGH, - CMND_HLWPCAL, CMND_HLWPSET, CMND_HLWUCAL, CMND_HLWUSET, CMND_HLWICAL, CMND_HLWISET, - CMND_ENERGYRESET, CMND_MAXENERGY, CMND_MAXENERGYSTART, - CMND_MAXPOWER, CMND_MAXPOWERHOLD, CMND_MAXPOWERWINDOW, - CMND_SAFEPOWER, CMND_SAFEPOWERHOLD, CMND_SAFEPOWERWINDOW }; -const char kHlw8012Commands[] PROGMEM = - D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|" - D_CMND_HLWPCAL "|" D_CMND_HLWPSET "|" D_CMND_HLWUCAL "|" D_CMND_HLWUSET "|" D_CMND_HLWICAL "|" D_CMND_HLWISET "|" - D_CMND_ENERGYRESET "|" D_CMND_MAXENERGY "|" D_CMND_MAXENERGYSTART "|" - D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|" - D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW ; - -byte hlw_pmin_flag = 0; -byte hlw_pmax_flag = 0; -byte hlw_umin_flag = 0; -byte hlw_umax_flag = 0; -byte hlw_imin_flag = 0; -byte hlw_imax_flag = 0; - -byte power_steady_cntr; -byte hlw_mkwh_state = 0; - -#if FEATURE_POWER_LIMIT - byte hlw_mplr_counter = 0; - uint16_t hlw_mplh_counter = 0; - uint16_t hlw_mplw_counter = 0; -#endif // FEATURE_POWER_LIMIT - -byte hlw_select_ui_flag; -byte hlw_load_off; -byte hlw_cf1_timer; -byte hlw_fifth_second; -byte hlw_startup; -unsigned long hlw_cf_pulse_length; -unsigned long hlw_cf_pulse_last_time; -unsigned long hlw_cf1_pulse_length; -unsigned long hlw_cf1_pulse_last_time; -unsigned long hlw_cf1_summed_pulse_length; -unsigned long hlw_cf1_pulse_counter; -unsigned long hlw_cf1_voltage_pulse_length; -unsigned long hlw_cf1_current_pulse_length; -unsigned long hlw_energy_counter; -unsigned long hlw_energy_period_counter; -unsigned long hlw_kWhtoday; -uint32_t hlw_lasttime; - -unsigned long hlw_cf1_voltage_max_pulse_counter; -unsigned long hlw_cf1_current_max_pulse_counter; - -Ticker tickerHLW; - -#ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves exception -void HlwCfInterrupt() ICACHE_RAM_ATTR; -void HlwCf1Interrupt() ICACHE_RAM_ATTR; -#endif // USE_WS2812_DMA - -void HlwCfInterrupt() // Service Power -{ - unsigned long us = micros(); - - if (hlw_load_off) { // Restart plen measurement - hlw_cf_pulse_last_time = us; - hlw_load_off = 0; - } else { - hlw_cf_pulse_length = us - hlw_cf_pulse_last_time; - hlw_cf_pulse_last_time = us; - hlw_energy_period_counter++; - hlw_energy_counter++; - } -} - -void HlwCf1Interrupt() // Service Voltage and Current -{ - unsigned long us = micros(); - - hlw_cf1_pulse_length = us - hlw_cf1_pulse_last_time; - hlw_cf1_pulse_last_time = us; - if ((hlw_cf1_timer > 2) && (hlw_cf1_timer < 8)) { // Allow for 300 mSec set-up time and measure for up to 1 second - hlw_cf1_summed_pulse_length += hlw_cf1_pulse_length; - hlw_cf1_pulse_counter++; - if (10 == hlw_cf1_pulse_counter) { - hlw_cf1_timer = 8; // We need up to ten samples within 1 second (low current could take up to 0.3 second) - } - } -} - -void hlw_200mS() -{ - unsigned long hlw_len; - unsigned long hlw_temp; - - hlw_fifth_second++; - if (5 == hlw_fifth_second) { - hlw_fifth_second = 0; - - if (hlw_energy_period_counter) { - hlw_len = 10000 / hlw_energy_period_counter; - hlw_energy_period_counter = 0; - if (hlw_len) { - hlw_temp = ((HLW_PREF * Settings.hlw_power_calibration) / hlw_len) / 36; - hlw_kWhtoday += hlw_temp; - RtcSettings.hlw_kWhtoday = hlw_kWhtoday; - } - } - if (RtcTime.valid) { - if (LocalTime() == Midnight()) { - Settings.hlw_kWhyesterday = hlw_kWhtoday; - Settings.hlw_kWhtotal += (hlw_kWhtoday / 1000); - RtcSettings.hlw_kWhtotal = Settings.hlw_kWhtotal; - hlw_kWhtoday = 0; - RtcSettings.hlw_kWhtoday = hlw_kWhtoday; - hlw_mkwh_state = 3; - } - if ((RtcTime.hour == Settings.hlw_mkwhs) && (3 == hlw_mkwh_state)) { - hlw_mkwh_state = 0; - } - if (hlw_startup && (RtcTime.day_of_year == Settings.hlw_kWhdoy)) { - hlw_kWhtoday = Settings.hlw_kWhtoday; - RtcSettings.hlw_kWhtoday = hlw_kWhtoday; - hlw_startup = 0; - } - } - } - - if (micros() - hlw_cf_pulse_last_time > (HLW_POWER_PROBE_TIME * 1000000)) { - hlw_cf_pulse_length = 0; // No load for some time - hlw_load_off = 1; - } - - hlw_cf1_timer++; - if (hlw_cf1_timer >= 8) { - hlw_cf1_timer = 0; - hlw_select_ui_flag = (hlw_select_ui_flag) ? 0 : 1; - digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); - - if (hlw_cf1_pulse_counter) { - hlw_cf1_pulse_length = hlw_cf1_summed_pulse_length / hlw_cf1_pulse_counter; - } else { - hlw_cf1_pulse_length = 0; - } - if (hlw_select_ui_flag) { - hlw_cf1_voltage_pulse_length = hlw_cf1_pulse_length; - hlw_cf1_voltage_max_pulse_counter = hlw_cf1_pulse_counter; - } else { - hlw_cf1_current_pulse_length = hlw_cf1_pulse_length; - hlw_cf1_current_max_pulse_counter = hlw_cf1_pulse_counter; - } - hlw_cf1_summed_pulse_length = 0; - hlw_cf1_pulse_counter = 0; - } -} - -void HlwSaveState() -{ - Settings.hlw_kWhdoy = (RtcTime.valid) ? RtcTime.day_of_year : 0; - Settings.hlw_kWhtoday = hlw_kWhtoday; - Settings.hlw_kWhtotal = RtcSettings.hlw_kWhtotal; -} - -void HlwReadEnergy(byte option, float &total_energy, float &daily_energy, float &energy, float &watts, float &voltage, float ¤t, float &power_factor) -{ -/* option 0 = do not calculate period energy usage - * option 1 = calculate period energy usage - */ - unsigned long cur_kWhtoday = hlw_kWhtoday; - unsigned long hlw_len; - unsigned long hlw_temp; - unsigned long hlw_w; - unsigned long hlw_u; - unsigned long hlw_i; - uint16_t hlw_period; - -//snprintf_P(log_data, sizeof(log_data), PSTR("HLW: CF %d, CF1U %d (%d), CF1I %d (%d)"), hlw_cf_pulse_length, hlw_cf1_voltage_pulse_length, hlw_cf1_voltage_max_pulse_counter, hlw_cf1_current_pulse_length, hlw_cf1_current_max_pulse_counter); -//AddLog(LOG_LEVEL_DEBUG); - - total_energy = (float)(RtcSettings.hlw_kWhtotal + (cur_kWhtoday / 1000)) / 100000; - daily_energy = 0; - if (cur_kWhtoday) { - daily_energy = (float)cur_kWhtoday / 100000000; - } - energy = 0; - if (option) { - if (!hlw_lasttime) { - hlw_period = Settings.tele_period; - } else { - hlw_period = LocalTime() - hlw_lasttime; - } - hlw_lasttime = LocalTime(); - if (hlw_period) { - uint16_t hlw_interval = 3600 / hlw_period; - if (hlw_energy_counter) { - hlw_len = hlw_period * 1000000 / hlw_energy_counter; - if (hlw_interval && hlw_len) { - hlw_energy_counter = 0; - hlw_temp = ((HLW_PREF * Settings.hlw_power_calibration) / hlw_len) / hlw_interval; - energy = (float)hlw_temp / 10; - } - } - } - } - watts = 0; - if (hlw_cf_pulse_length && (power &1) && !hlw_load_off) { - hlw_w = (HLW_PREF * Settings.hlw_power_calibration) / hlw_cf_pulse_length; - watts = (float)hlw_w / 10; - } - voltage = 0; - if (hlw_cf1_voltage_pulse_length && (power &1)) { // If powered on always provide voltage - hlw_u = (HLW_UREF * Settings.hlw_voltage_calibration) / hlw_cf1_voltage_pulse_length; - voltage = (float)hlw_u / 10; - } - current = 0; - if (hlw_cf1_current_pulse_length && watts) { // No current if no power being consumed - hlw_i = (HLW_IREF * Settings.hlw_current_calibration) / hlw_cf1_current_pulse_length; - current = (float)hlw_i / 1000; - } - power_factor = 0; - if (hlw_i && hlw_u && hlw_w && watts) { - hlw_temp = (hlw_w * 100) / ((hlw_u * hlw_i) / 1000); - if (hlw_temp > 100) { - hlw_temp = 100; - } - power_factor = (float)hlw_temp / 100; - } -} - -void HlwInit() -{ - if (!Settings.hlw_power_calibration || (4975 == Settings.hlw_power_calibration)) { - Settings.hlw_power_calibration = HLW_PREF_PULSE; - Settings.hlw_voltage_calibration = HLW_UREF_PULSE; - Settings.hlw_current_calibration = HLW_IREF_PULSE; - } - - hlw_cf_pulse_length = 0; - hlw_cf_pulse_last_time = 0; - hlw_cf1_pulse_length = 0; - hlw_cf1_pulse_last_time = 0; - hlw_cf1_voltage_pulse_length = 0; - hlw_cf1_current_pulse_length = 0; - hlw_cf1_voltage_max_pulse_counter = 0; - hlw_cf1_current_max_pulse_counter = 0; - - hlw_load_off = 1; - hlw_energy_counter = 0; - hlw_energy_period_counter = 0; - hlw_kWhtoday = (RtcSettingsValid()) ? RtcSettings.hlw_kWhtoday : 0; - - hlw_select_ui_flag = 0; // Voltage; - - pinMode(pin[GPIO_HLW_SEL], OUTPUT); - digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); - pinMode(pin[GPIO_HLW_CF1], INPUT_PULLUP); - attachInterrupt(pin[GPIO_HLW_CF1], HlwCf1Interrupt, FALLING); - pinMode(pin[GPIO_HLW_CF], INPUT_PULLUP); - attachInterrupt(pin[GPIO_HLW_CF], HlwCfInterrupt, FALLING); - - hlw_startup = 1; - hlw_lasttime = 0; - hlw_fifth_second = 0; - hlw_cf1_timer = 0; - tickerHLW.attach_ms(200, hlw_200mS); -} - -/********************************************************************************************/ - -boolean HlwMargin(byte type, uint16_t margin, uint16_t value, byte &flag, byte &save_flag) -{ - byte change; - - if (!margin) { - return false; - } - change = save_flag; - if (type) { - flag = (value > margin); - } else { - flag = (value < margin); - } - save_flag = flag; - return (change != save_flag); -} - -void HlwSetPowerSteadyCounter(byte value) -{ - power_steady_cntr = 2; -} - -void HlwMarginCheck() -{ - float total_energy; - float daily_energy; - float energy; - float watts; - float voltage; - float current; - float power_factor; - uint16_t udaily_energy; - uint16_t uwatts; - uint16_t uvoltage; - uint16_t ucurrent; - boolean flag; - boolean jsonflg; - - if (power_steady_cntr) { - power_steady_cntr--; - return; - } - - HlwReadEnergy(0, total_energy, daily_energy, energy, watts, voltage, current, power_factor); - if (power && (Settings.hlw_pmin || Settings.hlw_pmax || Settings.hlw_umin || Settings.hlw_umax || Settings.hlw_imin || Settings.hlw_imax)) { - uwatts = (uint16_t)(watts); - uvoltage = (uint16_t)(voltage); - ucurrent = (uint16_t)(current * 1000); - -// snprintf_P(log_data, sizeof(log_data), PSTR("HLW: W %d, U %d, I %d"), watts, voltage, ucurrent); -// AddLog(LOG_LEVEL_DEBUG); - - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{")); - jsonflg = 0; - if (HlwMargin(0, Settings.hlw_pmin, uwatts, flag, hlw_pmin_flag)) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_POWERLOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); - jsonflg = 1; - } - if (HlwMargin(1, Settings.hlw_pmax, uwatts, flag, hlw_pmax_flag)) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_POWERHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); - jsonflg = 1; - } - if (HlwMargin(0, Settings.hlw_umin, uvoltage, flag, hlw_umin_flag)) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_VOLTAGELOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); - jsonflg = 1; - } - if (HlwMargin(1, Settings.hlw_umax, uvoltage, flag, hlw_umax_flag)) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_VOLTAGEHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); - jsonflg = 1; - } - if (HlwMargin(0, Settings.hlw_imin, ucurrent, flag, hlw_imin_flag)) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_CURRENTLOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); - jsonflg = 1; - } - if (HlwMargin(1, Settings.hlw_imax, ucurrent, flag, hlw_imax_flag)) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_CURRENTHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag)); - jsonflg = 1; - } - if (jsonflg) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); - MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_MARGINS)); - MqttShowHlw8012(0); - } - } - -#if FEATURE_POWER_LIMIT - // Max Power - if (Settings.hlw_mpl) { - if (watts > Settings.hlw_mpl) { - if (!hlw_mplh_counter) { - hlw_mplh_counter = Settings.hlw_mplh; - } else { - hlw_mplh_counter--; - if (!hlw_mplh_counter) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_MAXPOWERREACHED "\":\"%d%s\"}"), uwatts, (Settings.flag.value_units) ? " " D_UNIT_WATT : ""); - MqttPublishPrefixTopic_P(1, S_RSLT_WARNING); - MqttShowHlw8012(0); - ExecuteCommandPower(1, 0); - if (!hlw_mplr_counter) { - hlw_mplr_counter = Settings.param[P_MAX_POWER_RETRY] +1; - } - hlw_mplw_counter = Settings.hlw_mplw; - } - } - } - else if (power && (uwatts <= Settings.hlw_mpl)) { - hlw_mplh_counter = 0; - hlw_mplr_counter = 0; - hlw_mplw_counter = 0; - } - if (!power) { - if (hlw_mplw_counter) { - hlw_mplw_counter--; - } else { - if (hlw_mplr_counter) { - hlw_mplr_counter--; - if (hlw_mplr_counter) { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_POWERMONITOR "\":\"%s\"}"), GetStateText(1)); - MqttPublishPrefixTopic_P(5, PSTR(D_POWERMONITOR)); - ExecuteCommandPower(1, 1); - } else { - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0)); - MqttPublishPrefixTopic_P(1, S_RSLT_WARNING); - MqttShowHlw8012(0); - } - } - } - } - } - - // Max Energy - if (Settings.hlw_mkwh) { - udaily_energy = (uint16_t)(daily_energy * 1000); - if (!hlw_mkwh_state && (RtcTime.hour == Settings.hlw_mkwhs)) { - hlw_mkwh_state = 1; - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1)); - MqttPublishPrefixTopic_P(5, PSTR(D_ENERGYMONITOR)); - ExecuteCommandPower(1, 1); - } - else if ((1 == hlw_mkwh_state) && (udaily_energy >= Settings.hlw_mkwh)) { - hlw_mkwh_state = 2; - dtostrfd(daily_energy, 3, mqtt_data); - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_MAXENERGYREACHED "\":\"%s%s\"}"), mqtt_data, (Settings.flag.value_units) ? " " D_UNIT_KILOWATTHOUR : ""); - MqttPublishPrefixTopic_P(1, S_RSLT_WARNING); - MqttShowHlw8012(0); - ExecuteCommandPower(1, 0); - } - } -#endif // FEATURE_POWER_LIMIT -} - -/*********************************************************************************************\ - * Commands -\*********************************************************************************************/ - -boolean HlwCommand(char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload) -{ - char command [CMDSZ]; - char sunit[CMDSZ]; - boolean serviced = true; - uint8_t status_flag = 0; - uint8_t unit = 0; - unsigned long nvalue = 0; - - int command_code = GetCommandCode(command, sizeof(command), type, kHlw8012Commands); - if (CMND_POWERLOW == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_pmin = payload; - } - nvalue = Settings.hlw_pmin; - unit = UNIT_WATT; - } - else if (CMND_POWERHIGH == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_pmax = payload; - } - nvalue = Settings.hlw_pmax; - unit = UNIT_WATT; - } - else if (CMND_VOLTAGELOW == command_code) { - if ((payload >= 0) && (payload < 501)) { - Settings.hlw_umin = payload; - } - nvalue = Settings.hlw_umin; - unit = UNIT_VOLT; - } - else if (CMND_VOLTAGEHIGH == command_code) { - if ((payload >= 0) && (payload < 501)) { - Settings.hlw_umax = payload; - } - nvalue = Settings.hlw_umax; - unit = UNIT_VOLT; - } - else if (CMND_CURRENTLOW == command_code) { - if ((payload >= 0) && (payload < 16001)) { - Settings.hlw_imin = payload; - } - nvalue = Settings.hlw_imin; - unit = UNIT_MILLIAMPERE; - } - else if (CMND_CURRENTHIGH == command_code) { - if ((payload >= 0) && (payload < 16001)) { - Settings.hlw_imax = payload; - } - nvalue = Settings.hlw_imax; - unit = UNIT_MILLIAMPERE; - } - else if ((CMND_ENERGYRESET == command_code) && (index > 0) && (index <= 3)) { - char *p; - unsigned long lnum = strtoul(dataBuf, &p, 10); - if (p != dataBuf) { - switch (index) { - case 1: - hlw_kWhtoday = lnum *100000; - RtcSettings.hlw_kWhtoday = hlw_kWhtoday; - Settings.hlw_kWhtoday = hlw_kWhtoday; - break; - case 2: - Settings.hlw_kWhyesterday = lnum *100000; - break; - case 3: - RtcSettings.hlw_kWhtotal = lnum *100; - Settings.hlw_kWhtotal = RtcSettings.hlw_kWhtotal; - break; - } - } - char syesterday_energy[10]; - char stoday_energy[10]; - char stotal_energy[10]; - dtostrfd((float)Settings.hlw_kWhyesterday / 100000000, Settings.flag2.energy_resolution, syesterday_energy); - dtostrfd((float)RtcSettings.hlw_kWhtoday / 100000000, Settings.flag2.energy_resolution, stoday_energy); - dtostrfd((float)(RtcSettings.hlw_kWhtotal + (hlw_kWhtoday / 1000)) / 100000, Settings.flag2.energy_resolution, stotal_energy); - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"" D_TOTAL "\":%s,\"" D_YESTERDAY "\":%s,\"" D_TODAY "\":%s}}"), - command, stotal_energy, syesterday_energy, stoday_energy); - status_flag = 1; - } - else if (CMND_HLWPCAL == command_code) { - if ((payload > 0) && (payload < 32001)) { - Settings.hlw_power_calibration = (payload > 4000) ? payload : HLW_PREF_PULSE; // 12530 - } - nvalue = Settings.hlw_power_calibration; - unit = UNIT_MICROSECOND; - } - else if (CMND_HLWPSET == command_code) { - if ((payload > 0) && (payload < 3601) && hlw_cf_pulse_length) { - Settings.hlw_power_calibration = (payload * 10 * hlw_cf_pulse_length) / HLW_PREF; - } - snprintf_P(command, sizeof(command), PSTR(D_CMND_HLWPCAL)); - nvalue = Settings.hlw_power_calibration; - unit = UNIT_MICROSECOND; - } - else if (CMND_HLWUCAL == command_code) { - if ((payload > 0) && (payload < 32001)) { - Settings.hlw_voltage_calibration = (payload > 999) ? payload : HLW_UREF_PULSE; // 1950 - } - nvalue = Settings.hlw_voltage_calibration; - unit = UNIT_MICROSECOND; - } - else if (CMND_HLWUSET == command_code) { - if ((payload > 0) && (payload < 501) && hlw_cf1_voltage_pulse_length) { - Settings.hlw_voltage_calibration = (payload * 10 * hlw_cf1_voltage_pulse_length) / HLW_UREF; - } - snprintf_P(command, sizeof(command), PSTR(D_CMND_HLWUCAL)); - nvalue = Settings.hlw_voltage_calibration; - unit = UNIT_MICROSECOND; - } - else if (CMND_HLWICAL == command_code) { - if ((payload > 0) && (payload < 32001)) { - Settings.hlw_current_calibration = (payload > 1100) ? payload : HLW_IREF_PULSE; // 3500 - } - nvalue = Settings.hlw_current_calibration; - unit = UNIT_MICROSECOND; - } - else if (CMND_HLWISET == command_code) { - if ((payload > 0) && (payload < 16001) && hlw_cf1_current_pulse_length) { - Settings.hlw_current_calibration = (payload * hlw_cf1_current_pulse_length) / HLW_IREF; - } - snprintf_P(command, sizeof(command), PSTR(D_CMND_HLWICAL)); - nvalue = Settings.hlw_current_calibration; - unit = UNIT_MICROSECOND; - } -#if FEATURE_POWER_LIMIT - else if (CMND_MAXPOWER == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_mpl = payload; - } - nvalue = Settings.hlw_mpl; - unit = UNIT_WATT; - } - else if (CMND_MAXPOWERHOLD == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_mplh = (1 == payload) ? MAX_POWER_HOLD : payload; - } - nvalue = Settings.hlw_mplh; - unit = UNIT_SECOND; - } - else if (CMND_MAXPOWERWINDOW == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_mplw = (1 == payload) ? MAX_POWER_WINDOW : payload; - } - nvalue = Settings.hlw_mplw; - unit = UNIT_SECOND; - } - else if (CMND_SAFEPOWER == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_mspl = payload; - } - nvalue = Settings.hlw_mspl; - unit = UNIT_WATT; - } - else if (CMND_SAFEPOWERHOLD == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_msplh = (1 == payload) ? SAFE_POWER_HOLD : payload; - } - nvalue = Settings.hlw_msplh; - unit = UNIT_SECOND; - } - else if (CMND_SAFEPOWERWINDOW == command_code) { - if ((payload >= 0) && (payload < 1440)) { - Settings.hlw_msplw = (1 == payload) ? SAFE_POWER_WINDOW : payload; - } - nvalue = Settings.hlw_msplw; - unit = UNIT_MINUTE; - } - else if (CMND_MAXENERGY == command_code) { - if ((payload >= 0) && (payload < 3601)) { - Settings.hlw_mkwh = payload; - hlw_mkwh_state = 3; - } - nvalue = Settings.hlw_mkwh; - unit = UNIT_WATTHOUR; - } - else if (CMND_MAXENERGYSTART == command_code) { - if ((payload >= 0) && (payload < 24)) { - Settings.hlw_mkwhs = payload; - } - nvalue = Settings.hlw_mkwhs; - unit = UNIT_HOUR; - } -#endif // FEATURE_POWER_LIMIT - else { - serviced = false; - } - if (!status_flag) { - if (Settings.flag.value_units) { - snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_SPACE_UNIT, command, nvalue, GetTextIndexed(sunit, sizeof(sunit), unit, kUnitNames)); - } else { - snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, nvalue); - } - } - return serviced; -} - -/********************************************************************************************/ - -#ifdef USE_WEBSERVER -const char HTTP_ENERGY_SNS[] PROGMEM = - "{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" - "{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" - "{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}" - "{s}" D_POWER_FACTOR "{m}%s{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 - -void HlwShow(boolean json, boolean option) -{ -/* option 0 = do not show period energy usage - * option 1 = show period energy usage - */ - float total_energy; - float daily_energy; - float energy; - float watts; - float voltage; - float current; - float power_factor; - char stotal_energy[10]; - char sdaily_energy[10]; - char senergy[10]; - char swatts[10]; - char svoltage[10]; - char scurrent[10]; - char spower_factor[10]; - char syesterday_energy[10]; - char speriod[20]; - - HlwReadEnergy(option, total_energy, daily_energy, energy, watts, voltage, current, power_factor); - dtostrfd(total_energy, Settings.flag2.energy_resolution, stotal_energy); - dtostrfd(daily_energy, Settings.flag2.energy_resolution, sdaily_energy); - dtostrfd(energy, Settings.flag2.wattage_resolution, senergy); - dtostrfd(watts, Settings.flag2.wattage_resolution, swatts); - dtostrfd(voltage, Settings.flag2.voltage_resolution, svoltage); - dtostrfd(current, Settings.flag2.current_resolution, scurrent); - dtostrfd(power_factor, 2, spower_factor); - dtostrfd((float)Settings.hlw_kWhyesterday / 100000000, Settings.flag2.energy_resolution, syesterday_energy); - - if (json) { - snprintf_P(speriod, sizeof(speriod), PSTR(",\"" D_PERIOD "\":%s"), senergy); - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_TOTAL "\":%s,\"" D_YESTERDAY "\":%s,\"" D_TODAY "\":%s%s,\"" D_POWERUSAGE "\":%s,\"" D_POWERFACTOR "\":%s,\"" D_VOLTAGE "\":%s,\"" D_CURRENT "\":%s}"), - mqtt_data, stotal_energy, syesterday_energy, sdaily_energy, (option) ? speriod : "", swatts, spower_factor, svoltage, scurrent); -#ifdef USE_DOMOTICZ - if (option) { // Only send if telemetry - dtostrfd(total_energy * 1000, 1, stotal_energy); - DomoticzSensorPowerEnergy((uint16_t)watts, stotal_energy); // PowerUsage, EnergyToday - DomoticzSensor(DZ_VOLTAGE, svoltage); // Voltage - DomoticzSensor(DZ_CURRENT, scurrent); // Current - } -#endif // USE_DOMOTICZ -#ifdef USE_WEBSERVER - } else { - snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS, svoltage, scurrent, swatts, spower_factor, sdaily_energy, syesterday_energy, stotal_energy); -#endif // USE_WEBSERVER - } -} - -void MqttShowHlw8012(byte option) -{ -/* option 0 = do not show period energy usage - * option 1 = show period energy usage - */ -// {"Time":"2017-03-04T13:37:24", "Total":0.013, "Yesterday":0.013, "Today":0.000, "Period":0, "Power":0, "Factor":0.00, "Voltage":0, "Current":0.000} - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_TIME "\":\"%s\","), GetDateAndTime().c_str()); - HlwShow(1, option); - MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_ENERGY), Settings.flag.mqtt_sensor_retain); -} - -void HlwMqttStatus() -{ - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS8_POWER "\":{")); - HlwShow(1, 0); - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); -} - -/*********************************************************************************************\ - * Interface -\*********************************************************************************************/ - -#define XSNS_03 - -boolean Xsns03(byte function) -{ - boolean result = false; - - if (hlw_flg) { - switch (function) { - case FUNC_XSNS_INIT: - HlwInit(); - break; -// case FUNC_XSNS_PREP: -// break; -// case FUNC_XSNS_JSON_APPEND: -// break; - case FUNC_XSNS_MQTT_SHOW: - MqttShowHlw8012(1); - break; -#ifdef USE_WEBSERVER - case FUNC_XSNS_WEB: - HlwShow(0, 0); - break; -#endif // USE_WEBSERVER - } - } - return result; -} diff --git a/sonoff/xsns_interface.ino b/sonoff/xsns_interface.ino index 75702590a..7ef5d8199 100644 --- a/sonoff/xsns_interface.ino +++ b/sonoff/xsns_interface.ino @@ -117,10 +117,11 @@ boolean XsnsCall(byte Function) switch (Function) { case FUNC_XSNS_INIT: + case FUNC_XSNS_EVERY_SECOND: case FUNC_XSNS_PREP: case FUNC_XSNS_JSON_APPEND: - case FUNC_XSNS_MQTT_SHOW: case FUNC_XSNS_WEB: + case FUNC_XSNS_SAVE_STATE: for (byte x = 0; x < xsns_present; x++) { xsns_func_ptr[x](Function); }