From 2529759974a01dabde9319db2749add9bf42ce63 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 25 Jan 2023 17:05:48 +0100 Subject: [PATCH] Prep energy for four+ phase support --- .../tasmota_xdrv_driver/xdrv_03_energy.ino | 50 +- .../xdrv_03_esp32_energy.ino | 1658 +++++++++++++++++ .../tasmota_xnrg_energy/xnrg_01_hlw8012.ino | 16 +- .../tasmota_xnrg_energy/xnrg_02_cse7766.ino | 20 +- .../tasmota_xnrg_energy/xnrg_04_mcp39f501.ino | 16 +- .../tasmota_xnrg_energy/xnrg_07_ade7953.ino | 20 +- .../tasmota_xnrg_energy/xnrg_14_bl09xx.ino | 24 +- .../tasmota_xnrg_energy/xnrg_19_cse7761.ino | 26 +- .../tasmota_xnrg_energy/xnrg_22_bl6523.ino | 21 +- tasmota/tasmota_xnrg_energy/xnrg_30_dummy.ino | 26 +- 10 files changed, 1769 insertions(+), 108 deletions(-) create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino diff --git a/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino b/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino index 14d7279d8..94c5feb89 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino @@ -17,10 +17,10 @@ along with this program. If not, see . */ -//#ifdef ESP8266 +#ifdef ESP8266 #ifdef USE_ENERGY_SENSOR /*********************************************************************************************\ - * Energy + * Energy for ESP8266 \*********************************************************************************************/ #define XDRV_03 3 @@ -31,6 +31,8 @@ #define ENERGY_NONE 0 #define ENERGY_WATCHDOG 4 // Allow up to 4 seconds before deciding no valid data present + +#undef ENERGY_MAX_PHASES #define ENERGY_MAX_PHASES 3 #include @@ -860,7 +862,7 @@ void CmndTariff(void) { GetStateText(Settings->flag3.energy_weekend)); // CMND_TARIFF } -uint32_t EnergyGetCalibration(uint32_t chan, uint32_t cal_type) { +uint32_t EnergyGetCalibration(uint32_t cal_type, uint32_t chan = 0) { uint32_t channel = ((1 == chan) && (2 == Energy->phase_count)) ? 1 : 0; if (channel) { switch (cal_type) { @@ -878,32 +880,36 @@ uint32_t EnergyGetCalibration(uint32_t chan, uint32_t cal_type) { return Settings->energy_frequency_calibration; } +void EnergySetCalibration(uint32_t cal_type, uint32_t value, uint32_t chan = 0) { + uint32_t channel = ((1 == chan) && (2 == Energy->phase_count)) ? 1 : 0; + if (channel) { + switch (cal_type) { + case ENERGY_POWER_CALIBRATION: Settings->energy_power_calibration2 = value; return; + case ENERGY_VOLTAGE_CALIBRATION: Settings->energy_voltage_calibration2 = value; return; + case ENERGY_CURRENT_CALIBRATION: Settings->energy_current_calibration2 = value; return; + case ENERGY_FREQUENCY_CALIBRATION: Settings->energy_frequency_calibration = value; return; + } + } else { + switch (cal_type) { + case ENERGY_POWER_CALIBRATION: Settings->energy_power_calibration = value; return; + case ENERGY_VOLTAGE_CALIBRATION: Settings->energy_voltage_calibration = value; return; + case ENERGY_CURRENT_CALIBRATION: Settings->energy_current_calibration = value; return; + case ENERGY_FREQUENCY_CALIBRATION: Settings->energy_frequency_calibration = value; return; + } + } +} + void EnergyCommandCalSetResponse(uint32_t cal_type) { if (XdrvMailbox.payload > 99) { - uint32_t channel = ((2 == XdrvMailbox.index) && (2 == Energy->phase_count)) ? 1 : 0; - if (channel) { - switch (cal_type) { - case ENERGY_POWER_CALIBRATION: Settings->energy_power_calibration2 = XdrvMailbox.payload; break; - case ENERGY_VOLTAGE_CALIBRATION: Settings->energy_voltage_calibration2 = XdrvMailbox.payload; break; - case ENERGY_CURRENT_CALIBRATION: Settings->energy_current_calibration2 = XdrvMailbox.payload; break; - case ENERGY_FREQUENCY_CALIBRATION: Settings->energy_frequency_calibration = XdrvMailbox.payload; break; - } - } else { - switch (cal_type) { - case ENERGY_POWER_CALIBRATION: Settings->energy_power_calibration = XdrvMailbox.payload; break; - case ENERGY_VOLTAGE_CALIBRATION: Settings->energy_voltage_calibration = XdrvMailbox.payload; break; - case ENERGY_CURRENT_CALIBRATION: Settings->energy_current_calibration = XdrvMailbox.payload; break; - case ENERGY_FREQUENCY_CALIBRATION: Settings->energy_frequency_calibration = XdrvMailbox.payload; break; - } - } + EnergySetCalibration(cal_type, XdrvMailbox.payload, XdrvMailbox.index -1); } if (ENERGY_FREQUENCY_CALIBRATION == cal_type) { ResponseAppend_P(PSTR("%d}"), Settings->energy_frequency_calibration); } else { if (2 == Energy->phase_count) { - ResponseAppend_P(PSTR("[%d,%d]}"), EnergyGetCalibration(0, cal_type), EnergyGetCalibration(1, cal_type)); + ResponseAppend_P(PSTR("[%d,%d]}"), EnergyGetCalibration(cal_type), EnergyGetCalibration(cal_type, 1)); } else { - ResponseAppend_P(PSTR("%d}"), EnergyGetCalibration(0, cal_type)); + ResponseAppend_P(PSTR("%d}"), EnergyGetCalibration(cal_type)); } } } @@ -1494,4 +1500,4 @@ bool Xsns03(uint32_t function) } #endif // USE_ENERGY_SENSOR -//#endif // ESP8266 +#endif // ESP8266 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino b/tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino new file mode 100644 index 000000000..2db22d9b1 --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_03_esp32_energy.ino @@ -0,0 +1,1658 @@ +/* + xdrv_03_esp32_energy.ino - Energy sensor support for Tasmota + + Copyright (C) 2021 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 ESP32 +#ifdef USE_ENERGY_SENSOR +/*********************************************************************************************\ + * Energy for ESP32 +\*********************************************************************************************/ + +#define XDRV_03 3 +#define XSNS_03 3 + +#define ENERGY_NONE 0 +#define ENERGY_WATCHDOG 4 // Allow up to 4 seconds before deciding no valid data present + +#undef ENERGY_MAX_PHASES +#define ENERGY_MAX_PHASES 4 + +#define ENERGY_MAX_PHASES_FUTURE 8 + +#include + +#define D_CMND_POWERCAL "PowerCal" +#define D_CMND_VOLTAGECAL "VoltageCal" +#define D_CMND_CURRENTCAL "CurrentCal" +#define D_CMND_FREQUENCYCAL "FrequencyCal" +#define D_CMND_TARIFF "Tariff" +#define D_CMND_MODULEADDRESS "ModuleAddress" + +enum EnergyCalibration { + ENERGY_POWER_CALIBRATION, ENERGY_VOLTAGE_CALIBRATION, ENERGY_CURRENT_CALIBRATION, ENERGY_FREQUENCY_CALIBRATION }; + +enum EnergyCommands { + CMND_POWERCAL, CMND_VOLTAGECAL, CMND_CURRENTCAL, CMND_FREQUENCYCAL, + CMND_POWERSET, CMND_VOLTAGESET, CMND_CURRENTSET, CMND_FREQUENCYSET, CMND_MODULEADDRESS, CMND_ENERGYCONFIG }; + +const char kEnergyCommands[] PROGMEM = "|" // No prefix + D_CMND_POWERCAL "|" D_CMND_VOLTAGECAL "|" D_CMND_CURRENTCAL "|" D_CMND_FREQUENCYCAL "|" + D_CMND_POWERSET "|" D_CMND_VOLTAGESET "|" D_CMND_CURRENTSET "|" D_CMND_FREQUENCYSET "|" D_CMND_MODULEADDRESS "|" D_CMND_ENERGYCONFIG "|" + D_CMND_POWERDELTA "|" D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|" + D_CMND_MAXENERGY "|" D_CMND_MAXENERGYSTART "|" + D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|" + D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW "|" + D_CMND_ENERGYTODAY "|" D_CMND_ENERGYYESTERDAY "|" D_CMND_ENERGYTOTAL "|" D_CMND_ENERGYEXPORTACTIVE "|" D_CMND_ENERGYUSAGE "|" D_CMND_ENERGYEXPORT "|" D_CMND_TARIFF; + +void (* const EnergyCommand[])(void) PROGMEM = { + &CmndPowerCal, &CmndVoltageCal, &CmndCurrentCal, &CmndFrequencyCal, + &CmndPowerSet, &CmndVoltageSet, &CmndCurrentSet, &CmndFrequencySet, &CmndModuleAddress, &CmndEnergyConfig, + &CmndPowerDelta, &CmndPowerLow, &CmndPowerHigh, &CmndVoltageLow, &CmndVoltageHigh, &CmndCurrentLow, &CmndCurrentHigh, + &CmndMaxEnergy, &CmndMaxEnergyStart, + &CmndMaxPower, &CmndMaxPowerHold, &CmndMaxPowerWindow, + &CmndSafePower, &CmndSafePowerHold, &CmndSafePowerWindow, + &CmndEnergyToday, &CmndEnergyYesterday, &CmndEnergyTotal, &CmndEnergyExportActive, &CmndEnergyUsage, &CmndEnergyExport, &CmndTariff}; + +/********************************************************************************************/ + +typedef struct { + float usage_total_kWh[2]; + float return_total_kWh[2]; + float last_return_total_kWh; + float last_usage_total_kWh; +} tEnergyUsage; + +typedef struct { + uint32_t crc32; // To detect file changes + uint16_t version; // To detect driver function changes + uint16_t energy_kWhdoy; + uint32_t energy_kWhtotal_time; + uint32_t spare1; + uint32_t spare2; + uint32_t spare3; + uint32_t spare4; + uint32_t spare5; + + uint32_t power_calibration[ENERGY_MAX_PHASES_FUTURE]; + uint32_t voltage_calibration[ENERGY_MAX_PHASES_FUTURE]; + uint32_t current_calibration[ENERGY_MAX_PHASES_FUTURE]; + uint32_t frequency_calibration[ENERGY_MAX_PHASES_FUTURE]; + + uint16_t tariff[2][2]; + tEnergyUsage energy_usage; + + float energy_today_kWh[ENERGY_MAX_PHASES_FUTURE]; // Energy today in kWh - float allows up to 262143.99 kWh + float energy_yesterday_kWh[ENERGY_MAX_PHASES_FUTURE]; // Energy yesterday in kWh - float allows up to 262143.99 kWh + float energy_total_kWh[ENERGY_MAX_PHASES_FUTURE]; // Total energy in kWh - float allows up to 262143.99 kWh + float energy_export_kWh[ENERGY_MAX_PHASES_FUTURE]; + + uint16_t power_delta[ENERGY_MAX_PHASES_FUTURE]; // PowerDelta + + uint16_t min_power; // PowerLow + uint16_t max_power; // PowerHigh + uint16_t min_voltage; // VoltageLow + uint16_t max_voltage; // VoltageHigh + uint16_t min_current; // CurrentLow + uint16_t max_current; // CurrentHigh + + 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 +} tEnergySettings; + +typedef struct { + tEnergySettings Settings; + + // Global updated / accessed + float voltage[ENERGY_MAX_PHASES]; // 123.1 V + float current[ENERGY_MAX_PHASES]; // 123.123 A + float active_power[ENERGY_MAX_PHASES]; // 123.1 W + float apparent_power[ENERGY_MAX_PHASES]; // 123.1 VA + float reactive_power[ENERGY_MAX_PHASES]; // 123.1 VAr + float power_factor[ENERGY_MAX_PHASES]; // 0.12 + float frequency[ENERGY_MAX_PHASES]; // 123.1 Hz + float import_active[ENERGY_MAX_PHASES]; // 123.123 kWh + float export_active[ENERGY_MAX_PHASES]; // 123.123 kWh + float start_energy[ENERGY_MAX_PHASES]; // 12345.12345 kWh total previous + float total[ENERGY_MAX_PHASES]; // 12345.12345 kWh total energy + float daily_sum; // 123.123 kWh + float total_sum; // 12345.12345 kWh total energy + float yesterday_sum; // 123.123 kWh + + int32_t kWhtoday_delta[ENERGY_MAX_PHASES]; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to Energy->kWhtoday (HLW and CSE only) + int32_t kWhtoday[ENERGY_MAX_PHASES]; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy->daily + + // Local only + float daily_kWh[ENERGY_MAX_PHASES]; // 123.123 kWh + float energy_today_offset_kWh[ENERGY_MAX_PHASES]; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy->daily + float period_kWh[ENERGY_MAX_PHASES]; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy->daily + float daily_sum_import_balanced; // 123.123 kWh + float daily_sum_export_balanced; // 123.123 kWh + + uint8_t fifth_second; + uint8_t command_code; + uint8_t data_valid[ENERGY_MAX_PHASES]; + + uint8_t phase_count; // Number of phases active + bool voltage_common; // Use common voltage + bool frequency_common; // Use common frequency + bool use_overtemp; // Use global temperature as overtemp trigger on internal energy monitor hardware + bool kWhtoday_offset_init; + + bool voltage_available; // Enable if voltage is measured + bool current_available; // Enable if current is measured + bool local_energy_active_export; // Enable if support for storing energy_active + + bool type_dc; + bool power_on; + + uint16_t power_history[ENERGY_MAX_PHASES][3]; + uint8_t power_steady_counter; // Allow for power on stabilization + bool min_power_flag; + bool max_power_flag; + bool min_voltage_flag; + bool max_voltage_flag; + bool min_current_flag; + bool max_current_flag; + + uint16_t mplh_counter; + uint16_t mplw_counter; + uint8_t mplr_counter; + uint8_t max_energy_state; +} tEnergy; + +tEnergy *Energy = nullptr; + +Ticker ticker_energy; + +/*********************************************************************************************\ + * RTC Energy memory +\*********************************************************************************************/ + +const uint16_t RTC_ENERGY_MEM_VALID = 0xA55A; + +typedef struct { + uint16_t valid; + tEnergyUsage energy_usage; + float energy_today_kWh[ENERGY_MAX_PHASES_FUTURE]; + float energy_total_kWh[ENERGY_MAX_PHASES_FUTURE]; + float energy_export_kWh[ENERGY_MAX_PHASES_FUTURE]; +} tRtcEnergySettings; +tRtcEnergySettings RtcEnergySettings; +static RTC_NOINIT_ATTR tRtcEnergySettings RtcDataEnergySettings; + +uint32_t energy_rtc_settings_crc = 0; + +uint32_t EnergyGetRtcSettingsCrc(void) { + uint32_t crc = 0; + uint8_t *bytes = (uint8_t*)&RtcEnergySettings; + + for (uint32_t i = 0; i < sizeof(RtcEnergySettings); i++) { + crc += bytes[i]*(i+1); + } + return crc; +} + +void EnergyRtcSettingsSave(void) { + if (EnergyGetRtcSettingsCrc() != energy_rtc_settings_crc) { + + if (RTC_ENERGY_MEM_VALID != RtcEnergySettings.valid) { + memset(&RtcEnergySettings, 0, sizeof(RtcEnergySettings)); + RtcEnergySettings.valid = RTC_ENERGY_MEM_VALID; + RtcEnergySettings.energy_usage = Energy->Settings.energy_usage; + for (uint32_t i = 0; i < ENERGY_MAX_PHASES_FUTURE; i++) { + RtcEnergySettings.energy_today_kWh[i] = Energy->Settings.energy_today_kWh[i]; + RtcEnergySettings.energy_total_kWh[i] = Energy->Settings.energy_total_kWh[i]; + RtcEnergySettings.energy_export_kWh[i] = Energy->Settings.energy_export_kWh[i]; + } + } + +// AddLog(LOG_LEVEL_INFO, PSTR("DBG: energy_today_kWh[0] %3_f/%3_f"), RtcEnergySettings.energy_today_kWh[0], Energy->Settings.energy_today_kWh[0]); + + RtcDataEnergySettings = RtcEnergySettings; + energy_rtc_settings_crc = EnergyGetRtcSettingsCrc(); + } +} + +bool EnergyRtcSettingsLoad(void) { + RtcEnergySettings = RtcDataEnergySettings; + + bool read_valid = (RTC_ENERGY_MEM_VALID == RtcEnergySettings.valid); + if (!read_valid) { + EnergyRtcSettingsSave(); + } + return read_valid; +} + +bool EnergyRtcSettingsValid(void) { + return (RTC_ENERGY_MEM_VALID == RtcEnergySettings.valid); +} + +/*********************************************************************************************\ + * Driver Settings load and save using filesystem +\*********************************************************************************************/ + +const uint32_t XDRV_03_VERSION = 0x0101; // Latest driver version (See settings deltas below) + +void Xdrv03SettingsLoad(void) { + // *** Start init default values in case file is not found *** + memset(&Energy->Settings, 0x00, sizeof(tEnergySettings)); + Energy->Settings.version = XDRV_03_VERSION; + // Init any other parameter in struct + for (uint32_t i = 0; i < ENERGY_MAX_PHASES_FUTURE; i++) { + Energy->Settings.power_calibration[i] = HLW_PREF_PULSE; + Energy->Settings.voltage_calibration[i] = HLW_UREF_PULSE; + Energy->Settings.current_calibration[i] = HLW_IREF_PULSE; +// Energy->Settings.frequency_calibration[i] = 0; + } + Energy->Settings.max_power_limit_hold = MAX_POWER_HOLD; + Energy->Settings.max_power_limit_window = MAX_POWER_WINDOW; +// Energy->Settings.max_power_safe_limit = 0; // MaxSafePowerLimit + Energy->Settings.max_power_safe_limit_hold = SAFE_POWER_HOLD; + Energy->Settings.max_power_safe_limit_window = SAFE_POWER_WINDOW; +/* + RtcEnergySettings.energy_total_kWh[0] = 0; + RtcEnergySettings.energy_total_kWh[1] = 0; + RtcEnergySettings.energy_total_kWh[2] = 0; + memset((char*)&RtcEnergySettings.energy_usage, 0x00, sizeof(RtcEnergySettings.energy_usage)); +*/ + // *** End Init default values *** + +#ifndef USE_UFILESYS + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV03 Use defaults as file system not enabled")); +#else + // Try to load file /.drvset003 + char filename[20]; + // Use for drivers: + snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_03); + if (TfsLoadFile(filename, (uint8_t*)&Energy->Settings, sizeof(tEnergySettings))) { + if (Energy->Settings.version != XDRV_03_VERSION) { // Fix version dependent changes + + // *** Start fix possible setting deltas *** + + // *** End setting deltas *** + + // Set current version and save settings + Energy->Settings.version = XDRV_03_VERSION; + Xdrv03SettingsSave(); + } + AddLog(LOG_LEVEL_INFO, PSTR("CFG: XDRV03 loaded from file")); + } else { + // File system not ready: No flash space reserved for file system + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV03 Use defaults as file system not ready or file not found")); + } +#endif // USE_UFILESYS +} + +void Xdrv03SettingsSave(void) { +#ifdef USE_UFILESYS + // Called from FUNC_SAVE_SETTINGS every SaveData second and at restart + uint32_t crc32 = GetCfgCrc32((uint8_t*)&Energy->Settings +4, sizeof(tEnergySettings) -4); // Skip crc32 + if (crc32 != Energy->Settings.crc32) { + // Try to save file /.drvset003 + Energy->Settings.crc32 = crc32; + + char filename[20]; + // Use for drivers: + snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_03); + if (TfsSaveFile(filename, (const uint8_t*)&Energy->Settings, sizeof(tEnergySettings))) { + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV03 saved to file")); + } else { + // File system not ready: No flash space reserved for file system + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV03 ERROR File system not ready or unable to save file")); + } + } +#endif // USE_UFILESYS +} + +/********************************************************************************************/ + +char* EnergyFormat(char* result, float* input, uint32_t resolution, uint32_t single = 0); +char* EnergyFormat(char* result, float* input, uint32_t resolution, uint32_t single) { + // single = 0 - Energy->phase_count - xx or [xx,xx] or [xx,xx,xx] + // single = 1 - Energy->voltage_common or Energy->frequency_common - xx + // single = 2 - Sum of Energy->phase_count if SO129 0 - xx or if SO129 1 - [xx,xx,xx] + // single = 5 - single &0x03 = 1 - xx + // single = 6 - single &0x03 = 2 - [xx,xx] - used by tarriff + // single = 7 - single &0x03 = 3 - [xx,xx,xx] + uint32_t index = (single > 3) ? single &0x03 : (0 == single) ? Energy->phase_count : 1; // 1,2,3 + if (single > 2) { single = 0; } // 0,1,2 + float input_sum = 0.0f; + if (single > 1) { + if (!Settings->flag5.energy_phase) { // SetOption129 - (Energy) Show phase information + for (uint32_t i = 0; i < Energy->phase_count; i++) { + if (!isnan(input[i])) { + input_sum += input[i]; + } + } + input = &input_sum; + } else { + index = Energy->phase_count; + } + } + result[0] = '\0'; + for (uint32_t i = 0; i < index; i++) { + ext_snprintf_P(result, TOPSZ, PSTR("%s%s%*_f%s"), result, (0==i)?(1==index)?"":"[":",", resolution, &input[i], (index-1==i)?(1==index)?"":"]":""); + } + return result; +} + +#ifdef USE_WEBSERVER +char* WebEnergyFormat(char* result, float* input, uint32_t resolution, uint32_t single = 0); +char* WebEnergyFormat(char* result, float* input, uint32_t resolution, uint32_t single) { + // single = 0 - Energy->phase_count - xx / xx / xx or multi column + // single = 1 - Energy->voltage_common or Energy->frequency_common - xx or single column using colspan (if needed) + // single = 2 - Sum of Energy->phase_count if SO129 0 - xx or single column using colspan (if needed) or if SO129 1 - xx / xx / xx or multi column + float input_sum = 0.0f; + if (single > 1) { // Sum and/or Single column + if (!Settings->flag5.energy_phase) { // SetOption129 - (Energy) Show phase information + for (uint32_t i = 0; i < Energy->phase_count; i++) { + if (!isnan(input[i])) { + input_sum += input[i]; + } + } + input = &input_sum; + } else { + single = 0; + } + } +#ifdef USE_ENERGY_COLUMN_GUI + ext_snprintf_P(result, GUISZ, PSTR("")); // Skip first column + if ((Energy->phase_count > 1) && single) { // Need to set colspan so need new columns + // 1.23  + // 1.23  + // 1.23  + ext_snprintf_P(result, GUISZ, PSTR("%s%*_f "), + result, (Energy->phase_count *2) -1, (Settings->flag5.gui_table_align)?PSTR("right"):PSTR("center"), resolution, &input[0]); + } else { + // 1.23  + // 1.23 1.23  + // 1.23 1.23 1.23  + // 1.23 1.23 1.23 1.23  + for (uint32_t i = 0; i < Energy->phase_count; i++) { + ext_snprintf_P(result, GUISZ, PSTR("%s%*_f "), + result, (Settings->flag5.gui_table_align)?PSTR("right"):PSTR("left"), resolution, &input[i]); + } + } + ext_snprintf_P(result, GUISZ, PSTR("%s"), result); +#else // not USE_ENERGY_COLUMN_GUI + uint32_t index = (single) ? 1 : Energy->phase_count; // 1,2,3 + result[0] = '\0'; + for (uint32_t i = 0; i < index; i++) { + ext_snprintf_P(result, GUISZ, PSTR("%s%s%*_f"), result, (i)?" / ":"", resolution, &input[i]); + } +#endif // USE_ENERGY_COLUMN_GUI + return result; +} +#endif // USE_WEBSERVER + +/********************************************************************************************/ + +bool EnergyTariff1Active() // Off-Peak hours +{ + uint8_t dst = 0; + if (IsDst() && (Energy->Settings.tariff[0][1] != Energy->Settings.tariff[1][1])) { + dst = 1; + } + if (Energy->Settings.tariff[0][dst] != Energy->Settings.tariff[1][dst]) { + if (Settings->flag3.energy_weekend && ((RtcTime.day_of_week == 1) || // CMND_TARIFF + (RtcTime.day_of_week == 7))) { + return true; + } + uint32_t minutes = MinutesPastMidnight(); + if (Energy->Settings.tariff[0][dst] > Energy->Settings.tariff[1][dst]) { + // {"Tariff":{"Off-Peak":{"STD":"22:00","DST":"23:00"},"Standard":{"STD":"06:00","DST":"07:00"},"Weekend":"OFF"}} + return ((minutes >= Energy->Settings.tariff[0][dst]) || (minutes < Energy->Settings.tariff[1][dst])); + } else { + // {"Tariff":{"Off-Peak":{"STD":"00:29","DST":"01:29"},"Standard":{"STD":"07:29","DST":"08:29"},"Weekend":"OFF"}} + return ((minutes >= Energy->Settings.tariff[0][dst]) && (minutes < Energy->Settings.tariff[1][dst])); + } + } else { + return false; + } +} + +void EnergyUpdateToday(void) { + // Energy->kWhtoday_delta[]: int32_t x = 0.0000x kWh change + // Energy->kWhtoday[] : int32_t y = 0.0000y kWh + + Energy->total_sum = 0.0f; + Energy->yesterday_sum = 0.0f; + Energy->daily_sum = 0.0f; + int32_t delta_sum_balanced = 0; + + for (uint32_t i = 0; i < Energy->phase_count; i++) { + if (abs(Energy->kWhtoday_delta[i]) > 1000) { + int32_t delta = Energy->kWhtoday_delta[i] / 1000; + delta_sum_balanced += delta; + Energy->kWhtoday_delta[i] -= (delta * 1000); + Energy->kWhtoday[i] += delta; + if (delta < 0) { // Export energy + RtcEnergySettings.energy_export_kWh[i] += ((float)(delta / 100) *-1) / 1000; + } + } + + RtcEnergySettings.energy_today_kWh[i] = Energy->energy_today_offset_kWh[i] + ((float)(Energy->kWhtoday[i]) / 100000); + Energy->daily_kWh[i] = RtcEnergySettings.energy_today_kWh[i]; + Energy->total[i] = RtcEnergySettings.energy_total_kWh[i] + RtcEnergySettings.energy_today_kWh[i]; + if (Energy->local_energy_active_export) { + Energy->export_active[i] = RtcEnergySettings.energy_export_kWh[i]; + } + + Energy->total_sum += Energy->total[i]; + Energy->yesterday_sum += Energy->Settings.energy_yesterday_kWh[i]; + Energy->daily_sum += Energy->daily_kWh[i]; + } + + if (delta_sum_balanced > 0) { + Energy->daily_sum_import_balanced += (float)delta_sum_balanced / 100000; + } else { + Energy->daily_sum_export_balanced += (float)abs(delta_sum_balanced) / 100000; + } + + if (RtcTime.valid){ // We calc the difference only if we have a valid RTC time. + + float energy_diff = Energy->total_sum - RtcEnergySettings.energy_usage.last_usage_total_kWh; + RtcEnergySettings.energy_usage.last_usage_total_kWh = Energy->total_sum; + + float return_diff = 0; + if (!isnan(Energy->export_active[0])) { +// return_diff = Energy->export_active - RtcEnergySettings.energy_usage.last_return_total_kWh; +// RtcEnergySettings.energy_usage.last_return_total_kWh = Energy->export_active; + + float export_active = 0.0f; + for (uint32_t i = 0; i < Energy->phase_count; i++) { + if (!isnan(Energy->export_active[i])) { + export_active += Energy->export_active[i]; + } + } + return_diff = export_active - RtcEnergySettings.energy_usage.last_return_total_kWh; + RtcEnergySettings.energy_usage.last_return_total_kWh = export_active; + } + + uint32_t index = (EnergyTariff1Active()) ? 0 : 1; // Tarrif1 = Off-Peak + RtcEnergySettings.energy_usage.usage_total_kWh[index] += energy_diff; + RtcEnergySettings.energy_usage.return_total_kWh[index] += return_diff; + } +} + +void EnergyUpdateTotal(void) { + // Provide total import active energy as float Energy->import_active[phase] in kWh: 98Wh = 0.098kWh + + for (uint32_t i = 0; i < Energy->phase_count; i++) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NRG: EnergyTotal[%d] %4_f kWh"), i, &Energy->import_active[i]); + + // Try to fix instable input by verifying allowed bandwidth (#17659) + if ((Energy->start_energy[i] != 0) && + (Settings->param[P_CSE7766_INVALID_POWER] > 0) && + (Settings->param[P_CSE7766_INVALID_POWER] < 128)) { // SetOption39 1..127 kWh + int total = abs((int)Energy->total[i]); // We only use kWh + int import_active = abs((int)Energy->import_active[i]); + if ((import_active < (total - Settings->param[P_CSE7766_INVALID_POWER])) || + (import_active > (total + Settings->param[P_CSE7766_INVALID_POWER]))) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NRG: Outside bandwidth")); + continue; // No valid energy value received + } + } + + if (0 == Energy->start_energy[i] || (Energy->import_active[i] < Energy->start_energy[i])) { + Energy->start_energy[i] = Energy->import_active[i]; // Init after restart and handle roll-over if any + } + else if (Energy->import_active[i] != Energy->start_energy[i]) { + Energy->kWhtoday[i] = (int32_t)((Energy->import_active[i] - Energy->start_energy[i]) * 100000); + } + + if ((Energy->total[i] < (Energy->import_active[i] - 0.01f)) && // We subtract a little offset of 10Wh to avoid continuous updates + Settings->flag3.hardware_energy_total) { // SetOption72 - Enable hardware energy total counter as reference (#6561) + // The following calculation allows total usage (Energy->import_active[i]) up to +/-2147483.647 kWh + RtcEnergySettings.energy_total_kWh[i] = Energy->import_active[i] - (Energy->energy_today_offset_kWh[i] + ((float)(Energy->kWhtoday[i]) / 100000)); + Energy->Settings.energy_total_kWh[i] = RtcEnergySettings.energy_total_kWh[i]; + Energy->total[i] = Energy->import_active[i]; + Energy->Settings.energy_kWhtotal_time = (!Energy->energy_today_offset_kWh[i]) ? LocalTime() : Midnight(); + // AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Energy Total updated with hardware value")); + } + } + + EnergyUpdateToday(); +} + +/*********************************************************************************************/ + +void Energy200ms(void) +{ + Energy->power_on = (TasmotaGlobal.power != 0) | Settings->flag.no_power_on_check; // SetOption21 - Show voltage even if powered off + + Energy->fifth_second++; + if (5 == Energy->fifth_second) { + Energy->fifth_second = 0; + + XnrgCall(FUNC_ENERGY_EVERY_SECOND); + + if (RtcTime.valid) { + + if (!Energy->kWhtoday_offset_init && (RtcTime.day_of_year == Energy->Settings.energy_kWhdoy)) { + Energy->kWhtoday_offset_init = true; + for (uint32_t i = 0; i < 3; i++) { + Energy->energy_today_offset_kWh[i] = Energy->Settings.energy_today_kWh[i]; +// RtcEnergySettings.energy_today_kWh[i] = 0; + } + } + + if ((LocalTime() == Midnight()) || (RtcTime.day_of_year > Energy->Settings.energy_kWhdoy)) { + Energy->kWhtoday_offset_init = true; + Energy->Settings.energy_kWhdoy = RtcTime.day_of_year; + + for (uint32_t i = 0; i < 3; i++) { + Energy->Settings.energy_yesterday_kWh[i] = RtcEnergySettings.energy_today_kWh[i]; + + RtcEnergySettings.energy_total_kWh[i] += RtcEnergySettings.energy_today_kWh[i]; + Energy->Settings.energy_total_kWh[i] = RtcEnergySettings.energy_total_kWh[i]; + Energy->Settings.energy_export_kWh[i] = RtcEnergySettings.energy_export_kWh[i]; + + Energy->period_kWh[i] -= RtcEnergySettings.energy_today_kWh[i]; // this becomes a large unsigned, effectively a negative for EnergyShow calculation + Energy->kWhtoday[i] = 0; + Energy->energy_today_offset_kWh[i] = 0; + RtcEnergySettings.energy_today_kWh[i] = 0; + Energy->Settings.energy_today_kWh[i] = 0; + + Energy->start_energy[i] = 0; +// Energy->kWhtoday_delta = 0; // dont zero this, we need to carry the remainder over to tomorrow + Energy->daily_sum_import_balanced = 0.0; + Energy->daily_sum_export_balanced = 0.0; + } + EnergyUpdateToday(); + Energy->max_energy_state = 3; + } + if ((RtcTime.hour == Energy->Settings.max_energy_start) && (3 == Energy->max_energy_state )) { + Energy->max_energy_state = 0; + } + + } + } + + XnrgCall(FUNC_EVERY_200_MSECOND); +} + +void EnergySaveState(void) +{ + Energy->Settings.energy_kWhdoy = (RtcTime.valid) ? RtcTime.day_of_year : 0; + + for (uint32_t i = 0; i < 3; i++) { + Energy->Settings.energy_today_kWh[i] = RtcEnergySettings.energy_today_kWh[i]; + Energy->Settings.energy_total_kWh[i] = RtcEnergySettings.energy_total_kWh[i]; + Energy->Settings.energy_export_kWh[i] = RtcEnergySettings.energy_export_kWh[i]; + } + + Energy->Settings.energy_usage = RtcEnergySettings.energy_usage; +} + +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) { + if (!Energy->phase_count || (TasmotaGlobal.uptime < 8)) { return; } + if (Energy->power_steady_counter) { + Energy->power_steady_counter--; + return; + } + + bool jsonflg = false; + Response_P(PSTR("{\"" D_RSLT_MARGINS "\":{")); + + int16_t power_diff[ENERGY_MAX_PHASES] = { 0 }; + for (uint32_t phase = 0; phase < Energy->phase_count; phase++) { + uint16_t active_power = (uint16_t)(Energy->active_power[phase]); + +// AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: APower %d, HPower0 %d, HPower1 %d, HPower2 %d"), active_power, Energy->power_history[phase][0], Energy->power_history[phase][1], Energy->power_history[phase][2]); + + if (Energy->Settings.power_delta[phase]) { + power_diff[phase] = active_power - Energy->power_history[phase][0]; + uint16_t delta = abs(power_diff[phase]); + bool threshold_met = false; + if (delta > 0) { + if (Energy->Settings.power_delta[phase] < 101) { // 1..100 = Percentage + uint16_t min_power = (Energy->power_history[phase][0] > active_power) ? active_power : Energy->power_history[phase][0]; + if (0 == min_power) { min_power++; } // Fix divide by 0 exception (#6741) + delta = (delta * 100) / min_power; + if (delta >= Energy->Settings.power_delta[phase]) { + threshold_met = true; + } + } else { // 101..32000 = Absolute + if (delta >= (Energy->Settings.power_delta[phase] -100)) { + threshold_met = true; + } + } + } + if (threshold_met) { + Energy->power_history[phase][1] = active_power; // We only want one report so reset history + Energy->power_history[phase][2] = active_power; + jsonflg = true; + } else { + power_diff[phase] = 0; + } + } + Energy->power_history[phase][0] = Energy->power_history[phase][1]; // Shift in history every second allowing power changes to settle for up to three seconds + Energy->power_history[phase][1] = Energy->power_history[phase][2]; + Energy->power_history[phase][2] = active_power; + } + if (jsonflg) { + float power_diff_f[Energy->phase_count]; + for (uint32_t phase = 0; phase < Energy->phase_count; phase++) { + power_diff_f[phase] = power_diff[phase]; + } + char value_chr[TOPSZ]; + ResponseAppend_P(PSTR("\"" D_CMND_POWERDELTA "\":%s"), EnergyFormat(value_chr, power_diff_f, 0)); + } + + uint16_t energy_power_u = (uint16_t)(Energy->active_power[0]); + + if (Energy->power_on && (Energy->Settings.min_power || + Energy->Settings.max_power || + Energy->Settings.min_voltage || + Energy->Settings.max_voltage || + Energy->Settings.min_current || + Energy->Settings.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; + if (EnergyMargin(false, Energy->Settings.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, Energy->Settings.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, Energy->Settings.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, Energy->Settings.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, Energy->Settings.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, Energy->Settings.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(); + MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_MARGINS), MQTT_TELE_RETAIN); + EnergyMqttShow(); + } + + // Max Power + if (Energy->Settings.max_power_limit) { + if (Energy->active_power[0] > Energy->Settings.max_power_limit) { + if (!Energy->mplh_counter) { + Energy->mplh_counter = Energy->Settings.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_ALL_OFF, SRC_MAXPOWER); + if (!Energy->mplr_counter) { + Energy->mplr_counter = Settings->param[P_MAX_POWER_RETRY] +1; // SetOption33 - Max Power Retry count + } + Energy->mplw_counter = Energy->Settings.max_power_limit_window; + } + } + } + else if (TasmotaGlobal.power && (energy_power_u <= Energy->Settings.max_power_limit)) { + Energy->mplh_counter = 0; + Energy->mplr_counter = 0; + Energy->mplw_counter = 0; + } + 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)); + MqttPublishPrefixTopicRulesProcess_P(STAT, S_RSLT_WARNING); + EnergyMqttShow(); + SetAllPower(POWER_ALL_OFF, SRC_MAXPOWER); + } + } + } + } + } + + // Max Energy + if (Energy->Settings.max_energy) { + uint16_t energy_daily_u = (uint16_t)(Energy->daily_sum * 1000); + if (!Energy->max_energy_state && (RtcTime.hour == Energy->Settings.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 >= Energy->Settings.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_ALL_OFF, SRC_MAXENERGY); + } + } +} + +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}} + int tele_period_save = TasmotaGlobal.tele_period; + TasmotaGlobal.tele_period = 2; + ResponseClear(); + ResponseAppendTime(); + EnergyShow(true); + TasmotaGlobal.tele_period = tele_period_save; + ResponseJsonEnd(); + MqttPublishTeleSensor(); +} + +void EnergyEverySecond(void) +{ + // Overtemp check + if (Energy->use_overtemp && TasmotaGlobal.global_update) { + if (TasmotaGlobal.power && !isnan(TasmotaGlobal.temperature_celsius) && (TasmotaGlobal.temperature_celsius > (float)Settings->param[P_OVER_TEMP])) { // SetOption42 Device overtemp, turn off relays + + AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Temperature %1_f"), &TasmotaGlobal.temperature_celsius); + + SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP); + } + } + + // Invalid data reset + if (TasmotaGlobal.uptime > ENERGY_WATCHDOG) { + uint32_t data_valid = Energy->phase_count; + for (uint32_t i = 0; i < Energy->phase_count; i++) { + if (Energy->data_valid[i] <= ENERGY_WATCHDOG) { + Energy->data_valid[i]++; + if (Energy->data_valid[i] > ENERGY_WATCHDOG) { + // Reset energy registers + Energy->voltage[i] = 0; + Energy->current[i] = 0; + Energy->active_power[i] = 0; + if (!isnan(Energy->apparent_power[i])) { Energy->apparent_power[i] = 0; } + if (!isnan(Energy->reactive_power[i])) { Energy->reactive_power[i] = 0; } + if (!isnan(Energy->frequency[i])) { Energy->frequency[i] = 0; } + if (!isnan(Energy->power_factor[i])) { Energy->power_factor[i] = 0; } + if (!isnan(Energy->export_active[i])) { Energy->export_active[i] = 0; } + + data_valid--; + } + } + } + if (!data_valid) { + //Energy->start_energy = 0; + AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Energy reset by invalid data")); + + XnrgCall(FUNC_ENERGY_RESET); + } + } + + EnergyMarginCheck(); +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +void ResponseCmndEnergyTotalYesterdayToday(void) { + char value_chr[TOPSZ]; // Used by EnergyFormatIndex + char value2_chr[TOPSZ]; + char value3_chr[TOPSZ]; + + float energy_yesterday_kWh[3]; + for (uint32_t i = 0; i < Energy->phase_count; i++) { + energy_yesterday_kWh[i] = Energy->Settings.energy_yesterday_kWh[i]; + Energy->total[i] = RtcEnergySettings.energy_total_kWh[i] + Energy->energy_today_offset_kWh[i] + ((float)(Energy->kWhtoday[i]) / 100000); + if (Energy->local_energy_active_export) { + Energy->export_active[i] = RtcEnergySettings.energy_export_kWh[i]; + } + } + + Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s"), + XdrvMailbox.command, + EnergyFormat(value_chr, Energy->total, Settings->flag2.energy_resolution), + EnergyFormat(value2_chr, energy_yesterday_kWh, Settings->flag2.energy_resolution), + EnergyFormat(value3_chr, Energy->daily_kWh, Settings->flag2.energy_resolution)); + if (Energy->local_energy_active_export) { + ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT_ACTIVE "\":%s"), + EnergyFormat(value_chr, Energy->export_active, Settings->flag2.energy_resolution)); + } + ResponseJsonEndEnd(); +} + +void CmndEnergyTotal(void) { + uint32_t values[2] = { 0 }; + uint32_t params = ParseParameters(2, values); + + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Energy->phase_count) && (params > 0)) { + uint32_t phase = XdrvMailbox.index -1; + // Reset Energy Total + RtcEnergySettings.energy_total_kWh[phase] = (float)(values[0]) / 1000; + Energy->Settings.energy_total_kWh[phase] = RtcEnergySettings.energy_total_kWh[phase]; + if (params > 1) { + Energy->Settings.energy_kWhtotal_time = values[1]; + } else { + Energy->Settings.energy_kWhtotal_time = (!Energy->energy_today_offset_kWh[phase]) ? LocalTime() : Midnight(); + } + RtcEnergySettings.energy_usage.last_usage_total_kWh = Energy->total[phase]; + } + ResponseCmndEnergyTotalYesterdayToday(); +} + +void CmndEnergyYesterday(void) { + uint32_t values[2] = { 0 }; + uint32_t params = ParseParameters(2, values); + + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Energy->phase_count) && (params > 0)) { + uint32_t phase = XdrvMailbox.index -1; + // Reset Energy Yesterday + Energy->Settings.energy_yesterday_kWh[phase] = (float)(values[0]) / 1000; + if (params > 1) { + Energy->Settings.energy_kWhtotal_time = values[1]; + } + } + ResponseCmndEnergyTotalYesterdayToday(); +} + +void CmndEnergyToday(void) { + uint32_t values[2] = { 0 }; + uint32_t params = ParseParameters(2, values); + + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Energy->phase_count) && (params > 0)) { + uint32_t phase = XdrvMailbox.index -1; + // Reset Energy Today + Energy->energy_today_offset_kWh[phase] = (float)(values[0]) / 1000; + Energy->kWhtoday[phase] = 0; + Energy->kWhtoday_delta[phase] = 0; + Energy->start_energy[phase] = 0; + Energy->period_kWh[phase] = Energy->energy_today_offset_kWh[phase]; + Energy->Settings.energy_today_kWh[phase] = Energy->energy_today_offset_kWh[phase]; + RtcEnergySettings.energy_today_kWh[phase] = Energy->energy_today_offset_kWh[phase]; + Energy->daily_kWh[phase] = Energy->energy_today_offset_kWh[phase]; + if (params > 1) { + Energy->Settings.energy_kWhtotal_time = values[1]; + } + else if (!RtcEnergySettings.energy_total_kWh[phase] && !Energy->energy_today_offset_kWh[phase]) { + Energy->Settings.energy_kWhtotal_time = LocalTime(); + } + } + ResponseCmndEnergyTotalYesterdayToday(); +} + +void CmndEnergyExportActive(void) { + if (Energy->local_energy_active_export) { + // EnergyExportActive1 24 + // EnergyExportActive1 24,1650111291 + uint32_t values[2] = { 0 }; + uint32_t params = ParseParameters(2, values); + + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Energy->phase_count) && (params > 0)) { + uint32_t phase = XdrvMailbox.index -1; + // Reset Energy Export Active + RtcEnergySettings.energy_export_kWh[phase] = (float)(values[0]) / 1000; + Energy->Settings.energy_export_kWh[phase] = RtcEnergySettings.energy_export_kWh[phase]; + if (params > 1) { + Energy->Settings.energy_kWhtotal_time = values[1]; + } + } + ResponseCmndEnergyTotalYesterdayToday(); + } +} + +void ResponseCmndEnergyUsageExport(void) { + Response_P(PSTR("{\"%s\":{\"" D_JSON_USAGE "\":[%*_f,%*_f],\"" D_JSON_EXPORT "\":[%*_f,%*_f]}}"), + XdrvMailbox.command, + Settings->flag2.energy_resolution, &Energy->Settings.energy_usage.usage_total_kWh[0], + Settings->flag2.energy_resolution, &Energy->Settings.energy_usage.usage_total_kWh[1], + Settings->flag2.energy_resolution, &Energy->Settings.energy_usage.return_total_kWh[0], + Settings->flag2.energy_resolution, &Energy->Settings.energy_usage.return_total_kWh[1]); +} + +void CmndEnergyUsage(void) { + uint32_t values[2] = { 0 }; + uint32_t params = ParseParameters(2, values); + if (params > 0) { + // Reset energy_usage.usage totals + RtcEnergySettings.energy_usage.usage_total_kWh[0] = (float)(values[0]) / 1000; + if (params > 1) { + RtcEnergySettings.energy_usage.usage_total_kWh[1] = (float)(values[1]) / 1000; + } + Energy->Settings.energy_usage.usage_total_kWh[0] = RtcEnergySettings.energy_usage.usage_total_kWh[0]; + Energy->Settings.energy_usage.usage_total_kWh[1] = RtcEnergySettings.energy_usage.usage_total_kWh[1]; + } + ResponseCmndEnergyUsageExport(); +} + +void CmndEnergyExport(void) { + uint32_t values[2] = { 0 }; + uint32_t params = ParseParameters(2, values); + if (params > 0) { + // Reset energy_usage.return totals + RtcEnergySettings.energy_usage.return_total_kWh[0] = (float)(values[0]) / 1000; + if (params > 1) { + RtcEnergySettings.energy_usage.return_total_kWh[1] = (float)(values[1]) / 1000; + } + Energy->Settings.energy_usage.return_total_kWh[0] = RtcEnergySettings.energy_usage.return_total_kWh[0]; + Energy->Settings.energy_usage.return_total_kWh[1] = RtcEnergySettings.energy_usage.return_total_kWh[1]; + } + ResponseCmndEnergyUsageExport(); +} + +void CmndTariff(void) { + // Tariff1 22:00,23:00 - Tariff1 start hour for Standard Time and Daylight Savings Time + // Tariff2 6:00,7:00 - Tariff2 start hour for Standard Time and Daylight Savings Time + // Tariffx 1320, 1380 = minutes and also 22:00, 23:00 + // Tariffx 22, 23 = hours and also 22:00, 23:00 + // Tariff9 0/1 + + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) { + uint32_t tariff = XdrvMailbox.index -1; + uint32_t time_type = 0; + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); // 23:15, 22:30 + while ((str != nullptr) && (time_type < 2)) { + char *q; + uint32_t value = strtol(str, &q, 10); // 23 or 22 + Energy->Settings.tariff[tariff][time_type] = value; + if (value < 24) { // Below 24 is hours + Energy->Settings.tariff[tariff][time_type] *= 60; // Multiply hours by 60 minutes + char *minute = strtok_r(nullptr, ":", &q); + if (minute) { + value = strtol(minute, nullptr, 10); // 15 or 30 + if (value > 59) { + value = 59; + } + Energy->Settings.tariff[tariff][time_type] += value; + } + } + if (Energy->Settings.tariff[tariff][time_type] > 1439) { + Energy->Settings.tariff[tariff][time_type] = 1439; // Max is 23:59 + } + str = strtok_r(nullptr, ", ", &p); + time_type++; + } + } + else if (XdrvMailbox.index == 9) { + Settings->flag3.energy_weekend = XdrvMailbox.payload & 1; // CMND_TARIFF + } + Response_P(PSTR("{\"%s\":{\"Off-Peak\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Standard\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Weekend\":\"%s\"}}"), + XdrvMailbox.command, + GetMinuteTime(Energy->Settings.tariff[0][0]).c_str(),GetMinuteTime(Energy->Settings.tariff[0][1]).c_str(), + GetMinuteTime(Energy->Settings.tariff[1][0]).c_str(),GetMinuteTime(Energy->Settings.tariff[1][1]).c_str(), + GetStateText(Settings->flag3.energy_weekend)); // CMND_TARIFF +} + +uint32_t EnergyGetCalibration(uint32_t cal_type, uint32_t chan = 0) { +// uint32_t channel = ((1 == chan) && (2 == Energy->phase_count)) ? 1 : 0; + uint32_t channel = chan; + switch (cal_type) { + case ENERGY_POWER_CALIBRATION: return Energy->Settings.power_calibration[channel]; + case ENERGY_VOLTAGE_CALIBRATION: return Energy->Settings.voltage_calibration[channel]; + case ENERGY_CURRENT_CALIBRATION: return Energy->Settings.current_calibration[channel]; + case ENERGY_FREQUENCY_CALIBRATION: return Energy->Settings.frequency_calibration[channel]; + } + return 0; +} + +void EnergySetCalibration(uint32_t cal_type, uint32_t value, uint32_t chan = 0) { +// uint32_t channel = ((1 == chan) && (2 == Energy->phase_count)) ? 1 : 0; + uint32_t channel = chan; + switch (cal_type) { + case ENERGY_POWER_CALIBRATION: Energy->Settings.power_calibration[channel] = value; return; + case ENERGY_VOLTAGE_CALIBRATION: Energy->Settings.voltage_calibration[channel] = value; return; + case ENERGY_CURRENT_CALIBRATION: Energy->Settings.current_calibration[channel] = value; return; + case ENERGY_FREQUENCY_CALIBRATION: Energy->Settings.frequency_calibration[channel] = value; return; + } +} + +void EnergyCommandCalSetResponse(uint32_t cal_type) { + if (XdrvMailbox.payload > 99) { + EnergySetCalibration(cal_type, XdrvMailbox.payload, XdrvMailbox.index -1); + } + if (2 == Energy->phase_count) { + ResponseAppend_P(PSTR("[%d,%d]}"), EnergyGetCalibration(cal_type), EnergyGetCalibration(cal_type, 1)); + } else { + ResponseAppend_P(PSTR("%d}"), EnergyGetCalibration(cal_type)); + } +} + +void EnergyCommandCalResponse(uint32_t cal_type) { + Response_P(PSTR("{\"%s\":"), XdrvMailbox.command); + EnergyCommandCalSetResponse(cal_type); +} + +void EnergyCommandSetCalResponse(uint32_t cal_type) { + Response_P(PSTR("{\"%sCal\":"), XdrvMailbox.command); + EnergyCommandCalSetResponse(cal_type); +} + +void CmndPowerCal(void) { + Energy->command_code = CMND_POWERCAL; + if (XnrgCall(FUNC_COMMAND)) { // microseconds + EnergyCommandCalResponse(ENERGY_POWER_CALIBRATION); + } +} + +void CmndVoltageCal(void) { + Energy->command_code = CMND_VOLTAGECAL; + if (XnrgCall(FUNC_COMMAND)) { // microseconds + EnergyCommandCalResponse(ENERGY_VOLTAGE_CALIBRATION); + } +} + +void CmndCurrentCal(void) { + Energy->command_code = CMND_CURRENTCAL; + if (XnrgCall(FUNC_COMMAND)) { // microseconds + EnergyCommandCalResponse(ENERGY_CURRENT_CALIBRATION); + } +} + +void CmndFrequencyCal(void) { + Energy->command_code = CMND_FREQUENCYCAL; + if (XnrgCall(FUNC_COMMAND)) { // microseconds + EnergyCommandCalResponse(ENERGY_FREQUENCY_CALIBRATION); + } +} + +void CmndPowerSet(void) { + Energy->command_code = CMND_POWERSET; + if (XnrgCall(FUNC_COMMAND)) { // Watt + EnergyCommandSetCalResponse(ENERGY_POWER_CALIBRATION); + } +} + +void CmndVoltageSet(void) { + Energy->command_code = CMND_VOLTAGESET; + if (XnrgCall(FUNC_COMMAND)) { // Volt + EnergyCommandSetCalResponse(ENERGY_VOLTAGE_CALIBRATION); + } +} + +void CmndCurrentSet(void) { + Energy->command_code = CMND_CURRENTSET; + if (XnrgCall(FUNC_COMMAND)) { // milliAmpere + EnergyCommandSetCalResponse(ENERGY_CURRENT_CALIBRATION); + } +} + +void CmndFrequencySet(void) { + Energy->command_code = CMND_FREQUENCYSET; + if (XnrgCall(FUNC_COMMAND)) { // Hz + EnergyCommandSetCalResponse(ENERGY_FREQUENCY_CALIBRATION); + } +} + +void CmndModuleAddress(void) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4) && (1 == Energy->phase_count)) { + Energy->command_code = CMND_MODULEADDRESS; + if (XnrgCall(FUNC_COMMAND)) { // Module address + ResponseCmndDone(); + } + } +} + +void CmndEnergyConfig(void) { + Energy->command_code = CMND_ENERGYCONFIG; + ResponseClear(); + if (XnrgCall(FUNC_COMMAND)) { + if (!ResponseLength()) { + ResponseCmndDone(); + } + } +} + +void CmndPowerDelta(void) { + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= ENERGY_MAX_PHASES)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 32000)) { + Energy->Settings.power_delta[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Energy->Settings.power_delta[XdrvMailbox.index -1]); + } +} + +void CmndPowerLow(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.min_power = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.min_power); +} + +void CmndPowerHigh(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.max_power = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_power); +} + +void CmndVoltageLow(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { + Energy->Settings.min_voltage = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.min_voltage); +} + +void CmndVoltageHigh(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 500)) { + Energy->Settings.max_voltage = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_voltage); +} + +void CmndCurrentLow(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { + Energy->Settings.min_current = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.min_current); +} + +void CmndCurrentHigh(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 25000)) { + Energy->Settings.max_current = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_current); +} + +void CmndMaxPower(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.max_power_limit = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_power_limit); +} + +void CmndMaxPowerHold(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_power_limit_hold); +} + +void CmndMaxPowerWindow(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_power_limit_window); +} + +void CmndSafePower(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.max_power_safe_limit = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_power_safe_limit); +} + +void CmndSafePowerHold(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.max_power_safe_limit_hold = (1 == XdrvMailbox.payload) ? SAFE_POWER_HOLD : XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_power_safe_limit_hold); +} + +void CmndSafePowerWindow(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 1440)) { + Energy->Settings.max_power_safe_limit_window = (1 == XdrvMailbox.payload) ? SAFE_POWER_WINDOW : XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_power_safe_limit_window); +} + +void CmndMaxEnergy(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6000)) { + Energy->Settings.max_energy = XdrvMailbox.payload; + Energy->max_energy_state = 3; + } + ResponseCmndNumber(Energy->Settings.max_energy); +} + +void CmndMaxEnergyStart(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) { + Energy->Settings.max_energy_start = XdrvMailbox.payload; + } + ResponseCmndNumber(Energy->Settings.max_energy_start); +} + +void EnergyDrvInit(void) { + Energy = (tEnergy*)calloc(sizeof(tEnergy), 1); // Need calloc to reset registers to 0/false + if (!Energy) { return; } + + Xdrv03SettingsLoad(); + EnergyRtcSettingsLoad(); + +// Energy->voltage_common = false; +// Energy->frequency_common = false; +// Energy->use_overtemp = false; + for (uint32_t phase = 0; phase < ENERGY_MAX_PHASES; phase++) { + Energy->apparent_power[phase] = NAN; + Energy->reactive_power[phase] = NAN; + Energy->power_factor[phase] = NAN; + Energy->frequency[phase] = NAN; + Energy->export_active[phase] = NAN; + } + Energy->phase_count = 1; // Number of phases active + Energy->voltage_available = true; // Enable if voltage is measured + Energy->current_available = true; // Enable if current is measured + Energy->power_on = true; + + TasmotaGlobal.energy_driver = ENERGY_NONE; + XnrgCall(FUNC_PRE_INIT); // Find first energy driver + if (TasmotaGlobal.energy_driver) { + AddLog(LOG_LEVEL_INFO, PSTR("NRG: Init driver %d"), TasmotaGlobal.energy_driver); + } +} + +void EnergySnsInit(void) +{ + XnrgCall(FUNC_INIT); + + if (TasmotaGlobal.energy_driver) { +/* + AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: Rtc valid %d, kWhtoday_ph Rtc %3_f/%3_f/%3_f, Set %3_f/%3_f/%3_f"), + EnergyRtcSettingsValid(), + &RtcEnergySettings.energy_today_kWh[0],&RtcEnergySettings.energy_today_kWh[1],&RtcEnergySettings.energy_today_kWh[2], + &Energy->Settings.energy_today_kWh[0],&Energy->Settings.energy_today_kWh[1],&Energy->Settings.energy_today_kWh[2] + ); +*/ + for (uint32_t i = 0; i < 3; i++) { +// Energy->energy_today_offset_kWh[i] = 0; // Reset by EnergyDrvInit() + // 20220805 - Change from https://github.com/arendst/Tasmota/issues/16118 + if (EnergyRtcSettingsValid()) { + Energy->energy_today_offset_kWh[i] = RtcEnergySettings.energy_today_kWh[i]; + RtcEnergySettings.energy_today_kWh[i] = 0; + Energy->kWhtoday_offset_init = true; + } +// Energy->kWhtoday_ph[i] = 0; // Reset by EnergyDrvInit() +// Energy->kWhtoday_delta[i] = 0; // Reset by EnergyDrvInit() + Energy->period_kWh[i] = Energy->energy_today_offset_kWh[i]; + if (Energy->local_energy_active_export) { + Energy->export_active[i] = 0; // Was set to NAN by EnergyDrvInit() + } + } + 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} = + +const char HTTP_ENERGY_SNS3[] PROGMEM = + "{s}" D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; +#endif // USE_WEBSERVER + +void EnergyShow(bool json) { + if (Energy->voltage_common) { + for (uint32_t i = 0; i < Energy->phase_count; i++) { + Energy->voltage[i] = Energy->voltage[0]; + } + } + + float apparent_power[Energy->phase_count]; + float reactive_power[Energy->phase_count]; + float power_factor[Energy->phase_count]; + if (!Energy->type_dc) { + if (Energy->current_available && Energy->voltage_available) { + for (uint32_t i = 0; i < Energy->phase_count; i++) { + apparent_power[i] = Energy->apparent_power[i]; + if (isnan(apparent_power[i])) { + apparent_power[i] = Energy->voltage[i] * Energy->current[i]; + } + if (apparent_power[i] < Energy->active_power[i]) { // Should be impossible + Energy->active_power[i] = apparent_power[i]; + } + + power_factor[i] = Energy->power_factor[i]; + if (isnan(power_factor[i])) { + power_factor[i] = (Energy->active_power[i] && apparent_power[i]) ? Energy->active_power[i] / apparent_power[i] : 0; + if (power_factor[i] > 1) { + power_factor[i] = 1; + } + } + + reactive_power[i] = Energy->reactive_power[i]; + if (isnan(reactive_power[i])) { + reactive_power[i] = 0; + uint32_t difference = ((uint32_t)(apparent_power[i] * 100) - (uint32_t)(Energy->active_power[i] * 100)) / 10; + if ((Energy->current[i] > 0.005f) && ((difference > 15) || (difference > (uint32_t)(apparent_power[i] * 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[i] = (float)(RoundSqrtInt((uint64_t)(apparent_power[i] * apparent_power[i] * 100) - (uint64_t)(Energy->active_power[i] * Energy->active_power[i] * 100))) / 10; + float power_diff = apparent_power[i] * apparent_power[i] - Energy->active_power[i] * Energy->active_power[i]; + if (power_diff < 10737418) // 2^30 / 100 (RoundSqrtInt is limited to 2^30-1) + reactive_power[i] = (float)(RoundSqrtInt((uint32_t)(power_diff * 100.0f))) / 10.0f; + else + reactive_power[i] = (float)(SqrtInt((uint32_t)(power_diff))); + } + } + + } + } + } + + float active_power_sum = 0.0f; + float energy_yesterday_kWh[Energy->phase_count]; + for (uint32_t i = 0; i < Energy->phase_count; i++) { + energy_yesterday_kWh[i] = Energy->Settings.energy_yesterday_kWh[i]; + + active_power_sum += Energy->active_power[i]; + } + + bool energy_tariff = false; + float energy_usage_kWh[2]; + float energy_return_kWh[2]; + if (Energy->Settings.tariff[0][0] != Energy->Settings.tariff[1][0]) { + energy_usage_kWh[0] = RtcEnergySettings.energy_usage.usage_total_kWh[0]; // Tariff1 + energy_usage_kWh[1] = RtcEnergySettings.energy_usage.usage_total_kWh[1]; // Tariff2 + energy_return_kWh[0] = RtcEnergySettings.energy_usage.return_total_kWh[0]; // Tariff1 + energy_return_kWh[1] = RtcEnergySettings.energy_usage.return_total_kWh[1]; // Tariff2 + energy_tariff = true; + } + + char value_chr[GUISZ]; // Used by EnergyFormatIndex + char value2_chr[GUISZ]; + char value3_chr[GUISZ]; + + if (json) { + bool show_energy_period = (0 == TasmotaGlobal.tele_period); + + ResponseAppend_P(PSTR(",\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL_START_TIME "\":\"%s\",\"" D_JSON_TOTAL "\":%s"), + GetDateAndTime(DT_ENERGY).c_str(), + EnergyFormat(value_chr, Energy->total, Settings->flag2.energy_resolution, 2)); + + if (energy_tariff) { + ResponseAppend_P(PSTR(",\"" D_JSON_TOTAL D_CMND_TARIFF "\":%s"), + EnergyFormat(value_chr, energy_usage_kWh, Settings->flag2.energy_resolution, 6)); + } + + ResponseAppend_P(PSTR(",\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s"), + EnergyFormat(value_chr, energy_yesterday_kWh, Settings->flag2.energy_resolution, 2), + EnergyFormat(value2_chr, Energy->daily_kWh, Settings->flag2.energy_resolution, 2)); + +/* + #if defined(SDM630_IMPORT) || defined(SDM72_IMPEXP) + if (!isnan(Energy->import_active[0])) { + ResponseAppend_P(PSTR(",\"" D_JSON_IMPORT_ACTIVE "\":%s"), + EnergyFormat(value_chr, Energy->import_active, Settings->flag2.energy_resolution)); + if (energy_tariff) { + ResponseAppend_P(PSTR(",\"" D_JSON_IMPORT D_CMND_TARIFF "\":%s"), + EnergyFormat(value_chr, energy_return_kWh, Settings->flag2.energy_resolution, 6)); + } + } +#endif // SDM630_IMPORT || SDM72_IMPEXP +*/ + + if (!isnan(Energy->export_active[0])) { + uint32_t single = (!isnan(Energy->export_active[1]) && !isnan(Energy->export_active[2])) ? 0 : 1; + ResponseAppend_P(PSTR(",\"" D_JSON_TODAY_SUM_IMPORT "\":%s,\"" D_JSON_TODAY_SUM_EXPORT "\":%s,\"" D_JSON_EXPORT_ACTIVE "\":%s"), + EnergyFormat(value_chr, &Energy->daily_sum_import_balanced, Settings->flag2.energy_resolution, 1), + EnergyFormat(value2_chr, &Energy->daily_sum_export_balanced, Settings->flag2.energy_resolution, 1), + EnergyFormat(value3_chr, Energy->export_active, Settings->flag2.energy_resolution, single)); + if (energy_tariff) { + ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT D_CMND_TARIFF "\":%s"), + EnergyFormat(value_chr, energy_return_kWh, Settings->flag2.energy_resolution, 6)); + } + } + + if (show_energy_period) { + float energy_period[Energy->phase_count]; + for (uint32_t i = 0; i < Energy->phase_count; i++) { + energy_period[i] = RtcEnergySettings.energy_today_kWh[i] - Energy->period_kWh[i]; + Energy->period_kWh[i] = RtcEnergySettings.energy_today_kWh[i]; + } + ResponseAppend_P(PSTR(",\"" D_JSON_PERIOD "\":%s"), + EnergyFormat(value_chr, energy_period, Settings->flag2.wattage_resolution)); + } + + ResponseAppend_P(PSTR(",\"" D_JSON_POWERUSAGE "\":%s"), + EnergyFormat(value_chr, Energy->active_power, Settings->flag2.wattage_resolution)); + 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"), + EnergyFormat(value_chr, apparent_power, Settings->flag2.wattage_resolution), + EnergyFormat(value2_chr, reactive_power, Settings->flag2.wattage_resolution), + EnergyFormat(value3_chr, power_factor, 2)); + } + if (!isnan(Energy->frequency[0])) { + ResponseAppend_P(PSTR(",\"" D_JSON_FREQUENCY "\":%s"), + EnergyFormat(value_chr, Energy->frequency, Settings->flag2.frequency_resolution, Energy->frequency_common)); + } + } + if (Energy->voltage_available) { + ResponseAppend_P(PSTR(",\"" D_JSON_VOLTAGE "\":%s"), + EnergyFormat(value_chr, Energy->voltage, Settings->flag2.voltage_resolution, Energy->voltage_common)); + } + if (Energy->current_available) { + ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), + EnergyFormat(value_chr, Energy->current, Settings->flag2.current_resolution)); + } + XnrgCall(FUNC_JSON_APPEND); + ResponseJsonEnd(); + +#ifdef USE_DOMOTICZ + if (show_energy_period) { // Only send if telemetry + char temp_chr[FLOATSZ]; + if (Energy->voltage_available) { + dtostrfd(Energy->voltage[0], Settings->flag2.voltage_resolution, temp_chr); + DomoticzSensor(DZ_VOLTAGE, temp_chr); // Voltage + } + if (Energy->current_available) { + dtostrfd(Energy->current[0], Settings->flag2.current_resolution, temp_chr); + DomoticzSensor(DZ_CURRENT, temp_chr); // Current + } + dtostrfd(Energy->total_sum * 1000, 1, temp_chr); + DomoticzSensorPowerEnergy((int)active_power_sum, temp_chr); // PowerUsage, EnergyToday + + char energy_usage_chr[2][FLOATSZ]; + char energy_return_chr[2][FLOATSZ]; + dtostrfd(RtcEnergySettings.energy_usage.usage_total_kWh[0], 1, energy_usage_chr[0]); // Tariff1 + dtostrfd(RtcEnergySettings.energy_usage.usage_total_kWh[1], 1, energy_usage_chr[1]); // Tariff2 + dtostrfd(RtcEnergySettings.energy_usage.return_total_kWh[0], 1, energy_return_chr[0]); + dtostrfd(RtcEnergySettings.energy_usage.return_total_kWh[1], 1, energy_return_chr[1]); + DomoticzSensorP1SmartMeter(energy_usage_chr[0], energy_usage_chr[1], energy_return_chr[0], energy_return_chr[1], (int)active_power_sum); + + } +#endif // USE_DOMOTICZ +#ifdef USE_KNX + if (show_energy_period) { + if (Energy->voltage_available) { + KnxSensor(KNX_ENERGY_VOLTAGE, Energy->voltage[0]); + } + if (Energy->current_available) { + KnxSensor(KNX_ENERGY_CURRENT, Energy->current[0]); + } + KnxSensor(KNX_ENERGY_POWER, active_power_sum); + if (!Energy->type_dc) { + KnxSensor(KNX_ENERGY_POWERFACTOR, power_factor[0]); + } + KnxSensor(KNX_ENERGY_DAILY, Energy->daily_sum); + KnxSensor(KNX_ENERGY_TOTAL, Energy->total_sum); + KnxSensor(KNX_ENERGY_YESTERDAY, Energy->yesterday_sum); + } +#endif // USE_KNX +#ifdef USE_WEBSERVER + } else { +#ifdef USE_ENERGY_COLUMN_GUI + // Need a new table supporting more columns using empty columns (with   in data rows) as easy column spacing + // {s}Head1{e} + // {s}Head1Head2{e} + // {s}Head1Head2Head3{e} + // {s}Head1Head2Head3Head4{e} + WSContentSend_P(PSTR("
{t}{s}")); // First column is empty ({t} = , {s} = "), (no_label)?"":"L", (no_label)?"":itoa(i +1, value_chr, 10)); + } + WSContentSend_P(PSTR(") +#endif // USE_ENERGY_COLUMN_GUI + if (Energy->voltage_available) { + WSContentSend_PD(HTTP_SNS_VOLTAGE, WebEnergyFormat(value_chr, Energy->voltage, Settings->flag2.voltage_resolution, Energy->voltage_common)); + } + if (!Energy->type_dc) { + if (!isnan(Energy->frequency[0])) { + WSContentSend_PD(PSTR("{s}" D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"), + WebEnergyFormat(value_chr, Energy->frequency, Settings->flag2.frequency_resolution, Energy->frequency_common)); + } + } + if (Energy->current_available) { + WSContentSend_PD(HTTP_SNS_CURRENT, WebEnergyFormat(value_chr, Energy->current, Settings->flag2.current_resolution)); + } + WSContentSend_PD(HTTP_SNS_POWER, WebEnergyFormat(value_chr, Energy->active_power, Settings->flag2.wattage_resolution)); + if (!Energy->type_dc) { + if (Energy->current_available && Energy->voltage_available) { + WSContentSend_PD(HTTP_ENERGY_SNS1, WebEnergyFormat(value_chr, apparent_power, Settings->flag2.wattage_resolution), + WebEnergyFormat(value2_chr, reactive_power, Settings->flag2.wattage_resolution), + WebEnergyFormat(value3_chr, power_factor, 2)); + } + } + WSContentSend_PD(HTTP_ENERGY_SNS2, WebEnergyFormat(value_chr, Energy->daily_kWh, Settings->flag2.energy_resolution, 2), + WebEnergyFormat(value2_chr, energy_yesterday_kWh, Settings->flag2.energy_resolution, 2), + WebEnergyFormat(value3_chr, Energy->total, Settings->flag2.energy_resolution, 2)); + if (!isnan(Energy->export_active[0])) { + uint32_t single = (!isnan(Energy->export_active[1]) && !isnan(Energy->export_active[2])) ? 2 : 1; + WSContentSend_PD(HTTP_ENERGY_SNS3, WebEnergyFormat(value_chr, Energy->export_active, Settings->flag2.energy_resolution, single)); + } +#ifdef USE_ENERGY_COLUMN_GUI + XnrgCall(FUNC_WEB_COL_SENSOR); + WSContentSend_P(PSTR("
) + bool no_label = Energy->voltage_common || (1 == Energy->phase_count); + for (uint32_t i = 0; i < Energy->phase_count; i++) { + WSContentSend_P(PSTR("%s%s{e}")); // Last column is units ({e} =

{t}")); // {t} = - Define for next FUNC_WEB_SENSOR +#endif // USE_ENERGY_COLUMN_GUI + XnrgCall(FUNC_WEB_SENSOR); +#endif // USE_WEBSERVER + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv03(uint32_t function) +{ + bool result = false; + + if (FUNC_PRE_INIT == function) { + EnergyDrvInit(); + } + else if (TasmotaGlobal.energy_driver) { + switch (function) { + case FUNC_LOOP: + case FUNC_SLEEP_LOOP: + XnrgCall(FUNC_LOOP); + break; + case FUNC_EVERY_250_MSECOND: + if (TasmotaGlobal.uptime > 4) { + XnrgCall(FUNC_EVERY_250_MSECOND); + } + break; + case FUNC_EVERY_SECOND: + XnrgCall(FUNC_EVERY_SECOND); + break; + case FUNC_SERIAL: + result = XnrgCall(FUNC_SERIAL); + break; + case FUNC_SAVE_SETTINGS: + Xdrv03SettingsSave(); + EnergyRtcSettingsSave(); + break; + case FUNC_SET_POWER: + Energy->power_steady_counter = 2; + break; + case FUNC_COMMAND: + result = DecodeCommand(kEnergyCommands, EnergyCommand); + break; + case FUNC_NETWORK_UP: + XnrgCall(FUNC_NETWORK_UP); + break; + case FUNC_NETWORK_DOWN: + XnrgCall(FUNC_NETWORK_DOWN); + break; + } + } + return result; +} + +bool Xsns03(uint32_t function) +{ + bool result = false; + + if (TasmotaGlobal.energy_driver) { + switch (function) { + case FUNC_EVERY_SECOND: + EnergyEverySecond(); + 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; + case FUNC_INIT: + EnergySnsInit(); + break; + } + } + return result; +} + +#endif // USE_ENERGY_SENSOR +#endif // ESP32 diff --git a/tasmota/tasmota_xnrg_energy/xnrg_01_hlw8012.ino b/tasmota/tasmota_xnrg_energy/xnrg_01_hlw8012.ino index 88f6d2d74..c54d43dd6 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_01_hlw8012.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_01_hlw8012.ino @@ -134,7 +134,7 @@ void HlwEvery200ms(void) { Hlw.cf_pulse_counter = 0; if (Hlw.cf_power_pulse_length && Energy->power_on && !Hlw.load_off) { - hlw_w = (Hlw.power_ratio * Settings->energy_power_calibration) / Hlw.cf_power_pulse_length ; // W *10 + hlw_w = (Hlw.power_ratio * EnergyGetCalibration(ENERGY_POWER_CALIBRATION)) / Hlw.cf_power_pulse_length ; // W *10 Energy->active_power[0] = (float)hlw_w / 10; Hlw.power_retry = 1; // Workaround issue #5161 } else { @@ -179,7 +179,7 @@ void HlwEvery200ms(void) { Hlw.cf1_voltage_pulse_length = cf1_pulse_length; if (Hlw.cf1_voltage_pulse_length && Energy->power_on) { // If powered on always provide voltage - hlw_u = (Hlw.voltage_ratio * Settings->energy_voltage_calibration) / Hlw.cf1_voltage_pulse_length ; // V *10 + hlw_u = (Hlw.voltage_ratio * EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION)) / Hlw.cf1_voltage_pulse_length ; // V *10 Energy->voltage[0] = (float)hlw_u / 10; } else { Energy->voltage[0] = 0; @@ -189,7 +189,7 @@ void HlwEvery200ms(void) { Hlw.cf1_current_pulse_length = cf1_pulse_length; if (Hlw.cf1_current_pulse_length && Energy->active_power[0]) { // No current if no power being consumed - hlw_i = (Hlw.current_ratio * Settings->energy_current_calibration) / Hlw.cf1_current_pulse_length; // mA + hlw_i = (Hlw.current_ratio * EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION)) / Hlw.cf1_current_pulse_length; // mA Energy->current[0] = (float)hlw_i / 1000; } else { Energy->current[0] = 0; @@ -217,7 +217,7 @@ void HlwEverySecond(void) { hlw_len = 10000 * 100 / Hlw.energy_period_counter; // Add *100 to fix rounding on loads at 3.6kW (#9160) Hlw.energy_period_counter = 0; if (hlw_len) { - Energy->kWhtoday_delta[0] += (((Hlw.power_ratio * Settings->energy_power_calibration) / 36) * 100) / hlw_len; + Energy->kWhtoday_delta[0] += (((Hlw.power_ratio * EnergyGetCalibration(ENERGY_POWER_CALIBRATION)) / 36) * 100) / hlw_len; EnergyUpdateToday(); } } @@ -225,10 +225,10 @@ void HlwEverySecond(void) { } void HlwSnsInit(void) { - if (!Settings->energy_power_calibration || (4975 == Settings->energy_power_calibration)) { - Settings->energy_power_calibration = HLW_PREF_PULSE; - Settings->energy_voltage_calibration = HLW_UREF_PULSE; - Settings->energy_current_calibration = HLW_IREF_PULSE; + if (!EnergyGetCalibration(ENERGY_POWER_CALIBRATION) || (4975 == EnergyGetCalibration(ENERGY_POWER_CALIBRATION))) { + EnergySetCalibration(ENERGY_POWER_CALIBRATION, HLW_PREF_PULSE); + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, HLW_UREF_PULSE); + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, HLW_IREF_PULSE); } if (Hlw.model_type) { diff --git a/tasmota/tasmota_xnrg_energy/xnrg_02_cse7766.ino b/tasmota/tasmota_xnrg_energy/xnrg_02_cse7766.ino index b4077ed74..023480298 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_02_cse7766.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_02_cse7766.ino @@ -77,26 +77,26 @@ void CseReceived(void) { } // Get chip calibration data (coefficients) and use as initial defaults - if (HLW_UREF_PULSE == Settings->energy_voltage_calibration) { + if (HLW_UREF_PULSE == EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION)) { long voltage_coefficient = 191200; // uSec if (CSE_NOT_CALIBRATED != header) { voltage_coefficient = Cse.rx_buffer[2] << 16 | Cse.rx_buffer[3] << 8 | Cse.rx_buffer[4]; } - Settings->energy_voltage_calibration = voltage_coefficient / CSE_UREF; + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, voltage_coefficient / CSE_UREF); } - if (HLW_IREF_PULSE == Settings->energy_current_calibration) { + if (HLW_IREF_PULSE == EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION)) { long current_coefficient = 16140; // uSec if (CSE_NOT_CALIBRATED != header) { current_coefficient = Cse.rx_buffer[8] << 16 | Cse.rx_buffer[9] << 8 | Cse.rx_buffer[10]; } - Settings->energy_current_calibration = current_coefficient; + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, current_coefficient); } - if (HLW_PREF_PULSE == Settings->energy_power_calibration) { + if (HLW_PREF_PULSE == EnergyGetCalibration(ENERGY_POWER_CALIBRATION)) { long power_coefficient = 5364000; // uSec if (CSE_NOT_CALIBRATED != header) { power_coefficient = Cse.rx_buffer[14] << 16 | Cse.rx_buffer[15] << 8 | Cse.rx_buffer[16]; } - Settings->energy_power_calibration = power_coefficient / CSE_PREF; + EnergySetCalibration(ENERGY_POWER_CALIBRATION, power_coefficient / CSE_PREF); } uint8_t adjustement = Cse.rx_buffer[20]; @@ -107,7 +107,7 @@ void CseReceived(void) { if (Energy->power_on) { // Powered on if (adjustement & 0x40) { // Voltage valid - Energy->voltage[0] = (float)(Settings->energy_voltage_calibration * CSE_UREF) / (float)Cse.voltage_cycle; + Energy->voltage[0] = (float)(EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION) * CSE_UREF) / (float)Cse.voltage_cycle; } if (adjustement & 0x10) { // Power valid Cse.power_invalid = 0; @@ -117,7 +117,7 @@ void CseReceived(void) { if (0 == Cse.power_cycle_first) { Cse.power_cycle_first = Cse.power_cycle; } // Skip first incomplete Cse.power_cycle if (Cse.power_cycle_first != Cse.power_cycle) { Cse.power_cycle_first = -1; - Energy->active_power[0] = (float)(Settings->energy_power_calibration * CSE_PREF) / (float)Cse.power_cycle; + Energy->active_power[0] = (float)(EnergyGetCalibration(ENERGY_POWER_CALIBRATION) * CSE_PREF) / (float)Cse.power_cycle; } else { Energy->active_power[0] = 0; } @@ -134,7 +134,7 @@ void CseReceived(void) { if (0 == Energy->active_power[0]) { Energy->current[0] = 0; } else { - Energy->current[0] = (float)Settings->energy_current_calibration / (float)Cse.current_cycle; + Energy->current[0] = (float)EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION) / (float)Cse.current_cycle; } } } else { // Powered off @@ -204,7 +204,7 @@ void CseEverySecond(void) { cf_pulses = Cse.cf_pulses - Cse.cf_pulses_last_time; } if (cf_pulses && Energy->active_power[0]) { - uint32_t delta = (cf_pulses * Settings->energy_power_calibration) / 36; + uint32_t delta = (cf_pulses * EnergyGetCalibration(ENERGY_POWER_CALIBRATION)) / 36; // prevent invalid load delta steps even checksum is valid (issue #5789): // prevent invalid load delta steps even checksum is valid but allow up to 4kW (issue #7155): // if (delta <= (4000 * 1000 / 36)) { // max load for S31/Pow R2: 4.00kW diff --git a/tasmota/tasmota_xnrg_energy/xnrg_04_mcp39f501.ino b/tasmota/tasmota_xnrg_energy/xnrg_04_mcp39f501.ino index 41259ba87..9cd8c4a52 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_04_mcp39f501.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_04_mcp39f501.ino @@ -214,15 +214,15 @@ void McpParseCalibration(void) cal_registers.accumulation_interval = McpExtractInt(mcp_buffer, 52, 2); if (mcp_calibrate & MCP_CALIBRATE_POWER) { - cal_registers.calibration_active_power = Settings->energy_power_calibration; + cal_registers.calibration_active_power = EnergyGetCalibration(ENERGY_POWER_CALIBRATION); if (McpCalibrationCalc(&cal_registers, 16)) { action = true; } } if (mcp_calibrate & MCP_CALIBRATE_VOLTAGE) { - cal_registers.calibration_voltage = Settings->energy_voltage_calibration; + cal_registers.calibration_voltage = EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION); if (McpCalibrationCalc(&cal_registers, 0)) { action = true; } } if (mcp_calibrate & MCP_CALIBRATE_CURRENT) { - cal_registers.calibration_current = Settings->energy_current_calibration; + cal_registers.calibration_current = EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION); if (McpCalibrationCalc(&cal_registers, 8)) { action = true; } } mcp_timeout = 0; @@ -230,9 +230,9 @@ void McpParseCalibration(void) mcp_calibrate = 0; - Settings->energy_power_calibration = cal_registers.calibration_active_power; - Settings->energy_voltage_calibration = cal_registers.calibration_voltage; - Settings->energy_current_calibration = cal_registers.calibration_current; + EnergySetCalibration(ENERGY_POWER_CALIBRATION, cal_registers.calibration_active_power); + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, cal_registers.calibration_voltage); + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, cal_registers.calibration_current); mcp_system_configuration = cal_registers.system_configuration; @@ -386,7 +386,7 @@ void McpParseFrequency(void) uint16_t gain_line_frequency = mcp_buffer[4] * 256 + mcp_buffer[5]; if (mcp_calibrate & MCP_CALIBRATE_FREQUENCY) { - line_frequency_ref = Settings->energy_frequency_calibration; + line_frequency_ref = EnergyGetCalibration(ENERGY_FREQUENCY_CALIBRATION); if ((0xFFFF == mcp_line_frequency) || (0 == gain_line_frequency)) { // Reset values to 50Hz mcp_line_frequency = 50000; @@ -398,7 +398,7 @@ void McpParseFrequency(void) McpSetFrequency(line_frequency_ref, gain_line_frequency); } - Settings->energy_frequency_calibration = line_frequency_ref; + EnergySetCalibration(ENERGY_FREQUENCY_CALIBRATION, line_frequency_ref); mcp_calibrate = 0; } diff --git a/tasmota/tasmota_xnrg_energy/xnrg_07_ade7953.ino b/tasmota/tasmota_xnrg_energy/xnrg_07_ade7953.ino index d5f79e848..4c66d7d15 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_07_ade7953.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_07_ade7953.ino @@ -496,12 +496,12 @@ void Ade7953GetData(void) { for (uint32_t channel = 0; channel < Energy->phase_count; channel++) { Energy->data_valid[channel] = 0; - float power_calibration = (float)EnergyGetCalibration(channel, ENERGY_POWER_CALIBRATION) / 10; + float power_calibration = (float)EnergyGetCalibration(ENERGY_POWER_CALIBRATION, channel) / 10; #ifdef ADE7953_ACCU_ENERGY power_calibration /= ADE7953_POWER_CORRECTION; #endif // ADE7953_ACCU_ENERGY - float voltage_calibration = (float)EnergyGetCalibration(channel, ENERGY_VOLTAGE_CALIBRATION); - float current_calibration = (float)EnergyGetCalibration(channel, ENERGY_CURRENT_CALIBRATION) * 10; + float voltage_calibration = (float)EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION, channel); + float current_calibration = (float)EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION, channel) * 10; Energy->frequency[channel] = 223750.0f / ((float)reg[channel][5] + 1); divider = (Ade7953.calib_data[channel][ADE7953_CAL_VGAIN] != ADE7953_GAIN_DEFAULT) ? 10000 : voltage_calibration; @@ -705,14 +705,12 @@ void Ade7953DrvInit(void) { #ifdef USE_ESP32_SPI } #endif // USE_ESP32_SPI - - if (HLW_PREF_PULSE == Settings->energy_power_calibration) { - Settings->energy_power_calibration = ADE7953_PREF; - Settings->energy_voltage_calibration = ADE7953_UREF; - Settings->energy_current_calibration = ADE7953_IREF; - Settings->energy_power_calibration2 = ADE7953_PREF; - Settings->energy_voltage_calibration2 = ADE7953_UREF; - Settings->energy_current_calibration2 = ADE7953_IREF; + if (EnergyGetCalibration(ENERGY_POWER_CALIBRATION) == HLW_PREF_PULSE) { + for (uint32_t i = 0; i < 4; i++) { + EnergySetCalibration(ENERGY_POWER_CALIBRATION, ADE7953_PREF, i); + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, ADE7953_UREF, i); + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, ADE7953_IREF, i); + } } Ade7953Defaults(); diff --git a/tasmota/tasmota_xnrg_energy/xnrg_14_bl09xx.ino b/tasmota/tasmota_xnrg_energy/xnrg_14_bl09xx.ino index b944078af..a1dc34e32 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_14_bl09xx.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_14_bl09xx.ino @@ -188,13 +188,13 @@ bool Bl09XXDecode42(void) { void Bl09XXUpdateEnergy() { if (Energy->power_on) { // Powered on - Energy->voltage[0] = (float)Bl09XX.voltage / Settings->energy_voltage_calibration; + Energy->voltage[0] = (float)Bl09XX.voltage / EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION); #ifdef DEBUG_BL09XX AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("BL9: U %2_f, T %2_f"), &Energy->voltage[0], &Bl09XX.temperature); #endif for (uint32_t chan = 0; chan < Energy->phase_count; chan++) { - uint32_t power_calibration = EnergyGetCalibration(chan, ENERGY_POWER_CALIBRATION); - uint32_t current_calibration = EnergyGetCalibration(chan, ENERGY_CURRENT_CALIBRATION); + uint32_t power_calibration = EnergyGetCalibration(ENERGY_POWER_CALIBRATION, chan); + uint32_t current_calibration = EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION, chan); if (Bl09XX.power[chan] > power_calibration) { // We need at least 1W Energy->active_power[chan] = (float)Bl09XX.power[chan] / power_calibration; Energy->current[chan] = (float)Bl09XX.current[chan] / current_calibration; @@ -287,16 +287,16 @@ void Bl09XXInit(void) { if (Bl09XXSerial->hardwareSerial()) { ClaimSerial(); } - if (HLW_UREF_PULSE == Settings->energy_voltage_calibration) { - Settings->energy_voltage_calibration = bl09xx_uref[Bl09XX.model]; - Settings->energy_current_calibration = bl09xx_iref[Bl09XX.model]; - Settings->energy_power_calibration = bl09xx_pref[Bl09XX.model]; - Settings->energy_voltage_calibration2 = bl09xx_uref[Bl09XX.model]; - Settings->energy_current_calibration2 = bl09xx_iref[Bl09XX.model]; - Settings->energy_power_calibration2 = bl09xx_pref[Bl09XX.model]; + if (HLW_UREF_PULSE == EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION)) { + for (uint32_t i = 0; i < 2; i++) { + EnergySetCalibration(ENERGY_POWER_CALIBRATION, bl09xx_pref[Bl09XX.model], i); + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, bl09xx_uref[Bl09XX.model], i); + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, bl09xx_iref[Bl09XX.model], i); + } } - if ((BL0940_MODEL == Bl09XX.model) && (Settings->energy_current_calibration < (BL0940_IREF / 20))) { - Settings->energy_current_calibration *= 100; + if ((BL0940_MODEL == Bl09XX.model) && (EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION) < (BL0940_IREF / 20))) { + uint32_t current_calibration = EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION) * 100; + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, current_calibration); } if (BL0942_MODEL != Bl09XX.model) { diff --git a/tasmota/tasmota_xnrg_energy/xnrg_19_cse7761.ino b/tasmota/tasmota_xnrg_energy/xnrg_19_cse7761.ino index a75035b5b..17ad3ad7f 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_19_cse7761.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_19_cse7761.ino @@ -221,17 +221,17 @@ bool Cse7761ChipInit(void) { CSE7761Data.coefficient[PowerPAC] = CSE7761_PREF; // CSE7761Data.coefficient[PowerPBC] = 0xADD7; } - if (HLW_PREF_PULSE == Settings->energy_power_calibration) { - Settings->energy_frequency_calibration = CSE7761_FREF; - Settings->energy_voltage_calibration = Cse7761Ref(RmsUC); - Settings->energy_current_calibration = Cse7761Ref(RmsIAC); - Settings->energy_power_calibration = Cse7761Ref(PowerPAC); - Settings->energy_current_calibration2 = Settings->energy_current_calibration; - Settings->energy_power_calibration2 = Settings->energy_power_calibration; + if (HLW_PREF_PULSE == EnergyGetCalibration(ENERGY_POWER_CALIBRATION)) { + for (uint32_t i = 0; i < 2; i++) { + EnergySetCalibration(ENERGY_POWER_CALIBRATION, Cse7761Ref(PowerPAC), i); + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, Cse7761Ref(RmsUC), i); + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, Cse7761Ref(RmsIAC), i); + EnergySetCalibration(ENERGY_FREQUENCY_CALIBRATION, CSE7761_FREF, i); + } } // Just to fix intermediate users - if (Settings->energy_frequency_calibration < CSE7761_FREF / 2) { - Settings->energy_frequency_calibration = CSE7761_FREF; + if (EnergyGetCalibration(ENERGY_FREQUENCY_CALIBRATION) < CSE7761_FREF / 2) { + EnergySetCalibration(ENERGY_FREQUENCY_CALIBRATION, CSE7761_FREF); } Cse7761Write(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE); @@ -461,21 +461,21 @@ void Cse7761GetData(void) { if (Energy->power_on) { // Powered on // Voltage = RmsU * RmsUC * 10 / 0x400000 // Energy->voltage[0] = (float)(((uint64_t)CSE7761Data.voltage_rms * CSE7761Data.coefficient[RmsUC] * 10) >> 22) / 1000; // V - Energy->voltage[0] = ((float)CSE7761Data.voltage_rms / Settings->energy_voltage_calibration); // V + Energy->voltage[0] = ((float)CSE7761Data.voltage_rms / EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION)); // V #ifdef CSE7761_FREQUENCY - Energy->frequency[0] = (CSE7761Data.frequency) ? ((float)Settings->energy_frequency_calibration / 8 / CSE7761Data.frequency) : 0; // Hz + Energy->frequency[0] = (CSE7761Data.frequency) ? ((float)EnergyGetCalibration(ENERGY_FREQUENCY_CALIBRATION) / 8 / CSE7761Data.frequency) : 0; // Hz #endif for (uint32_t channel = 0; channel < 2; channel++) { Energy->data_valid[channel] = 0; - uint32_t power_calibration = EnergyGetCalibration(channel, ENERGY_POWER_CALIBRATION); + uint32_t power_calibration = EnergyGetCalibration(ENERGY_POWER_CALIBRATION, channel); // Active power = PowerPA * PowerPAC * 1000 / 0x80000000 // Energy->active_power[channel] = (float)(((uint64_t)CSE7761Data.active_power[channel] * CSE7761Data.coefficient[PowerPAC + channel] * 1000) >> 31) / 1000; // W Energy->active_power[channel] = (float)CSE7761Data.active_power[channel] / power_calibration; // W if (0 == Energy->active_power[channel]) { Energy->current[channel] = 0; } else { - uint32_t current_calibration = EnergyGetCalibration(channel, ENERGY_CURRENT_CALIBRATION); + uint32_t current_calibration = EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION, channel); // Current = RmsIA * RmsIAC / 0x800000 // Energy->current[channel] = (float)(((uint64_t)CSE7761Data.current_rms[channel] * CSE7761Data.coefficient[RmsIAC + channel]) >> 23) / 1000; // A Energy->current[channel] = (float)CSE7761Data.current_rms[channel] / current_calibration; // A diff --git a/tasmota/tasmota_xnrg_energy/xnrg_22_bl6523.ino b/tasmota/tasmota_xnrg_energy/xnrg_22_bl6523.ino index 383b19073..6df796461 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_22_bl6523.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_22_bl6523.ino @@ -162,16 +162,16 @@ RX: 35 0C TX: 00 00 00 F3 (WATT_HR) switch(rx_buffer[1]) { case BL6523_REG_AMPS : - Energy->current[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / Settings->energy_current_calibration; // 1.260 A + Energy->current[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION); // 1.260 A break; case BL6523_REG_VOLTS : - Energy->voltage[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / Settings->energy_voltage_calibration; // 230.2 V + Energy->voltage[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION); // 230.2 V break; case BL6523_REG_FREQ : - Energy->frequency[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / Settings->energy_frequency_calibration; // 50.0 Hz + Energy->frequency[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / EnergyGetCalibration(ENERGY_FREQUENCY_CALIBRATION); // 50.0 Hz break; case BL6523_REG_WATTS : - Energy->active_power[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / Settings->energy_power_calibration; // -196.3 W + Energy->active_power[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / EnergyGetCalibration(ENERGY_POWER_CALIBRATION); // -196.3 W break; case BL6523_REG_POWF : /* Power factor =(sign bit)*((PF[22]×2^-1)+(PF[21]×2^-2)+。。。) @@ -188,7 +188,7 @@ switch(rx_buffer[1]) { Energy->power_factor[SINGLE_PHASE] = powf; break; case BL6523_REG_WATTHR : - Energy->import_active[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / ( Settings->energy_power_calibration - BL6523_PWHRREF_D ); // 6.216 kWh => used in EnergyUpdateTotal() + Energy->import_active[SINGLE_PHASE] = (float)((tx_buffer[2] << 16) | (tx_buffer[1] << 8) | tx_buffer[0]) / ( EnergyGetCalibration(ENERGY_POWER_CALIBRATION) - BL6523_PWHRREF_D ); // 6.216 kWh => used in EnergyUpdateTotal() break; default : break; @@ -310,13 +310,12 @@ void Bl6523DrvInit(void) if (PinUsed(GPIO_BL6523_RX) && PinUsed(GPIO_BL6523_TX)) { AddLog(LOG_LEVEL_DEBUG, PSTR("BL6:PreInit Success" )); TasmotaGlobal.energy_driver = XNRG_22; - if (HLW_PREF_PULSE == Settings->energy_power_calibration) { - Settings->energy_frequency_calibration = BL6523_FREF; - Settings->energy_voltage_calibration = BL6523_UREF; - Settings->energy_current_calibration = BL6523_IREF; - Settings->energy_power_calibration = BL6523_PREF; + if (HLW_PREF_PULSE == EnergyGetCalibration(ENERGY_POWER_CALIBRATION)) { + EnergySetCalibration(ENERGY_POWER_CALIBRATION, BL6523_PREF); + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, BL6523_UREF); + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, BL6523_IREF); + EnergySetCalibration(ENERGY_FREQUENCY_CALIBRATION, BL6523_FREF); } - } else { diff --git a/tasmota/tasmota_xnrg_energy/xnrg_30_dummy.ino b/tasmota/tasmota_xnrg_energy/xnrg_30_dummy.ino index dd198e8ad..49de5be28 100644 --- a/tasmota/tasmota_xnrg_energy/xnrg_30_dummy.ino +++ b/tasmota/tasmota_xnrg_energy/xnrg_30_dummy.ino @@ -56,10 +56,10 @@ void NrgDummyEverySecond(void) { if (Energy->power_on) { // Powered on for (uint32_t channel = 0; channel < Energy->phase_count; channel++) { - float power_calibration = (float)EnergyGetCalibration(channel, ENERGY_POWER_CALIBRATION) / 100; - float voltage_calibration = (float)EnergyGetCalibration(channel, ENERGY_VOLTAGE_CALIBRATION) / 100; - float current_calibration = (float)EnergyGetCalibration(channel, ENERGY_CURRENT_CALIBRATION) / 100000; - float frequency_calibration = (float)EnergyGetCalibration(channel, ENERGY_FREQUENCY_CALIBRATION) / 100; + float power_calibration = (float)EnergyGetCalibration(ENERGY_POWER_CALIBRATION, channel) / 100; + float voltage_calibration = (float)EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION, channel) / 100; + float current_calibration = (float)EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION, channel) / 100000; + float frequency_calibration = (float)EnergyGetCalibration(ENERGY_FREQUENCY_CALIBRATION, channel) / 100; Energy->voltage[channel] = voltage_calibration; // V Energy->frequency[channel] = frequency_calibration; // Hz @@ -135,17 +135,17 @@ bool NrgDummyCommand(void) { void NrgDummyDrvInit(void) { if (TasmotaGlobal.gpio_optiona.dummy_energy && TasmotaGlobal.devices_present) { - if (HLW_PREF_PULSE == Settings->energy_power_calibration) { - Settings->energy_frequency_calibration = NRG_DUMMY_FREF; - Settings->energy_voltage_calibration = NRG_DUMMY_UREF; - Settings->energy_current_calibration = NRG_DUMMY_IREF; - Settings->energy_power_calibration = NRG_DUMMY_PREF; - Settings->energy_voltage_calibration2 = NRG_DUMMY_UREF; - Settings->energy_current_calibration2 = NRG_DUMMY_IREF; - Settings->energy_power_calibration2 = NRG_DUMMY_PREF; + Energy->phase_count = (TasmotaGlobal.devices_present < ENERGY_MAX_PHASES) ? TasmotaGlobal.devices_present : ENERGY_MAX_PHASES; + + if (HLW_PREF_PULSE == EnergyGetCalibration(ENERGY_POWER_CALIBRATION)) { + for (uint32_t i = 0; i < Energy->phase_count; i++) { + EnergySetCalibration(ENERGY_POWER_CALIBRATION, NRG_DUMMY_PREF, i); + EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, NRG_DUMMY_UREF, i); + EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, NRG_DUMMY_IREF, i); + EnergySetCalibration(ENERGY_FREQUENCY_CALIBRATION, NRG_DUMMY_FREF, i); + } } - Energy->phase_count = (TasmotaGlobal.devices_present < ENERGY_MAX_PHASES) ? TasmotaGlobal.devices_present : ENERGY_MAX_PHASES; Energy->voltage_common = NRG_DUMMY_U_COMMON; // Phase voltage = false, Common voltage = true Energy->frequency_common = NRG_DUMMY_F_COMMON; // Phase frequency = false, Common frequency = true Energy->type_dc = NRG_DUMMY_DC; // AC = false, DC = true;