/* xdrv_03_energy.ino - Energy sensor support for Sonoff-Tasmota Copyright (C) 2019 Theo Arends This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef USE_ENERGY_SENSOR /*********************************************************************************************\ * Energy \*********************************************************************************************/ #define XDRV_03 3 #define XSNS_03 3 //#define USE_ENERGY_MARGIN_DETECTION // #define USE_ENERGY_POWER_LIMIT #define ENERGY_NONE 0 #define ENERGY_WATCHDOG 4 // Allow up to 4 seconds before deciding no valid data present #include #define D_CMND_POWERCAL "PowerCal" #define D_CMND_VOLTAGECAL "VoltageCal" #define D_CMND_CURRENTCAL "CurrentCal" enum EnergyCommands { CMND_POWERCAL, CMND_VOLTAGECAL, CMND_CURRENTCAL, CMND_POWERSET, CMND_VOLTAGESET, CMND_CURRENTSET, CMND_FREQUENCYSET }; const char kEnergyCommands[] PROGMEM = D_CMND_POWERCAL "|" D_CMND_VOLTAGECAL "|" D_CMND_CURRENTCAL "|" D_CMND_POWERSET "|" D_CMND_VOLTAGESET "|" D_CMND_CURRENTSET "|" D_CMND_FREQUENCYSET "|" #ifdef USE_ENERGY_MARGIN_DETECTION D_CMND_POWERDELTA "|" D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|" #ifdef USE_ENERGY_POWER_LIMIT D_CMND_MAXENERGY "|" D_CMND_MAXENERGYSTART "|" D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|" D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW "|" #endif // USE_ENERGY_POWER_LIMIT #endif // USE_ENERGY_MARGIN_DETECTION D_CMND_ENERGYRESET ; void (* const EnergyCommand[])(void) PROGMEM = { &CmndPowerCal, &CmndVoltageCal, &CmndCurrentCal, &CmndPowerSet, &CmndVoltageSet, &CmndCurrentSet, &CmndFrequencySet, #ifdef USE_ENERGY_MARGIN_DETECTION &CmndPowerDelta, &CmndPowerLow, &CmndPowerHigh, &CmndVoltageLow, &CmndVoltageHigh, &CmndCurrentLow, &CmndCurrentHigh, #ifdef USE_ENERGY_POWER_LIMIT &CmndMaxEnergy, &CmndMaxEnergyStart, &CmndMaxPower, &CmndMaxPowerHold, &CmndMaxPowerWindow, &CmndSafePower, &CmndSafePowerHold, &CmndSafePowerWindow, #endif // USE_ENERGY_POWER_LIMIT #endif // USE_ENERGY_MARGIN_DETECTION &CmndEnergyReset }; float energy_voltage = 0; // 123.1 V float energy_current = 0; // 123.123 A float energy_active_power = 0; // 123.1 W float energy_apparent_power = NAN; // 123.1 VA float energy_reactive_power = NAN; // 123.1 VAr float energy_power_factor = NAN; // 0.12 float energy_frequency = NAN; // 123.1 Hz float energy_start = 0; // 12345.12345 kWh total previous float energy_daily = 0; // 123.123 kWh float energy_total = 0; // 12345.12345 kWh unsigned long energy_kWhtoday_delta = 0; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to energy_kWhtoday (HLW and CSE only) unsigned long energy_kWhtoday; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily unsigned long energy_period = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily uint8_t energy_command_code = 0; uint8_t energy_data_valid = 0; bool energy_voltage_available = true; // Enable if voltage is measured bool energy_current_available = true; // Enable if current is measured bool energy_type_dc = false; bool energy_power_on = true; #ifdef USE_ENERGY_MARGIN_DETECTION float energy_power_last[3] = { 0 }; uint8_t energy_power_delta = 0; bool energy_min_power_flag = false; bool energy_max_power_flag = false; bool energy_min_voltage_flag = false; bool energy_max_voltage_flag = false; bool energy_min_current_flag = false; bool energy_max_current_flag = false; uint8_t energy_power_steady_cntr = 8; // Allow for power on stabilization #ifdef USE_ENERGY_POWER_LIMIT uint16_t energy_mplh_counter = 0; uint16_t energy_mplw_counter = 0; uint8_t energy_mplr_counter = 0; uint8_t energy_max_energy_state = 0; #endif // USE_ENERGY_POWER_LIMIT #endif // USE_ENERGY_MARGIN_DETECTION uint8_t energy_fifth_second = 0; Ticker ticker_energy; /********************************************************************************************/ void EnergyUpdateToday(void) { if (energy_kWhtoday_delta > 1000) { unsigned long delta = energy_kWhtoday_delta / 1000; energy_kWhtoday_delta -= (delta * 1000); energy_kWhtoday += delta; } RtcSettings.energy_kWhtoday = energy_kWhtoday; energy_daily = (float)energy_kWhtoday / 100000; energy_total = (float)(RtcSettings.energy_kWhtotal + energy_kWhtoday) / 100000; } /*********************************************************************************************/ void Energy200ms(void) { energy_power_on = (power != 0) | Settings.flag.no_power_on_check; energy_fifth_second++; if (5 == energy_fifth_second) { energy_fifth_second = 0; XnrgCall(FUNC_ENERGY_EVERY_SECOND); if (RtcTime.valid) { if (LocalTime() == Midnight()) { Settings.energy_kWhyesterday = energy_kWhtoday; Settings.energy_kWhtotal += energy_kWhtoday; RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal; energy_kWhtoday = 0; energy_kWhtoday_delta = 0; energy_period = energy_kWhtoday; EnergyUpdateToday(); #if defined(USE_ENERGY_MARGIN_DETECTION) && defined(USE_ENERGY_POWER_LIMIT) energy_max_energy_state = 3; #endif // USE_ENERGY_POWER_LIMIT } #if defined(USE_ENERGY_MARGIN_DETECTION) && defined(USE_ENERGY_POWER_LIMIT) if ((RtcTime.hour == Settings.energy_max_energy_start) && (3 == energy_max_energy_state)) { energy_max_energy_state = 0; } #endif // USE_ENERGY_POWER_LIMIT } } XnrgCall(FUNC_EVERY_200_MSECOND); } void EnergySaveState(void) { Settings.energy_kWhdoy = (RtcTime.valid) ? RtcTime.day_of_year : 0; Settings.energy_kWhtoday = energy_kWhtoday; RtcSettings.energy_kWhtoday = energy_kWhtoday; Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; } #ifdef USE_ENERGY_MARGIN_DETECTION bool EnergyMargin(bool type, uint16_t margin, uint16_t value, bool &flag, bool &save_flag) { bool 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 EnergyMarginCheck(void) { uint16_t energy_daily_u = 0; uint16_t energy_power_u = 0; uint16_t energy_voltage_u = 0; uint16_t energy_current_u = 0; bool flag; bool jsonflg; if (energy_power_steady_cntr) { energy_power_steady_cntr--; return; } if (Settings.energy_power_delta) { float delta = abs(energy_power_last[0] - energy_active_power); // Any delta compared to minimal delta float min_power = (energy_power_last[0] > energy_active_power) ? energy_active_power : energy_power_last[0]; if (((delta / min_power) * 100) > Settings.energy_power_delta) { energy_power_delta = 1; energy_power_last[1] = energy_active_power; // We only want one report so reset history energy_power_last[2] = energy_active_power; } } energy_power_last[0] = energy_power_last[1]; // Shift in history every second allowing power changes to settle for up to three seconds energy_power_last[1] = energy_power_last[2]; energy_power_last[2] = energy_active_power; 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)) { energy_power_u = (uint16_t)(energy_active_power); energy_voltage_u = (uint16_t)(energy_voltage); energy_current_u = (uint16_t)(energy_current * 1000); DEBUG_DRIVER_LOG(PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u); Response_P(PSTR("{")); jsonflg = false; 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%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%s\"" D_CMND_CURRENTHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag)); jsonflg = true; } if (jsonflg) { ResponseJsonEnd(); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_MARGINS), MQTT_TELE_RETAIN); EnergyMqttShow(); } } #ifdef USE_ENERGY_POWER_LIMIT // Max Power if (Settings.energy_max_power_limit) { if (energy_active_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) { Response_P(PSTR("{\"" D_JSON_MAXPOWERREACHED "\":\"%d%s\"}"), energy_power_u, (Settings.flag.value_units) ? " " D_UNIT_WATT : ""); MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING); EnergyMqttShow(); ExecuteCommandPower(1, POWER_OFF, SRC_MAXPOWER); 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) { Response_P(PSTR("{\"" D_JSON_POWERMONITOR "\":\"%s\"}"), GetStateText(1)); MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_POWERMONITOR)); ExecuteCommandPower(1, POWER_ON, SRC_MAXPOWER); } else { Response_P(PSTR("{\"" D_JSON_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0)); MqttPublishPrefixTopic_P(STAT, 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; Response_P(PSTR("{\"" D_JSON_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1)); MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_ENERGYMONITOR)); ExecuteCommandPower(1, POWER_ON, SRC_MAXENERGY); } 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); Response_P(PSTR("{\"" D_JSON_MAXENERGYREACHED "\":\"%s%s\"}"), mqtt_data, (Settings.flag.value_units) ? " " D_UNIT_KILOWATTHOUR : ""); MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING); EnergyMqttShow(); ExecuteCommandPower(1, POWER_OFF, SRC_MAXENERGY); } } #endif // USE_ENERGY_POWER_LIMIT if (energy_power_delta) { EnergyMqttShow(); } } void EnergyMqttShow(void) { // {"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}} ResponseBeginTime(); int tele_period_save = tele_period; tele_period = 2; EnergyShow(true); tele_period = tele_period_save; ResponseJsonEnd(); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); energy_power_delta = 0; } #endif // USE_ENERGY_MARGIN_DETECTION void EnergyOverTempCheck() { if (global_update) { if (power && (global_temperature != 9999) && (global_temperature > Settings.param[P_OVER_TEMP])) { // Device overtemp, turn off relays SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP); } } if (energy_data_valid <= ENERGY_WATCHDOG) { energy_data_valid++; if (energy_data_valid > ENERGY_WATCHDOG) { // Reset energy registers energy_voltage = 0; energy_current = 0; energy_active_power = 0; if (!isnan(energy_frequency)) { energy_frequency = 0; } if (!isnan(energy_power_factor)) { energy_power_factor = 0; } energy_start = 0; } } } /*********************************************************************************************\ * Commands \*********************************************************************************************/ void EnergyCommandResponse(uint32_t nvalue, uint32_t unit) { if (UNIT_MILLISECOND == unit) { snprintf_P(XdrvMailbox.command, CMDSZ, PSTR("%sCal"), XdrvMailbox.command); unit = UNIT_MICROSECOND; } if (Settings.flag.value_units) { char sunit[CMDSZ]; Response_P(S_JSON_COMMAND_LVALUE_SPACE_UNIT, XdrvMailbox.command, nvalue, GetTextIndexed(sunit, sizeof(sunit), unit, kUnitNames)); } else { Response_P(S_JSON_COMMAND_LVALUE, XdrvMailbox.command, nvalue); } } void CmndEnergyReset(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) { char *p; unsigned long lnum = strtoul(XdrvMailbox.data, &p, 10); if (p != XdrvMailbox.data) { switch (XdrvMailbox.index) { case 1: energy_kWhtoday = lnum *100; energy_kWhtoday_delta = 0; energy_period = energy_kWhtoday; Settings.energy_kWhtoday = energy_kWhtoday; RtcSettings.energy_kWhtoday = energy_kWhtoday; energy_daily = (float)energy_kWhtoday / 100000; if (!RtcSettings.energy_kWhtotal && !energy_kWhtoday) { Settings.energy_kWhtotal_time = LocalTime(); } break; case 2: Settings.energy_kWhyesterday = lnum *100; break; case 3: RtcSettings.energy_kWhtotal = lnum *100; Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal; energy_total = (float)(RtcSettings.energy_kWhtotal + energy_kWhtoday) / 100000; Settings.energy_kWhtotal_time = (!energy_kWhtoday) ? LocalTime() : Midnight(); break; } } char energy_total_chr[33]; dtostrfd(energy_total, Settings.flag2.energy_resolution, energy_total_chr); char energy_daily_chr[33]; dtostrfd(energy_daily, Settings.flag2.energy_resolution, energy_daily_chr); char energy_yesterday_chr[33]; dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr); Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s}}"), XdrvMailbox.command, energy_total_chr, energy_yesterday_chr, energy_daily_chr); } } void CmndPowerCal(void) { energy_command_code = CMND_POWERCAL; if (XnrgCall(FUNC_COMMAND)) { // microseconds if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) { Settings.energy_power_calibration = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_power_calibration, UNIT_MICROSECOND); } } void CmndVoltageCal(void) { energy_command_code = CMND_VOLTAGECAL; if (XnrgCall(FUNC_COMMAND)) { // microseconds if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) { Settings.energy_voltage_calibration = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_voltage_calibration, UNIT_MICROSECOND); } } void CmndCurrentCal(void) { energy_command_code = CMND_CURRENTCAL; if (XnrgCall(FUNC_COMMAND)) { // microseconds if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) { Settings.energy_current_calibration = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_current_calibration, UNIT_MICROSECOND); } } void CmndPowerSet(void) { energy_command_code = CMND_POWERSET; if (XnrgCall(FUNC_COMMAND)) { // Watt EnergyCommandResponse(Settings.energy_power_calibration, UNIT_MILLISECOND); } } void CmndVoltageSet(void) { energy_command_code = CMND_VOLTAGESET; if (XnrgCall(FUNC_COMMAND)) { // Volt EnergyCommandResponse(Settings.energy_voltage_calibration, UNIT_MILLISECOND); } } void CmndCurrentSet(void) { energy_command_code = CMND_CURRENTSET; if (XnrgCall(FUNC_COMMAND)) { // milliAmpere EnergyCommandResponse(Settings.energy_current_calibration, UNIT_MILLISECOND); } } void CmndFrequencySet(void) { energy_command_code = CMND_FREQUENCYSET; if (XnrgCall(FUNC_COMMAND)) { // Hz EnergyCommandResponse(Settings.energy_frequency_calibration, UNIT_MILLISECOND); } } #ifdef USE_ENERGY_MARGIN_DETECTION void CmndPowerDelta(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 101)) { Settings.energy_power_delta = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_power_delta, UNIT_PERCENTAGE); } void CmndPowerLow(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_min_power = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_min_power, UNIT_WATT); } void CmndPowerHigh(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_max_power = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_power, UNIT_WATT); } void CmndVoltageLow(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) { Settings.energy_min_voltage = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_min_voltage, UNIT_VOLT); } void CmndVoltageHigh(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) { Settings.energy_max_voltage = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_voltage, UNIT_VOLT); } void CmndCurrentLow(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) { Settings.energy_min_current = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_min_current, UNIT_MILLIAMPERE); } void CmndCurrentHigh(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) { Settings.energy_max_current = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_current, UNIT_MILLIAMPERE); } #ifdef USE_ENERGY_POWER_LIMIT void CmndMaxPower(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_max_power_limit = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_power_limit, UNIT_WATT); } void CmndMaxPowerHold(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_power_limit_hold, UNIT_SECOND); } void CmndMaxPowerWindow(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_power_limit_window, UNIT_SECOND); } void CmndSafePower(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_max_power_safe_limit = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_power_safe_limit, UNIT_WATT); } void CmndSafePowerHold(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_max_power_safe_limit_hold = (1 == XdrvMailbox.payload) ? SAFE_POWER_HOLD : XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_power_safe_limit_hold, UNIT_SECOND); } void CmndSafePowerWindow(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 1440)) { Settings.energy_max_power_safe_limit_window = (1 == XdrvMailbox.payload) ? SAFE_POWER_WINDOW : XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_power_safe_limit_window, UNIT_MINUTE); } void CmndMaxEnergy(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.energy_max_energy = XdrvMailbox.payload; energy_max_energy_state = 3; } EnergyCommandResponse(Settings.energy_max_energy, UNIT_WATTHOUR); } void CmndMaxEnergyStart(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) { Settings.energy_max_energy_start = XdrvMailbox.payload; } EnergyCommandResponse(Settings.energy_max_energy_start, UNIT_HOUR); } #endif // USE_ENERGY_POWER_LIMIT #endif // USE_ENERGY_MARGIN_DETECTION void EnergyDrvInit(void) { energy_flg = ENERGY_NONE; XnrgCall(FUNC_PRE_INIT); } void EnergySnsInit(void) { XnrgCall(FUNC_INIT); if (energy_flg) { energy_kWhtoday = (RtcSettingsValid()) ? RtcSettings.energy_kWhtoday : (RtcTime.day_of_year == Settings.energy_kWhdoy) ? Settings.energy_kWhtoday : 0; energy_kWhtoday_delta = 0; energy_period = energy_kWhtoday; EnergyUpdateToday(); ticker_energy.attach_ms(200, Energy200ms); } } #ifdef USE_WEBSERVER const char HTTP_ENERGY_SNS1[] PROGMEM = "{s}" D_POWERUSAGE_APPARENT "{m}%s " D_UNIT_VA "{e}" "{s}" D_POWERUSAGE_REACTIVE "{m}%s " D_UNIT_VAR "{e}" "{s}" D_POWER_FACTOR "{m}%s{e}"; const char HTTP_ENERGY_SNS2[] PROGMEM = "{s}" D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}" "{s}" D_ENERGY_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(bool json) { char speriod[20]; // char sfrequency[20]; bool show_energy_period = (0 == tele_period); float power_factor = energy_power_factor; char apparent_power_chr[33]; char reactive_power_chr[33]; char power_factor_chr[33]; char frequency_chr[33]; if (!energy_type_dc) { if (energy_current_available && energy_voltage_available) { float apparent_power = energy_apparent_power; if (isnan(apparent_power)) { apparent_power = energy_voltage * energy_current; } if (apparent_power < energy_active_power) { // Should be impossible energy_active_power = apparent_power; } if (isnan(power_factor)) { power_factor = (energy_active_power && apparent_power) ? energy_active_power / apparent_power : 0; if (power_factor > 1) power_factor = 1; } float reactive_power = energy_reactive_power; if (isnan(reactive_power)) { reactive_power = 0; uint32_t difference = ((uint32_t)(apparent_power * 100) - (uint32_t)(energy_active_power * 100)) / 10; if ((energy_current > 0.005) && ((difference > 15) || (difference > (uint32_t)(apparent_power * 100 / 1000)))) { // calculating reactive power only if current is greater than 0.005A and // difference between active and apparent power is greater than 1.5W or 1% reactive_power = (float)(RoundSqrtInt((uint32_t)(apparent_power * apparent_power * 100) - (uint32_t)(energy_active_power * energy_active_power * 100))) / 10; } } dtostrfd(apparent_power, Settings.flag2.wattage_resolution, apparent_power_chr); dtostrfd(reactive_power, Settings.flag2.wattage_resolution, reactive_power_chr); dtostrfd(power_factor, 2, power_factor_chr); } if (!isnan(energy_frequency)) { dtostrfd(energy_frequency, Settings.flag2.frequency_resolution, frequency_chr); } } char voltage_chr[33]; dtostrfd(energy_voltage, Settings.flag2.voltage_resolution, voltage_chr); char current_chr[33]; dtostrfd(energy_current, Settings.flag2.current_resolution, current_chr); char active_power_chr[33]; dtostrfd(energy_active_power, Settings.flag2.wattage_resolution, active_power_chr); char energy_daily_chr[33]; dtostrfd(energy_daily, Settings.flag2.energy_resolution, energy_daily_chr); char energy_yesterday_chr[33]; dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr); char energy_total_chr[33]; dtostrfd(energy_total, Settings.flag2.energy_resolution, energy_total_chr); float energy = 0; char energy_period_chr[33]; if (show_energy_period) { if (energy_period) energy = (float)(energy_kWhtoday - energy_period) / 100; energy_period = energy_kWhtoday; dtostrfd(energy, Settings.flag2.wattage_resolution, energy_period_chr); snprintf_P(speriod, sizeof(speriod), PSTR(",\"" D_JSON_PERIOD "\":%s"), energy_period_chr); } if (json) { ResponseAppend_P(PSTR(",\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL_START_TIME "\":\"%s\",\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s%s,\"" D_JSON_POWERUSAGE "\":%s"), GetDateAndTime(DT_ENERGY).c_str(), energy_total_chr, energy_yesterday_chr, energy_daily_chr, (show_energy_period) ? speriod : "", active_power_chr); if (!energy_type_dc) { if (energy_current_available && energy_voltage_available) { ResponseAppend_P(PSTR(",\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_POWERFACTOR "\":%s"), apparent_power_chr, reactive_power_chr, power_factor_chr); } if (!isnan(energy_frequency)) { ResponseAppend_P(PSTR(",\"" D_JSON_FREQUENCY "\":%s"), frequency_chr); } } if (energy_voltage_available) { ResponseAppend_P(PSTR(",\"" D_JSON_VOLTAGE "\":%s"), voltage_chr); } if (energy_current_available) { ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), current_chr); } ResponseJsonEnd(); #ifdef USE_DOMOTICZ if (show_energy_period) { // Only send if telemetry dtostrfd(energy_total * 1000, 1, energy_total_chr); DomoticzSensorPowerEnergy((int)energy_active_power, energy_total_chr); // PowerUsage, EnergyToday if (energy_voltage_available) { DomoticzSensor(DZ_VOLTAGE, voltage_chr); // Voltage } if (energy_current_available) { DomoticzSensor(DZ_CURRENT, current_chr); // Current } } #endif // USE_DOMOTICZ #ifdef USE_KNX if (show_energy_period) { if (energy_voltage_available) { KnxSensor(KNX_ENERGY_VOLTAGE, energy_voltage); } if (energy_current_available) { KnxSensor(KNX_ENERGY_CURRENT, energy_current); } KnxSensor(KNX_ENERGY_POWER, energy_active_power); if (!energy_type_dc) { KnxSensor(KNX_ENERGY_POWERFACTOR, power_factor); } KnxSensor(KNX_ENERGY_DAILY, energy_daily); KnxSensor(KNX_ENERGY_TOTAL, energy_total); KnxSensor(KNX_ENERGY_START, energy_start); } #endif // USE_KNX #ifdef USE_WEBSERVER } else { if (energy_voltage_available) { WSContentSend_PD(PSTR("{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"), voltage_chr); } if (energy_current_available) { WSContentSend_PD(PSTR("{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"), current_chr); } WSContentSend_PD(PSTR("{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"), active_power_chr); if (!energy_type_dc) { if (energy_current_available && energy_voltage_available) { WSContentSend_PD(HTTP_ENERGY_SNS1, apparent_power_chr, reactive_power_chr, power_factor_chr); } if (!isnan(energy_frequency)) { WSContentSend_PD(PSTR("{s}" D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"), frequency_chr); } } WSContentSend_PD(HTTP_ENERGY_SNS2, energy_daily_chr, energy_yesterday_chr, energy_total_chr); #endif // USE_WEBSERVER } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv03(uint8_t function) { bool result = false; if (FUNC_PRE_INIT == function) { EnergyDrvInit(); } else if (energy_flg) { switch (function) { case FUNC_LOOP: XnrgCall(FUNC_LOOP); break; #ifdef USE_ENERGY_MARGIN_DETECTION case FUNC_SET_POWER: energy_power_steady_cntr = 2; break; #endif // USE_ENERGY_MARGIN_DETECTION case FUNC_SERIAL: result = XnrgCall(FUNC_SERIAL); break; case FUNC_COMMAND: result = DecodeCommand(kEnergyCommands, EnergyCommand); break; } } return result; } bool Xsns03(uint8_t function) { bool result = false; if (energy_flg) { switch (function) { case FUNC_INIT: EnergySnsInit(); break; case FUNC_EVERY_SECOND: #ifdef USE_ENERGY_MARGIN_DETECTION EnergyMarginCheck(); #endif // USE_ENERGY_MARGIN_DETECTION EnergyOverTempCheck(); break; case FUNC_JSON_APPEND: EnergyShow(true); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: EnergyShow(false); break; #endif // USE_WEBSERVER case FUNC_SAVE_BEFORE_RESTART: EnergySaveState(); break; } } return result; } #endif // USE_ENERGY_SENSOR