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
This commit is contained in:
Theo Arends 2024-06-27 16:50:45 +02:00
parent 20d0207890
commit 178d42c286
5 changed files with 310 additions and 147 deletions

View File

@ -3,7 +3,20 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - Development ## [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 ### Added
- Support for Sonoff WTS01 temperature sensor using SerialBridge in ``SSerialMode 3`` - Support for Sonoff WTS01 temperature sensor using SerialBridge in ``SSerialMode 3``
- Berry `classof` extended to class methods (#21615) - 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 show event name in logs (#21649)
- Matter full support of events (#21698) - Matter full support of events (#21698)
### Breaking Changed
### Changed ### Changed
- SerialBridge command ``SSerialSend9`` replaced by ``SSerialMode`` - SerialBridge command ``SSerialSend9`` replaced by ``SSerialMode``
- SML replace vars in descriptor and line (#21622) - 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) - Matter resumption final ack (#21673)
- ESP32 allow use of UART0 with enabled USB_CDC_CONSOLE (#21496) - ESP32 allow use of UART0 with enabled USB_CDC_CONSOLE (#21496)
### Removed
## [14.1.0.1] 20240611 ## [14.1.0.1] 20240611
### Added ### Added
- Berry solidification of `bytes` instances (#21558) - Berry solidification of `bytes` instances (#21558)

View File

@ -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. [Complete list](BUILDS.md) of available feature and sensors.
## Changelog v14.1.0.2 ## Changelog v14.1.0.3
### Added ### Added
- Support for QMP6988 temperature and pressure sensor - Support for QMP6988 temperature and pressure sensor
- Support for Sonoff WTS01 temperature sensor using SerialBridge in ``SSerialMode 3`` - 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) - 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 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 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) - 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) - 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 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) - 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 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) - 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) - Matter refactoring of bridged devices [#21575](https://github.com/arendst/Tasmota/issues/21575)

View File

@ -22,6 +22,6 @@
#define TASMOTA_SHA_SHORT // Filled by Github sed #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_ #endif // _TASMOTA_VERSION_H_

View File

@ -690,6 +690,7 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source)
// state 2 = POWER_TOGGLE = Toggle relay // state 2 = POWER_TOGGLE = Toggle relay
// state 3 = POWER_BLINK = Blink relay // state 3 = POWER_BLINK = Blink relay
// state 4 = POWER_BLINK_STOP = Stop blinking 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 8 = POWER_OFF_NO_STATE = Relay Off and no publishPowerState
// state 9 = POWER_ON_NO_STATE = Relay On 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 // 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 #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; bool publish_power = true;
if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) { if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) {
state &= 3; // POWER_OFF, POWER_ON or POWER_TOGGLE 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; 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); AddLog(LOG_LEVEL_INFO, PSTR("CMD: Power%d is LOCKED"), device);
state = POWER_SHOW_STATE; // Only show state. Make no change state = POWER_SHOW_STATE; // Only show state. Make no change
} }

View File

