From 178d42c286f9a4f714bd80f58a65145a28502e7a Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:50:45 +0200 Subject: [PATCH] Change ESP32 support for energy margin checks, like ``MaxPower2`` per phase (#21695) - Add ESP32 support for power and energy limit checks, like ``MaxEnergy2`` per phase (#21695) - Bump version v14.1.0.3 --- CHANGELOG.md | 19 +- RELEASENOTES.md | 5 +- tasmota/include/tasmota_version.h | 2 +- tasmota/tasmota_support/support_tasmota.ino | 9 +- .../xdrv_03_esp32_energy.ino | 422 ++++++++++++------ 5 files changed, 310 insertions(+), 147 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 048bb2a84..5b6367ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,20 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - Development -## [14.1.0.2] +## [14.1.0.3] +### Added +- ESP32 support for power and energy limit checks, like ``MaxEnergy2`` per phase (#21695) + +### Breaking Changed + +### Changed +- ESP32 support for energy margin checks, like ``MaxPower2`` per phase (#21695) + +### Fixed + +### Removed + +## [14.1.0.2] 20240627 ### Added - Support for Sonoff WTS01 temperature sensor using SerialBridge in ``SSerialMode 3`` - Berry `classof` extended to class methods (#21615) @@ -14,8 +27,6 @@ All notable changes to this project will be documented in this file. - Matter show event name in logs (#21649) - Matter full support of events (#21698) -### Breaking Changed - ### Changed - SerialBridge command ``SSerialSend9`` replaced by ``SSerialMode`` - SML replace vars in descriptor and line (#21622) @@ -37,8 +48,6 @@ All notable changes to this project will be documented in this file. - Matter resumption final ack (#21673) - ESP32 allow use of UART0 with enabled USB_CDC_CONSOLE (#21496) -### Removed - ## [14.1.0.1] 20240611 ### Added - Berry solidification of `bytes` instances (#21558) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e2c86e624..00645d4d8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -119,11 +119,12 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm [Complete list](BUILDS.md) of available feature and sensors. -## Changelog v14.1.0.2 +## Changelog v14.1.0.3 ### Added - Support for QMP6988 temperature and pressure sensor - Support for Sonoff WTS01 temperature sensor using SerialBridge in ``SSerialMode 3`` - Extend command ``SetOption147 1`` to disable publish of IRReceived MQTT messages [#21574](https://github.com/arendst/Tasmota/issues/21574) +- ESP32 support for power and energy limit checks, like ``MaxEnergy2`` per phase [#21695](https://github.com/arendst/Tasmota/issues/21695) - Berry solidification of `bytes` instances [#21558](https://github.com/arendst/Tasmota/issues/21558) - Berry automatic rounding of float to int when calling C mapped functions [#21601](https://github.com/arendst/Tasmota/issues/21601) - Berry add `math.round` [#21602](https://github.com/arendst/Tasmota/issues/21602) @@ -148,7 +149,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm - SML replace vars in descriptor and line [#21622](https://github.com/arendst/Tasmota/issues/21622) - NeoPool using temperature as only frequently changing value for NPTeleperiod [#21628](https://github.com/arendst/Tasmota/issues/21628) - NeoPool make compiler setting available by `user_config_override.h` [#21645](https://github.com/arendst/Tasmota/issues/21645) -- ESP32 Core3 platform update from 2024.05.13 to 2024.06.10 [#21569](https://github.com/arendst/Tasmota/issues/21569) +- ESP32 support for energy margin checks, like ``MaxPower2`` per phase [#21695](https://github.com/arendst/Tasmota/issues/21695) - ESP32 MI32 refactoring, bugfixes, generic device scanning [#21603](https://github.com/arendst/Tasmota/issues/21603) - ESP32 MI32 improve parser [#21648](https://github.com/arendst/Tasmota/issues/21648) - Matter refactoring of bridged devices [#21575](https://github.com/arendst/Tasmota/issues/21575) diff --git a/tasmota/include/tasmota_version.h b/tasmota/include/tasmota_version.h index ecd3f1dfe..ecf9d54dd 100644 --- a/tasmota/include/tasmota_version.h +++ b/tasmota/include/tasmota_version.h @@ -22,6 +22,6 @@ #define TASMOTA_SHA_SHORT // Filled by Github sed -const uint32_t TASMOTA_VERSION = 0x0E010002; // 14.1.0.2 +const uint32_t TASMOTA_VERSION = 0x0E010003; // 14.1.0.3 #endif // _TASMOTA_VERSION_H_ diff --git a/tasmota/tasmota_support/support_tasmota.ino b/tasmota/tasmota_support/support_tasmota.ino index 71421ea9c..b614532df 100644 --- a/tasmota/tasmota_support/support_tasmota.ino +++ b/tasmota/tasmota_support/support_tasmota.ino @@ -690,6 +690,7 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source) // state 2 = POWER_TOGGLE = Toggle relay // state 3 = POWER_BLINK = Blink relay // state 4 = POWER_BLINK_STOP = Stop blinking relay +// state 5 = POWER_OFF_FORCE = Relay off even if locked // state 8 = POWER_OFF_NO_STATE = Relay Off and no publishPowerState // state 9 = POWER_ON_NO_STATE = Relay On and no publishPowerState // state 10 = POWER_TOGGLE_NO_STATE = Toggle relay and no publishPowerState @@ -709,6 +710,12 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source) } #endif // USE_SONOFF_IFAN + bool force_power_off = false; + if (POWER_OFF_FORCE == state) { + force_power_off = true; + state = POWER_OFF; + } + bool publish_power = true; if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) { state &= 3; // POWER_OFF, POWER_ON or POWER_TOGGLE @@ -720,7 +727,7 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source) } TasmotaGlobal.active_device = device; - if (bitRead(Settings->power_lock, device -1)) { + if (!force_power_off && bitRead(Settings->power_lock, device -1)) { AddLog(LOG_LEVEL_INFO, PSTR("CMD: Power%d is LOCKED"), device); state = POWER_SHOW_STATE; // Only show state. Make no change } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino b/tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino index 790fc79a6..3e925642f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino @@ -107,6 +107,23 @@ typedef union { }; } tEnergyBitfield; +typedef struct { + uint16_t min_voltage; // VoltageLow + uint16_t max_voltage; // VoltageHigh + uint16_t min_current; // CurrentLow + uint16_t max_current; // CurrentHigh + uint16_t min_power; // PowerLow + uint16_t max_power; // PowerHigh + uint16_t max_power_limit; // MaxPowerLimit + uint16_t max_power_limit_hold; // MaxPowerLimitHold + uint16_t max_power_limit_window; // MaxPowerLimitWindow + uint16_t max_power_safe_limit; // MaxSafePowerLimit + uint16_t max_power_safe_limit_hold; // MaxSafePowerLimitHold + uint16_t max_power_safe_limit_window; // MaxSafePowerLimitWindow + uint16_t max_energy; // MaxEnergy + uint16_t max_energy_start; // MaxEnergyStart +} tPhase; + typedef struct { uint32_t crc32; // To detect file changes uint16_t version; // To detect driver function changes @@ -134,6 +151,7 @@ typedef struct { uint16_t tariff[4][2]; tEnergyUsage energy_usage; + tPhase phase[ENERGY_MAX_PHASES]; } tEnergySettings; typedef struct { @@ -168,8 +186,8 @@ typedef struct { float daily_sum_export_balanced; // 123.123 kWh uint16_t power_history[ENERGY_MAX_PHASES][3]; - uint16_t mplh_counter; - uint16_t mplw_counter; + uint16_t mplh_counter[ENERGY_MAX_PHASES]; + uint16_t mplw_counter[ENERGY_MAX_PHASES]; uint8_t data_valid[ENERGY_MAX_PHASES]; uint8_t phase_count; // Number of phases active @@ -177,8 +195,9 @@ typedef struct { uint8_t command_code; uint8_t power_steady_counter; // Allow for power on stabilization uint8_t margin_stable; - uint8_t mplr_counter; - uint8_t max_energy_state; + uint8_t mplr_counter[ENERGY_MAX_PHASES]; + uint8_t max_energy_state[ENERGY_MAX_PHASES]; + uint8_t hour; uint8_t gui_indirect[ENERGY_MAX_PHASES]; uint8_t gui_rotate; @@ -195,12 +214,12 @@ typedef struct { bool type_dc; bool power_on; - bool min_power_flag; - bool max_power_flag; - bool min_voltage_flag; - bool max_voltage_flag; - bool min_current_flag; - bool max_current_flag; + bool min_power_flag[ENERGY_MAX_PHASES]; + bool max_power_flag[ENERGY_MAX_PHASES]; + bool min_voltage_flag[ENERGY_MAX_PHASES]; + bool max_voltage_flag[ENERGY_MAX_PHASES]; + bool min_current_flag[ENERGY_MAX_PHASES]; + bool max_current_flag[ENERGY_MAX_PHASES]; } tEnergy; tEnergy *Energy = nullptr; @@ -286,10 +305,26 @@ void EnergySettingsLoad(bool erase) { Energy->Settings.voltage_calibration[i] = Settings->energy_voltage_calibration;; Energy->Settings.current_calibration[i] = Settings->energy_current_calibration;; Energy->Settings.frequency_calibration[i] = Settings->energy_frequency_calibration; + Energy->Settings.phase[i].min_voltage = Settings->energy_min_voltage; + Energy->Settings.phase[i].max_voltage = Settings->energy_max_voltage; + Energy->Settings.phase[i].min_current = Settings->energy_min_current; + Energy->Settings.phase[i].max_current = Settings->energy_max_current; + Energy->Settings.phase[i].min_power = Settings->energy_min_power; + Energy->Settings.phase[i].max_power = Settings->energy_max_power; } Energy->Settings.power_calibration[1] = Settings->energy_power_calibration2; Energy->Settings.voltage_calibration[1] = Settings->energy_voltage_calibration2; Energy->Settings.current_calibration[1] = Settings->energy_current_calibration2; + + // Only restore phase 1 for backward compatibility (all power off) + Energy->Settings.phase[0].max_power_limit = Settings->energy_max_power_limit; + Energy->Settings.phase[0].max_power_limit_hold = Settings->energy_max_power_limit_hold; + Energy->Settings.phase[0].max_power_limit_window = Settings->energy_max_power_limit_window; + Energy->Settings.phase[0].max_power_safe_limit = Settings->energy_max_power_safe_limit; + Energy->Settings.phase[0].max_power_safe_limit_hold = Settings->energy_max_power_safe_limit_hold; + Energy->Settings.phase[0].max_power_safe_limit_window = Settings->energy_max_power_safe_limit_window; + Energy->Settings.phase[0].max_energy = Settings->energy_max_energy; + Energy->Settings.phase[0].max_energy_start = Settings->energy_max_energy_start; /* RtcEnergySettings.energy_total_kWh[0] = 0; RtcEnergySettings.energy_total_kWh[1] = 0; @@ -654,11 +689,19 @@ void Energy200ms(void) { } EnergyUpdateToday(); } + if (midnight) { - Energy->max_energy_state = 3; + for (uint32_t phase = 0; phase < Energy->phase_count; phase++) { + Energy->max_energy_state[phase] = 3; + } } - if ((RtcTime.hour == Settings->energy_max_energy_start) && (3 == Energy->max_energy_state )) { - Energy->max_energy_state = 0; + if (RtcTime.hour != Energy->hour) { + Energy->hour = RtcTime.hour; + for (uint32_t phase = 0; phase < Energy->phase_count; phase++) { + if ((RtcTime.hour == Energy->Settings.phase[phase].max_energy_start) && (3 == Energy->max_energy_state[phase] )) { + Energy->max_energy_state[phase] = 0; + } + } } } @@ -747,86 +790,124 @@ void EnergyMarginCheck(void) { ResponseAppend_P(PSTR("\"" D_CMND_POWERDELTA "\":%s"), EnergyFmt(power_diff_f, 0)); } - uint16_t energy_power_u = (uint16_t)(Energy->active_power[0]); - - if (Energy->power_on && (Settings->energy_min_power || Settings->energy_max_power || Settings->energy_min_voltage || Settings->energy_max_voltage || Settings->energy_min_current || Settings->energy_max_current)) { - uint16_t energy_voltage_u = (uint16_t)(Energy->voltage[0]); - uint16_t energy_current_u = (uint16_t)(Energy->current[0] * 1000); - - DEBUG_DRIVER_LOG(PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u); - + uint16_t energy_power_u; + if (Energy->power_on) { bool flag; - if (EnergyMargin(false, Settings->energy_min_power, energy_power_u, flag, Energy->min_power_flag)) { - ResponseAppend_P(PSTR("%s\"" D_CMND_POWERLOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); - jsonflg = true; - } - if (EnergyMargin(true, Settings->energy_max_power, energy_power_u, flag, Energy->max_power_flag)) { - ResponseAppend_P(PSTR("%s\"" D_CMND_POWERHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); - jsonflg = true; - } - if (EnergyMargin(false, Settings->energy_min_voltage, energy_voltage_u, flag, Energy->min_voltage_flag)) { - ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGELOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); - jsonflg = true; - } - if (EnergyMargin(true, Settings->energy_max_voltage, energy_voltage_u, flag, Energy->max_voltage_flag)) { - ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGEHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); - jsonflg = true; - } - if (EnergyMargin(false, Settings->energy_min_current, energy_current_u, flag, Energy->min_current_flag)) { - ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTLOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); - jsonflg = true; - } - if (EnergyMargin(true, Settings->energy_max_current, energy_current_u, flag, Energy->max_current_flag)) { - ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); - jsonflg = true; - } - } - if (jsonflg) { - ResponseJsonEndEnd(); - MqttPublishTele(PSTR(D_RSLT_MARGINS)); - EnergyMqttShow(); - Energy->margin_stable = 3; // Allow 2 seconds to stabilize before reporting - } + for (uint32_t phase = 0; phase < Energy->phase_count; phase++) { + energy_power_u = (uint16_t)(Energy->active_power[phase]); + uint16_t energy_voltage_u = (uint16_t)(Energy->voltage[phase]); + uint16_t energy_current_u = (uint16_t)(Energy->current[phase] * 1000); - // Max Power - if (Settings->energy_max_power_limit) { - if (Energy->active_power[0] > 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) { - ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHED "\":%d}"), energy_power_u); - MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING); - EnergyMqttShow(); - SetAllPower(POWER_OFF_FORCE, SRC_MAXPOWER); - if (!Energy->mplr_counter) { - Energy->mplr_counter = Settings->param[P_MAX_POWER_RETRY] +1; // SetOption33 - Max Power Retry count - } - Energy->mplw_counter = Settings->energy_max_power_limit_window; + DEBUG_DRIVER_LOG(PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u); + + if (Energy->Settings.phase[phase].min_power) { + if (EnergyMargin(false, Energy->Settings.phase[phase].min_power, energy_power_u, flag, Energy->min_power_flag[phase])) { + ResponseAppend_P(PSTR("%s\"" D_CMND_POWERLOW "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag)); + jsonflg = true; + } + } + if (Energy->Settings.phase[phase].max_power) { + if (EnergyMargin(true, Energy->Settings.phase[phase].max_power, energy_power_u, flag, Energy->max_power_flag[phase])) { + ResponseAppend_P(PSTR("%s\"" D_CMND_POWERHIGH "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag)); + jsonflg = true; + } + } + if (Energy->Settings.phase[phase].min_voltage) { + if (EnergyMargin(false, Energy->Settings.phase[phase].min_voltage, energy_voltage_u, flag, Energy->min_voltage_flag[phase])) { + ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGELOW "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag)); + jsonflg = true; + } + } + if (Energy->Settings.phase[phase].max_voltage) { + if (EnergyMargin(true, Energy->Settings.phase[phase].max_voltage, energy_voltage_u, flag, Energy->max_voltage_flag[phase])) { + ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGEHIGH "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag)); + jsonflg = true; + } + } + if (Energy->Settings.phase[phase].min_current) { + if (EnergyMargin(false, Energy->Settings.phase[phase].min_current, energy_current_u, flag, Energy->min_current_flag[phase])) { + ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTLOW "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag)); + jsonflg = true; + } + } + if (Energy->Settings.phase[phase].max_current) { + if (EnergyMargin(true, Energy->Settings.phase[phase].max_current, energy_current_u, flag, Energy->max_current_flag[phase])) { + ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTHIGH "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag)); + jsonflg = true; } } } - else if (TasmotaGlobal.power && (energy_power_u <= Settings->energy_max_power_limit)) { - Energy->mplh_counter = 0; - Energy->mplr_counter = 0; - Energy->mplw_counter = 0; + if (jsonflg) { + ResponseJsonEndEnd(); + MqttPublishTele(PSTR(D_RSLT_MARGINS)); + EnergyMqttShow(); + Energy->margin_stable = 3; // Allow 2 seconds to stabilize before reporting } - if (!TasmotaGlobal.power) { - if (Energy->mplw_counter) { - Energy->mplw_counter--; - } else { - if (Energy->mplr_counter) { - Energy->mplr_counter--; - if (Energy->mplr_counter) { - ResponseTime_P(PSTR(",\"" D_JSON_POWERMONITOR "\":\"%s\"}"), GetStateText(1)); - MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_JSON_POWERMONITOR)); - RestorePower(true, SRC_MAXPOWER); - } else { - ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0)); + } + + // Max Power + bool set_all_power = true; // Use all power control for backward compatibility + for (uint32_t phase = 1; phase < Energy->phase_count; phase++) { + if (Energy->Settings.phase[phase].max_power_limit) { + set_all_power = false; // If any other power limit is set use new selected power control + break; + } + } + for (uint32_t phase = 0; phase < Energy->phase_count; phase++) { + if (Energy->Settings.phase[phase].max_power_limit) { + energy_power_u = (uint16_t)(Energy->active_power[phase]); + bool power_on = (TasmotaGlobal.power & (1 << phase)); + if (Energy->active_power[phase] > Energy->Settings.phase[phase].max_power_limit) { + if (!Energy->mplh_counter[phase]) { + Energy->mplh_counter[phase] = Energy->Settings.phase[phase].max_power_limit_hold; + } else { + Energy->mplh_counter[phase]--; + if (!Energy->mplh_counter[phase]) { + ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHED "%d\":%d}"), phase +1, energy_power_u); MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING); EnergyMqttShow(); - SetAllPower(POWER_OFF_FORCE, SRC_MAXPOWER); + if (set_all_power) { + SetAllPower(POWER_OFF_FORCE, SRC_MAXPOWER); + } else { + ExecuteCommandPower(phase +1, POWER_OFF_FORCE, SRC_MAXPOWER); + } + if (!Energy->mplr_counter[phase]) { + Energy->mplr_counter[phase] = Settings->param[P_MAX_POWER_RETRY] +1; // SetOption33 - Max Power Retry count + } + Energy->mplw_counter[phase] = Energy->Settings.phase[phase].max_power_limit_window; + } + } + } + else if (power_on && (energy_power_u <= Energy->Settings.phase[phase].max_power_limit)) { + Energy->mplh_counter[phase] = 0; + Energy->mplr_counter[phase] = 0; + Energy->mplw_counter[phase] = 0; + } + + if (!power_on) { + if (Energy->mplw_counter[phase]) { + Energy->mplw_counter[phase]--; + } else { + if (Energy->mplr_counter[phase]) { + Energy->mplr_counter[phase]--; + if (Energy->mplr_counter[phase]) { + ResponseTime_P(PSTR(",\"" D_JSON_POWERMONITOR "%d\":\"%s\"}"), phase +1, GetStateText(1)); + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_JSON_POWERMONITOR)); + if (set_all_power) { + RestorePower(true, SRC_MAXPOWER); + } else { + ExecuteCommandPower(phase +1, POWER_ON, SRC_MAXPOWER); + } + } else { + ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHEDRETRY "%d\":\"%s\"}"), phase +1, GetStateText(0)); + MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING); + EnergyMqttShow(); + if (set_all_power) { + SetAllPower(POWER_OFF_FORCE, SRC_MAXPOWER); + } else { + ExecuteCommandPower(phase +1, POWER_OFF_FORCE, SRC_MAXPOWER); + } + } } } } @@ -834,22 +915,44 @@ void EnergyMarginCheck(void) { } // Max Energy - if (Settings->energy_max_energy) { - uint16_t energy_daily_u = (uint16_t)(Energy->daily_sum * 1000); - if (!Energy->max_energy_state && (RtcTime.hour == Settings->energy_max_energy_start)) { - Energy->max_energy_state = 1; - ResponseTime_P(PSTR(",\"" D_JSON_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1)); - MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_JSON_ENERGYMONITOR)); - RestorePower(true, SRC_MAXENERGY); - } - else if ((1 == Energy->max_energy_state ) && (energy_daily_u >= Settings->energy_max_energy)) { - Energy->max_energy_state = 2; - ResponseTime_P(PSTR(",\"" D_JSON_MAXENERGYREACHED "\":%3_f}"), &Energy->daily_sum); - MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING); - EnergyMqttShow(); - SetAllPower(POWER_OFF_FORCE, SRC_MAXENERGY); + set_all_power = true; // Use all power control for backward compatibility + for (uint32_t phase = 1; phase < Energy->phase_count; phase++) { + if (Energy->Settings.phase[phase].max_energy) { + set_all_power = false; // If any other max energy is set use new selected power control + break; } } + float daily_sum = Energy->daily_sum; + for (uint32_t phase = 0; phase < Energy->phase_count; phase++) { + if (Energy->Settings.phase[phase].max_energy) { + if (!set_all_power) { + daily_sum = Energy->daily_kWh[phase]; + } + uint16_t energy_daily_u = (uint16_t)(daily_sum * 1000); + if (!Energy->max_energy_state[phase] && (RtcTime.hour == Energy->Settings.phase[phase].max_energy_start)) { + Energy->max_energy_state[phase] = 1; + ResponseTime_P(PSTR(",\"" D_JSON_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1)); + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_JSON_ENERGYMONITOR)); + if (set_all_power) { + RestorePower(true, SRC_MAXENERGY); + } else { + ExecuteCommandPower(phase +1, POWER_ON, SRC_MAXENERGY); + } + } + else if ((1 == Energy->max_energy_state[phase]) && (energy_daily_u >= Energy->Settings.phase[phase].max_energy)) { + Energy->max_energy_state[phase] = 2; + ResponseTime_P(PSTR(",\"" D_JSON_MAXENERGYREACHED "\":%3_f}"), &daily_sum); + MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING); + EnergyMqttShow(); + if (set_all_power) { + SetAllPower(POWER_OFF_FORCE, SRC_MAXENERGY); + } else { + ExecuteCommandPower(phase +1, POWER_OFF_FORCE, SRC_MAXENERGY); + } + } + } + } + EnergyFmtFree(); } @@ -1251,13 +1354,34 @@ void CmndEnergyConfig(void) { void EnergyMarginStatus(void) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":[")); - for (uint32_t i = 0; i < ENERGY_MAX_PHASES; i++) { + for (uint32_t i = 0; i < Energy->phase_count; i++) { ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.power_delta[i]); } - ResponseAppend_P(PSTR("],\"" 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->energy_min_power, Settings->energy_max_power, - Settings->energy_min_voltage, Settings->energy_max_voltage, Settings->energy_min_current, Settings->energy_max_current); + ResponseAppend_P(PSTR("],\"" D_CMND_POWERLOW "\":[")); + for (uint32_t i = 0; i < Energy->phase_count; i++) { + ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.phase[i].min_power); + } + ResponseAppend_P(PSTR("],\"" D_CMND_POWERHIGH "\":[")); + for (uint32_t i = 0; i < Energy->phase_count; i++) { + ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.phase[i].max_power); + } + ResponseAppend_P(PSTR("],\"" D_CMND_VOLTAGELOW "\":[")); + for (uint32_t i = 0; i < Energy->phase_count; i++) { + ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.phase[i].min_voltage); + } + ResponseAppend_P(PSTR("],\"" D_CMND_VOLTAGEHIGH "\":[")); + for (uint32_t i = 0; i < Energy->phase_count; i++) { + ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.phase[i].max_voltage); + } + ResponseAppend_P(PSTR("],\"" D_CMND_CURRENTLOW "\":[")); + for (uint32_t i = 0; i < Energy->phase_count; i++) { + ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.phase[i].min_current); + } + ResponseAppend_P(PSTR("],\"" D_CMND_CURRENTHIGH "\":[")); + for (uint32_t i = 0; i < Energy->phase_count; i++) { + ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.phase[i].max_current); + } + ResponseAppend_P(PSTR("]}}")); } void CmndPowerDelta(void) { @@ -1270,66 +1394,84 @@ void CmndPowerDelta(void) { } void CmndPowerLow(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { - Settings->energy_min_power = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.phase[XdrvMailbox.index -1].min_power = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].min_power); } - ResponseCmndNumber(Settings->energy_min_power); } void CmndPowerHigh(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { - Settings->energy_max_power = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_power = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_power); } - ResponseCmndNumber(Settings->energy_max_power); } void CmndVoltageLow(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { - Settings->energy_min_voltage = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { + Energy->Settings.phase[XdrvMailbox.index -1].min_voltage = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].min_voltage); } - ResponseCmndNumber(Settings->energy_min_voltage); } void CmndVoltageHigh(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { - Settings->energy_max_voltage = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_voltage = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_voltage); } - ResponseCmndNumber(Settings->energy_max_voltage); } void CmndCurrentLow(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { - Settings->energy_min_current = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { + Energy->Settings.phase[XdrvMailbox.index -1].min_current = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].min_current); } - ResponseCmndNumber(Settings->energy_min_current); } void CmndCurrentHigh(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { - Settings->energy_max_current = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_current = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_current); } - ResponseCmndNumber(Settings->energy_max_current); } void CmndMaxPower(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { - Settings->energy_max_power_limit = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_power_limit = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_power_limit); } - ResponseCmndNumber(Settings->energy_max_power_limit); } void CmndMaxPowerHold(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { - Settings->energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_power_limit_hold); } - ResponseCmndNumber(Settings->energy_max_power_limit_hold); } void CmndMaxPowerWindow(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { - Settings->energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_power_limit_window); } - ResponseCmndNumber(Settings->energy_max_power_limit_window); } void CmndSafePower(void) { @@ -1354,18 +1496,22 @@ void CmndSafePowerWindow(void) { } void CmndMaxEnergy(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { - Settings->energy_max_energy = XdrvMailbox.payload; - Energy->max_energy_state = 3; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_energy = XdrvMailbox.payload; + Energy->max_energy_state[XdrvMailbox.index -1] = 3; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_energy); } - ResponseCmndNumber(Settings->energy_max_energy); } void CmndMaxEnergyStart(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) { - Settings->energy_max_energy_start = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) { + Energy->Settings.phase[XdrvMailbox.index -1].max_energy_start = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.phase[XdrvMailbox.index -1].max_energy_start); } - ResponseCmndNumber(Settings->energy_max_energy_start); } /********************************************************************************************/