@ -107,6 +107,23 @@ typedef union {
}; };
} tEnergyBitfield; } 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 { typedef struct {
uint32_t crc32; // To detect file changes uint32_t crc32; // To detect file changes
uint16_t version; // To detect driver function changes uint16_t version; // To detect driver function changes
@ -134,6 +151,7 @@ typedef struct {
uint16_t tariff[4][2]; uint16_t tariff[4][2];
tEnergyUsage energy_usage; tEnergyUsage energy_usage;
tPhase phase[ENERGY_MAX_PHASES];
} tEnergySettings; } tEnergySettings;
typedef struct { typedef struct {
@ -168,8 +186,8 @@ typedef struct {
float daily_sum_export_balanced; // 123.123 kWh float daily_sum_export_balanced; // 123.123 kWh
uint16_t power_history[ENERGY_MAX_PHASES][3]; uint16_t power_history[ENERGY_MAX_PHASES][3];
uint16_t mplh_counter; uint16_t mplh_counter[ENERGY_MAX_PHASES];
uint16_t mplw_counter; uint16_t mplw_counter[ENERGY_MAX_PHASES];
uint8_t data_valid[ENERGY_MAX_PHASES]; uint8_t data_valid[ENERGY_MAX_PHASES];
uint8_t phase_count; // Number of phases active uint8_t phase_count; // Number of phases active
@ -177,8 +195,9 @@ typedef struct {
uint8_t command_code; uint8_t command_code;
uint8_t power_steady_counter; // Allow for power on stabilization uint8_t power_steady_counter; // Allow for power on stabilization
uint8_t margin_stable; uint8_t margin_stable;
uint8_t mplr_counter; uint8_t mplr_counter[ENERGY_MAX_PHASES];
uint8_t max_energy_state; uint8_t max_energy_state[ENERGY_MAX_PHASES];
uint8_t hour;
uint8_t gui_indirect[ENERGY_MAX_PHASES]; uint8_t gui_indirect[ENERGY_MAX_PHASES];
uint8_t gui_rotate; uint8_t gui_rotate;
@ -195,12 +214,12 @@ typedef struct {
bool type_dc; bool type_dc;
bool power_on; bool power_on;
bool min_power_flag; bool min_power_flag[ENERGY_MAX_PHASES];
bool max_power_flag; bool max_power_flag[ENERGY_MAX_PHASES];
bool min_voltage_flag; bool min_voltage_flag[ENERGY_MAX_PHASES];
bool max_voltage_flag; bool max_voltage_flag[ENERGY_MAX_PHASES];
bool min_current_flag; bool min_current_flag[ENERGY_MAX_PHASES];
bool max_current_flag; bool max_current_flag[ENERGY_MAX_PHASES];
} tEnergy; } tEnergy;
tEnergy *Energy = nullptr; tEnergy *Energy = nullptr;
@ -286,10 +305,26 @@ void EnergySettingsLoad(bool erase) {
Energy->Settings.voltage_calibration[i] = Settings->energy_voltage_calibration;; Energy->Settings.voltage_calibration[i] = Settings->energy_voltage_calibration;;
Energy->Settings.current_calibration[i] = Settings->energy_current_calibration;; Energy->Settings.current_calibration[i] = Settings->energy_current_calibration;;
Energy->Settings.frequency_calibration[i] = Settings->energy_frequency_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.power_calibration[1] = Settings->energy_power_calibration2;
Energy->Settings.voltage_calibration[1] = Settings->energy_voltage_calibration2; Energy->Settings.voltage_calibration[1] = Settings->energy_voltage_calibration2;
Energy->Settings.current_calibration[1] = Settings->energy_current_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[0] = 0;
RtcEnergySettings.energy_total_kWh[1] = 0; RtcEnergySettings.energy_total_kWh[1] = 0;
@ -654,11 +689,19 @@ void Energy200ms(void) {
} }
EnergyUpdateToday(); EnergyUpdateToday();
} }
if (midnight) { 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 )) { if (RtcTime.hour != Energy->hour) {
Energy->max_energy_state = 0; 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)); ResponseAppend_P(PSTR("\"" D_CMND_POWERDELTA "\":%s"), EnergyFmt(power_diff_f, 0));
} }
uint16_t energy_power_u = (uint16_t)(Energy->active_power[0]); uint16_t energy_power_u;
if (Energy->power_on) {
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);
bool flag; bool flag;
if (EnergyMargin(false, Settings->energy_min_power, energy_power_u, flag, Energy->min_power_flag)) { for (uint32_t phase = 0; phase < Energy->phase_count; phase++) {
ResponseAppend_P(PSTR("%s\"" D_CMND_POWERLOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); energy_power_u = (uint16_t)(Energy->active_power[phase]);
jsonflg = true; uint16_t energy_voltage_u = (uint16_t)(Energy->voltage[phase]);
} uint16_t energy_current_u = (uint16_t)(Energy->current[phase] * 1000);
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
}
// Max Power DEBUG_DRIVER_LOG(PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u);
if (Settings->energy_max_power_limit) {
if (Energy->active_power[0] > Settings->energy_max_power_limit) { if (Energy->Settings.phase[phase].min_power) {
if (!Energy->mplh_counter) { if (EnergyMargin(false, Energy->Settings.phase[phase].min_power, energy_power_u, flag, Energy->min_power_flag[phase])) {
Energy->mplh_counter = Settings->energy_max_power_limit_hold; ResponseAppend_P(PSTR("%s\"" D_CMND_POWERLOW "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag));
} else { jsonflg = true;
Energy->mplh_counter--; }
if (!Energy->mplh_counter) { }
ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHED "\":%d}"), energy_power_u); if (Energy->Settings.phase[phase].max_power) {
MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING); if (EnergyMargin(true, Energy->Settings.phase[phase].max_power, energy_power_u, flag, Energy->max_power_flag[phase])) {
EnergyMqttShow(); ResponseAppend_P(PSTR("%s\"" D_CMND_POWERHIGH "%d\":\"%s\""), (jsonflg)?",":"", phase +1, GetStateText(flag));
SetAllPower(POWER_OFF_FORCE, SRC_MAXPOWER); jsonflg = true;
if (!Energy->mplr_counter) { }
Energy->mplr_counter = Settings->param[P_MAX_POWER_RETRY] +1; // SetOption33 - Max Power Retry count }
} if (Energy->Settings.phase[phase].min_voltage) {
Energy->mplw_counter = Settings->energy_max_power_limit_window; 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)) { if (jsonflg) {
Energy->mplh_counter = 0; ResponseJsonEndEnd();
Energy->mplr_counter = 0; MqttPublishTele(PSTR(D_RSLT_MARGINS));
Energy->mplw_counter = 0; EnergyMqttShow();
Energy->margin_stable = 3; // Allow 2 seconds to stabilize before reporting
} }
if (!TasmotaGlobal.power) { }
if (Energy->mplw_counter) {
Energy->mplw_counter--; // Max Power
} else { bool set_all_power = true; // Use all power control for backward compatibility
if (Energy->mplr_counter) { for (uint32_t phase = 1; phase < Energy->phase_count; phase++) {
Energy->mplr_counter--; if (Energy->Settings.phase[phase].max_power_limit) {
if (Energy->mplr_counter) { set_all_power = false; // If any other power limit is set use new selected power control
ResponseTime_P(PSTR(",\"" D_JSON_POWERMONITOR "\":\"%s\"}"), GetStateText(1)); break;
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_JSON_POWERMONITOR)); }
RestorePower(true, SRC_MAXPOWER); }
} else { for (uint32_t phase = 0; phase < Energy->phase_count; phase++) {
ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0)); 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); MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING);
EnergyMqttShow(); 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 // Max Energy
if (Settings->energy_max_energy) { set_all_power = true; // Use all power control for backward compatibility
uint16_t energy_daily_u = (uint16_t)(Energy->daily_sum * 1000); for (uint32_t phase = 1; phase < Energy->phase_count; phase++) {
if (!Energy->max_energy_state && (RtcTime.hour == Settings->energy_max_energy_start)) { if (Energy->Settings.phase[phase].max_energy) {
Energy->max_energy_state = 1; set_all_power = false; // If any other max energy is set use new selected power control
ResponseTime_P(PSTR(",\"" D_JSON_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1)); break;
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);
} }
} }
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(); EnergyFmtFree();
} }
@ -1251,13 +1354,34 @@ void CmndEnergyConfig(void) {
void EnergyMarginStatus(void) { void EnergyMarginStatus(void) {
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":[")); 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("%s%d"), (i>0)?",":"", Energy->Settings.power_delta[i]);
} }
ResponseAppend_P(PSTR("],\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\"" ResponseAppend_P(PSTR("],\"" D_CMND_POWERLOW "\":["));
D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"), for (uint32_t i = 0; i < Energy->phase_count; i++) {
Settings->energy_min_power, Settings->energy_max_power, ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Energy->Settings.phase[i].min_power);
Settings->energy_min_voltage, Settings->energy_max_voltage, Settings->energy_min_current, Settings->energy_max_current); }
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) { void CmndPowerDelta(void) {
@ -1270,66 +1394,84 @@ void CmndPowerDelta(void) {
} }
void CmndPowerLow(void) { void CmndPowerLow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_min_power = XdrvMailbox.payload; 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) { void CmndPowerHigh(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_power = XdrvMailbox.payload; 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) { void CmndVoltageLow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_min_voltage = XdrvMailbox.payload; 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) { void CmndVoltageHigh(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_voltage = XdrvMailbox.payload; 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) { void CmndCurrentLow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_min_current = XdrvMailbox.payload; 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) { void CmndCurrentHigh(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_current = XdrvMailbox.payload; 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) { void CmndMaxPower(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_power_limit = XdrvMailbox.payload; 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) { void CmndMaxPowerHold(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload; 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) { void CmndMaxPowerWindow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload; 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) { void CmndSafePower(void) {
@ -1354,18 +1496,22 @@ void CmndSafePowerWindow(void) {
} }
void CmndMaxEnergy(void) { void CmndMaxEnergy(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_energy = XdrvMailbox.payload; if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) {
Energy->max_energy_state = 3; 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) { void CmndMaxEnergyStart(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) {
Settings->energy_max_energy_start = XdrvMailbox.payload; 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);
} }
/********************************************************************************************/ /********************************************************************************************/