diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index fd47efc7e..176c5c08a 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -6,6 +6,8 @@ * Add command Gpios 255/All to show all available GPIO components (#6407) * Change JSON output format for commands Adc, Adcs, Modules, Gpio and Gpios from list to dictionary (#6407) * Add Zigbee support phase 3 - support for Xiaomi lumi.weather air quality sensor, Osram mini-switch + * Change energy sensors for three phase/channel support + * Add Shelly 2.5 energy dual channel support (#6160) * * 6.6.0.11 20190907 * Change Settings crc calculation allowing short term backward compatibility diff --git a/sonoff/xdrv_03_energy.ino b/sonoff/xdrv_03_energy.ino index 35f65e575..1ad177b1f 100644 --- a/sonoff/xdrv_03_energy.ino +++ b/sonoff/xdrv_03_energy.ino @@ -68,33 +68,37 @@ void (* const EnergyCommand[])(void) PROGMEM = { #endif // USE_ENERGY_MARGIN_DETECTION &CmndEnergyReset, &CmndTariff }; +const char kEnergyPhases[] PROGMEM = "|%s / %s|%s / %s / %s||[%s,%s]|[%s,%s,%s]"; + struct ENERGY { - float voltage = 0; // 123.1 V - float current = 0; // 123.123 A - float active_power = 0; // 123.1 W - float apparent_power = NAN; // 123.1 VA - float reactive_power = NAN; // 123.1 VAr - float power_factor = NAN; // 0.12 - float frequency = NAN; // 123.1 Hz + float voltage[3] = { 0, 0, 0 }; // 123.1 V + float current[3] = { 0, 0, 0 }; // 123.123 A + float active_power[3] = { 0, 0, 0 }; // 123.1 W + float apparent_power[3] = { NAN, NAN, NAN }; // 123.1 VA + float reactive_power[3] = { NAN, NAN, NAN }; // 123.1 VAr + float power_factor[3] = { NAN, NAN, NAN }; // 0.12 + float frequency[3] = { NAN, NAN, NAN }; // 123.1 Hz - float start_energy = 0; // 12345.12345 kWh total previous + float start_energy = 0; // 12345.12345 kWh total previous + float daily = 0; // 123.123 kWh + float total = 0; // 12345.12345 kWh tariff 1 + 2 + float total1 = 0; // 12345.12345 kWh tariff 1 - off-peak + float export_active = NAN; // 123.123 KWh - float daily = 0; // 123.123 kWh - float total = 0; // 12345.12345 kWh tariff 1 + 2 - float total1 = 0; // 12345.12345 kWh tariff 1 - off-peak - float export_active = NAN; // 123.123 KWh - - unsigned long kWhtoday_delta = 0; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to Energy.kWhtoday (HLW and CSE only) - unsigned long kWhtoday_offset = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy.daily - unsigned long kWhtoday; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy.daily - unsigned long period = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy.daily + unsigned long kWhtoday_delta = 0; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to Energy.kWhtoday (HLW and CSE only) + unsigned long kWhtoday_offset = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy.daily + unsigned long kWhtoday; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy.daily + unsigned long period = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy.daily uint8_t fifth_second = 0; uint8_t command_code = 0; uint8_t data_valid = 0; - bool voltage_available = true; // Enable if voltage is measured - bool current_available = true; // Enable if current is measured + uint8_t phase_count = 1; // Number of phases active + bool voltage_common = false; // Use single voltage + + bool voltage_available = true; // Enable if voltage is measured + bool current_available = true; // Enable if current is measured bool type_dc = false; bool power_on = true; @@ -275,23 +279,23 @@ void EnergyMarginCheck(void) } if (Settings.energy_power_delta) { - float delta = abs(Energy.power_history[0] - Energy.active_power); + float delta = abs(Energy.power_history[0] - Energy.active_power[0]); // Any delta compared to minimal delta - float min_power = (Energy.power_history[0] > Energy.active_power) ? Energy.active_power : Energy.power_history[0]; + float min_power = (Energy.power_history[0] > Energy.active_power[0]) ? Energy.active_power[0] : Energy.power_history[0]; if (((delta / min_power) * 100) > Settings.energy_power_delta) { Energy.power_delta = 1; - Energy.power_history[1] = Energy.active_power; // We only want one report so reset history - Energy.power_history[2] = Energy.active_power; + Energy.power_history[1] = Energy.active_power[0]; // We only want one report so reset history + Energy.power_history[2] = Energy.active_power[0]; } } Energy.power_history[0] = Energy.power_history[1]; // Shift in history every second allowing power changes to settle for up to three seconds Energy.power_history[1] = Energy.power_history[2]; - Energy.power_history[2] = Energy.active_power; + Energy.power_history[2] = Energy.active_power[0]; if (Energy.power_on && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) { - energy_power_u = (uint16_t)(Energy.active_power); - energy_voltage_u = (uint16_t)(Energy.voltage); - energy_current_u = (uint16_t)(Energy.current * 1000); + energy_power_u = (uint16_t)(Energy.active_power[0]); + energy_voltage_u = (uint16_t)(Energy.voltage[0]); + 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); @@ -331,7 +335,7 @@ void EnergyMarginCheck(void) #ifdef USE_ENERGY_POWER_LIMIT // Max Power if (Settings.energy_max_power_limit) { - if (Energy.active_power > Settings.energy_max_power_limit) { + if (Energy.active_power[0] > Settings.energy_max_power_limit) { if (!Energy.mplh_counter) { Energy.mplh_counter = Settings.energy_max_power_limit_hold; } else { @@ -422,13 +426,15 @@ void EnergyOverTempCheck() Energy.data_valid++; if (Energy.data_valid > ENERGY_WATCHDOG) { // Reset energy registers - Energy.voltage = 0; - Energy.current = 0; - Energy.active_power = 0; - if (!isnan(Energy.apparent_power)) { Energy.apparent_power = 0; } - if (!isnan(Energy.reactive_power)) { Energy.reactive_power = 0; } - if (!isnan(Energy.frequency)) { Energy.frequency = 0; } - if (!isnan(Energy.power_factor)) { Energy.power_factor = 0; } + for (uint32_t i = 0; i < Energy.phase_count; i++) { + 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)) { Energy.export_active = 0; } Energy.start_energy = 0; @@ -778,55 +784,93 @@ const char HTTP_ENERGY_SNS3[] PROGMEM = "{s}" D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; #endif // USE_WEBSERVER +char* EnergyFormat(char* result, char* input, bool json, bool single = false) +{ + uint8_t index = (single) ? 1 : Energy.phase_count; // 1,2,3 + + char layout[16]; + GetTextIndexed(layout, sizeof(layout), (index -1) + (3 * json), kEnergyPhases); + switch (index) { + case 2: + snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ); // Dirty + break; + case 3: + snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ, input + FLOATSZ + FLOATSZ); // Even dirtier + break; + default: + snprintf_P(result, FLOATSZ *3, input); + } + return result; +} + void EnergyShow(bool json) { - float power_factor = Energy.power_factor; - - char apparent_power_chr[FLOATSZ]; - char reactive_power_chr[FLOATSZ]; - char power_factor_chr[FLOATSZ]; - char frequency_chr[FLOATSZ]; - if (!Energy.type_dc) { - if (Energy.current_available && Energy.voltage_available) { - float apparent_power = Energy.apparent_power; - if (isnan(apparent_power)) { - apparent_power = Energy.voltage * Energy.current; - } - if (apparent_power < Energy.active_power) { // Should be impossible - Energy.active_power = apparent_power; - } - - if (isnan(power_factor)) { - power_factor = (Energy.active_power && apparent_power) ? Energy.active_power / apparent_power : 0; - if (power_factor > 1) power_factor = 1; - } - - float reactive_power = Energy.reactive_power; - if (isnan(reactive_power)) { - reactive_power = 0; - uint32_t difference = ((uint32_t)(apparent_power * 100) - (uint32_t)(Energy.active_power * 100)) / 10; - if ((Energy.current > 0.005) && ((difference > 15) || (difference > (uint32_t)(apparent_power * 100 / 1000)))) { - // calculating reactive power only if current is greater than 0.005A and - // difference between active and apparent power is greater than 1.5W or 1% - reactive_power = (float)(RoundSqrtInt((uint32_t)(apparent_power * apparent_power * 100) - (uint32_t)(Energy.active_power * Energy.active_power * 100))) / 10; - } - } - - dtostrfd(apparent_power, Settings.flag2.wattage_resolution, apparent_power_chr); - dtostrfd(reactive_power, Settings.flag2.wattage_resolution, reactive_power_chr); - dtostrfd(power_factor, 2, power_factor_chr); - } - if (!isnan(Energy.frequency)) { - dtostrfd(Energy.frequency, Settings.flag2.frequency_resolution, frequency_chr); + for (uint32_t i = 0; i < Energy.phase_count; i++) { + if (Energy.voltage_common) { + Energy.voltage[i] = Energy.voltage[0]; } } - char voltage_chr[FLOATSZ]; - dtostrfd(Energy.voltage, Settings.flag2.voltage_resolution, voltage_chr); - char current_chr[FLOATSZ]; - dtostrfd(Energy.current, Settings.flag2.current_resolution, current_chr); - char active_power_chr[FLOATSZ]; - dtostrfd(Energy.active_power, Settings.flag2.wattage_resolution, active_power_chr); + float power_factor_knx = Energy.power_factor[0]; + + char apparent_power_chr[Energy.phase_count][FLOATSZ]; + char reactive_power_chr[Energy.phase_count][FLOATSZ]; + char power_factor_chr[Energy.phase_count][FLOATSZ]; + char frequency_chr[Energy.phase_count][FLOATSZ]; + if (!Energy.type_dc) { + if (Energy.current_available && Energy.voltage_available) { + for (uint32_t i = 0; i < Energy.phase_count; i++) { + float apparent_power = Energy.apparent_power[i]; + if (isnan(apparent_power)) { + apparent_power = Energy.voltage[i] * Energy.current[i]; + } + if (apparent_power < Energy.active_power[i]) { // Should be impossible + Energy.active_power[i] = apparent_power; + } + + float power_factor = Energy.power_factor[i]; + if (isnan(power_factor)) { + power_factor = (Energy.active_power[i] && apparent_power) ? Energy.active_power[i] / apparent_power : 0; + if (power_factor > 1) { + power_factor = 1; + } + } + if (0 == i) { power_factor_knx = power_factor; } + + float reactive_power = Energy.reactive_power[i]; + if (isnan(reactive_power)) { + reactive_power = 0; + uint32_t difference = ((uint32_t)(apparent_power * 100) - (uint32_t)(Energy.active_power[i] * 100)) / 10; + if ((Energy.current[i] > 0.005) && ((difference > 15) || (difference > (uint32_t)(apparent_power * 100 / 1000)))) { + // calculating reactive power only if current is greater than 0.005A and + // difference between active and apparent power is greater than 1.5W or 1% + reactive_power = (float)(RoundSqrtInt((uint32_t)(apparent_power * apparent_power * 100) - (uint32_t)(Energy.active_power[i] * Energy.active_power[i] * 100))) / 10; + } + } + + dtostrfd(apparent_power, Settings.flag2.wattage_resolution, apparent_power_chr[i]); + dtostrfd(reactive_power, Settings.flag2.wattage_resolution, reactive_power_chr[i]); + dtostrfd(power_factor, 2, power_factor_chr[i]); + } + } + for (uint32_t i = 0; i < Energy.phase_count; i++) { + float frequency = Energy.frequency[i]; + if (isnan(Energy.frequency[i])) { + frequency = 0; + } + dtostrfd(frequency, Settings.flag2.frequency_resolution, frequency_chr[i]); + } + } + + char voltage_chr[Energy.phase_count][FLOATSZ]; + char current_chr[Energy.phase_count][FLOATSZ]; + char active_power_chr[Energy.phase_count][FLOATSZ]; + for (uint32_t i = 0; i < Energy.phase_count; i++) { + dtostrfd(Energy.voltage[i], Settings.flag2.voltage_resolution, voltage_chr[i]); + dtostrfd(Energy.current[i], Settings.flag2.current_resolution, current_chr[i]); + dtostrfd(Energy.active_power[i], Settings.flag2.wattage_resolution, active_power_chr[i]); + } + char energy_daily_chr[FLOATSZ]; dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr); char energy_yesterday_chr[FLOATSZ]; @@ -836,6 +880,10 @@ void EnergyShow(bool json) char export_active_chr[FLOATSZ]; dtostrfd(Energy.export_active, Settings.flag2.energy_resolution, export_active_chr); + char value_chr[FLOATSZ *3]; + char value2_chr[FLOATSZ *3]; + char value3_chr[FLOATSZ *3]; + if (json) { bool show_energy_period = (0 == tele_period); @@ -854,21 +902,27 @@ void EnergyShow(bool json) dtostrfd(energy, Settings.flag2.wattage_resolution, energy_period_chr); ResponseAppend_P(PSTR(",\"" D_JSON_PERIOD "\":%s"), energy_period_chr); } - ResponseAppend_P(PSTR(",\"" D_JSON_POWERUSAGE "\":%s"), active_power_chr); + ResponseAppend_P(PSTR(",\"" D_JSON_POWERUSAGE "\":%s"), + EnergyFormat(value_chr, active_power_chr[0], json)); if (!Energy.type_dc) { if (Energy.current_available && Energy.voltage_available) { ResponseAppend_P(PSTR(",\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_POWERFACTOR "\":%s"), - apparent_power_chr, reactive_power_chr, power_factor_chr); + EnergyFormat(value_chr, apparent_power_chr[0], json), + EnergyFormat(value2_chr, reactive_power_chr[0], json), + EnergyFormat(value3_chr, power_factor_chr[0], json)); } - if (!isnan(Energy.frequency)) { - ResponseAppend_P(PSTR(",\"" D_JSON_FREQUENCY "\":%s"), frequency_chr); + if (!isnan(Energy.frequency[0])) { + ResponseAppend_P(PSTR(",\"" D_JSON_FREQUENCY "\":%s"), + EnergyFormat(value_chr, frequency_chr[0], json)); } } if (Energy.voltage_available) { - ResponseAppend_P(PSTR(",\"" D_JSON_VOLTAGE "\":%s"), voltage_chr); + ResponseAppend_P(PSTR(",\"" D_JSON_VOLTAGE "\":%s"), + EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common)); } if (Energy.current_available) { - ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), current_chr); + ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"), + EnergyFormat(value_chr, current_chr[0], json)); } XnrgCall(FUNC_JSON_APPEND); ResponseJsonEnd(); @@ -876,7 +930,7 @@ void EnergyShow(bool json) #ifdef USE_DOMOTICZ if (show_energy_period) { // Only send if telemetry dtostrfd(Energy.total * 1000, 1, energy_total_chr); - DomoticzSensorPowerEnergy((int)Energy.active_power, energy_total_chr); // PowerUsage, EnergyToday + DomoticzSensorPowerEnergy((int)Energy.active_power[0], energy_total_chr); // PowerUsage, EnergyToday dtostrfd((Energy.total - Energy.total1) * 1000, 1, energy_total_chr); // Tariff2 char energy_total1_chr[FLOATSZ]; @@ -885,26 +939,28 @@ void EnergyShow(bool json) dtostrfd(RtcSettings.energy_usage.return1_kWhtotal, 1, return1_total_chr); char return2_total_chr[FLOATSZ]; dtostrfd(RtcSettings.energy_usage.return2_kWhtotal, 1, return2_total_chr); - DomoticzSensorP1SmartMeter(energy_total1_chr, energy_total_chr, return1_total_chr, return2_total_chr, (int)Energy.active_power); + DomoticzSensorP1SmartMeter(energy_total1_chr, energy_total_chr, return1_total_chr, return2_total_chr, (int)Energy.active_power[0]); if (Energy.voltage_available) { - DomoticzSensor(DZ_VOLTAGE, voltage_chr); // Voltage + DomoticzSensor(DZ_VOLTAGE, voltage_chr[0]); // Voltage } if (Energy.current_available) { - DomoticzSensor(DZ_CURRENT, current_chr); // Current + DomoticzSensor(DZ_CURRENT, current_chr[0]); // Current } } #endif // USE_DOMOTICZ #ifdef USE_KNX if (show_energy_period) { if (Energy.voltage_available) { - KnxSensor(KNX_ENERGY_VOLTAGE, Energy.voltage); + KnxSensor(KNX_ENERGY_VOLTAGE, Energy.voltage[0]); } if (Energy.current_available) { - KnxSensor(KNX_ENERGY_CURRENT, Energy.current); + KnxSensor(KNX_ENERGY_CURRENT, Energy.current[0]); + } + KnxSensor(KNX_ENERGY_POWER, Energy.active_power[0]); + if (!Energy.type_dc) { + KnxSensor(KNX_ENERGY_POWERFACTOR, power_factor_knx); } - KnxSensor(KNX_ENERGY_POWER, Energy.active_power); - if (!Energy.type_dc) { KnxSensor(KNX_ENERGY_POWERFACTOR, power_factor); } KnxSensor(KNX_ENERGY_DAILY, Energy.daily); KnxSensor(KNX_ENERGY_TOTAL, Energy.total); KnxSensor(KNX_ENERGY_START, Energy.start_energy); @@ -913,18 +969,24 @@ void EnergyShow(bool json) #ifdef USE_WEBSERVER } else { if (Energy.voltage_available) { - WSContentSend_PD(PSTR("{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"), voltage_chr); + WSContentSend_PD(PSTR("{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"), + EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common)); } if (Energy.current_available) { - WSContentSend_PD(PSTR("{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"), current_chr); + WSContentSend_PD(PSTR("{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"), + EnergyFormat(value_chr, current_chr[0], json)); } - WSContentSend_PD(PSTR("{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"), active_power_chr); + WSContentSend_PD(PSTR("{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"), + EnergyFormat(value_chr, active_power_chr[0], json)); if (!Energy.type_dc) { if (Energy.current_available && Energy.voltage_available) { - WSContentSend_PD(HTTP_ENERGY_SNS1, apparent_power_chr, reactive_power_chr, power_factor_chr); + WSContentSend_PD(HTTP_ENERGY_SNS1, EnergyFormat(value_chr, apparent_power_chr[0], json), + EnergyFormat(value2_chr, reactive_power_chr[0], json), + EnergyFormat(value3_chr, power_factor_chr[0], json)); } - if (!isnan(Energy.frequency)) { - WSContentSend_PD(PSTR("{s}" D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"), frequency_chr); + if (!isnan(Energy.frequency[0])) { + WSContentSend_PD(PSTR("{s}" D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"), + EnergyFormat(value_chr, frequency_chr[0], json)); } } WSContentSend_PD(HTTP_ENERGY_SNS2, energy_daily_chr, energy_yesterday_chr, energy_total_chr); diff --git a/sonoff/xdrv_16_tuyamcu.ino b/sonoff/xdrv_16_tuyamcu.ino index 6d9b26367..d849d4712 100644 --- a/sonoff/xdrv_16_tuyamcu.ino +++ b/sonoff/xdrv_16_tuyamcu.ino @@ -386,17 +386,17 @@ void TuyaPacketProcess(void) #ifdef USE_ENERGY_SENSOR else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_VOLTAGE) { - Energy.voltage = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10; + Energy.voltage[0] = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Voltage=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13])); } else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_CURRENT) { - Energy.current = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 1000; + Energy.current[0] = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 1000; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Current=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13])); } else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_POWER) { - Energy.active_power = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10; + Energy.active_power[0] = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Active_Power=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13])); - if (Tuya.lastPowerCheckTime != 0 && Energy.active_power > 0) { - Energy.kWhtoday += (float)Energy.active_power * (Rtc.utc_time - Tuya.lastPowerCheckTime) / 36; + if (Tuya.lastPowerCheckTime != 0 && Energy.active_power[0] > 0) { + Energy.kWhtoday += (float)Energy.active_power[0] * (Rtc.utc_time - Tuya.lastPowerCheckTime) / 36; EnergyUpdateToday(); } Tuya.lastPowerCheckTime = Rtc.utc_time; diff --git a/sonoff/xnrg_01_hlw8012.ino b/sonoff/xnrg_01_hlw8012.ino index 7bf039999..f2bd96734 100644 --- a/sonoff/xnrg_01_hlw8012.ino +++ b/sonoff/xnrg_01_hlw8012.ino @@ -128,13 +128,13 @@ void HlwEvery200ms(void) 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 - Energy.active_power = (float)hlw_w / 10; + Energy.active_power[0] = (float)hlw_w / 10; Hlw.power_retry = 1; // Workaround issue #5161 } else { if (Hlw.power_retry) { Hlw.power_retry--; } else { - Energy.active_power = 0; + Energy.active_power[0] = 0; } } @@ -175,19 +175,19 @@ void HlwEvery200ms(void) 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 - Energy.voltage = (float)hlw_u / 10; + Energy.voltage[0] = (float)hlw_u / 10; } else { - Energy.voltage = 0; + Energy.voltage[0] = 0; } } else { Hlw.cf1_current_pulse_length = cf1_pulse_length; - if (Hlw.cf1_current_pulse_length && Energy.active_power) { // No current if no power being consumed + 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 - Energy.current = (float)hlw_i / 1000; + Energy.current[0] = (float)hlw_i / 1000; } else { - Energy.current = 0; + Energy.current[0] = 0; } } diff --git a/sonoff/xnrg_02_cse7766.ino b/sonoff/xnrg_02_cse7766.ino index a8ab38916..5fd927c28 100644 --- a/sonoff/xnrg_02_cse7766.ino +++ b/sonoff/xnrg_02_cse7766.ino @@ -52,8 +52,9 @@ struct CSE { void CseReceived(void) { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - // 55 5A 02 F7 60 00 03 5A 00 40 10 04 8B 9F 51 A6 58 18 72 75 61 AC A1 30 - Power not valid (load below 5W) - // 55 5A 02 F7 60 00 03 AB 00 40 10 02 60 5D 51 A6 58 03 E9 EF 71 0B 7A 36 + // F2 5A 02 F7 60 00 03 61 00 40 10 05 72 40 51 A6 58 63 10 1B E1 7F 4D 4E - F2 = Power cycle exceeds range - takes too long - No load + // 55 5A 02 F7 60 00 03 5A 00 40 10 04 8B 9F 51 A6 58 18 72 75 61 AC A1 30 - 55 = Ok, 61 = Power not valid (load below 5W) + // 55 5A 02 F7 60 00 03 AB 00 40 10 02 60 5D 51 A6 58 03 E9 EF 71 0B 7A 36 - 55 = Ok, 71 = Ok // Hd Id VCal---- Voltage- ICal---- Current- PCal---- Power--- Ad CF--- Ck uint8_t header = serial_in_buffer[0]; @@ -93,19 +94,19 @@ void CseReceived(void) if (Energy.power_on) { // Powered on if (adjustement & 0x40) { // Voltage valid - Energy.voltage = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)Cse.voltage_cycle; + Energy.voltage[0] = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)Cse.voltage_cycle; } if (adjustement & 0x10) { // Power valid Cse.power_invalid = 0; if ((header & 0xF2) == 0xF2) { // Power cycle exceeds range - Energy.active_power = 0; + Energy.active_power[0] = 0; } else { 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 = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)Cse.power_cycle; + Energy.active_power[0] = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)Cse.power_cycle; } else { - Energy.active_power = 0; + Energy.active_power[0] = 0; } } } else { @@ -113,21 +114,21 @@ void CseReceived(void) Cse.power_invalid++; } else { Cse.power_cycle_first = 0; - Energy.active_power = 0; // Powered on but no load + Energy.active_power[0] = 0; // Powered on but no load } } if (adjustement & 0x20) { // Current valid - if (0 == Energy.active_power) { - Energy.current = 0; + if (0 == Energy.active_power[0]) { + Energy.current[0] = 0; } else { - Energy.current = (float)Settings.energy_current_calibration / (float)Cse.current_cycle; + Energy.current[0] = (float)Settings.energy_current_calibration / (float)Cse.current_cycle; } } } else { // Powered off Cse.power_cycle_first = 0; - Energy.voltage = 0; - Energy.active_power = 0; - Energy.current = 0; + Energy.voltage[0] = 0; + Energy.active_power[0] = 0; + Energy.current[0] = 0; } } @@ -189,7 +190,7 @@ void CseEverySecond(void) } else { cf_frequency = Cse.cf_pulses - Cse.cf_pulses_last_time; } - if (cf_frequency && Energy.active_power) { + if (cf_frequency && Energy.active_power[0]) { unsigned long delta = (cf_frequency * Settings.energy_power_calibration) / 36; // prevent invalid load delta steps even checksum is valid (issue #5789): if (delta <= (3680*100/36) * 10 ) { // max load for S31/Pow R2: 3.68kW diff --git a/sonoff/xnrg_03_pzem004t.ino b/sonoff/xnrg_03_pzem004t.ino index 1fc6e5e5e..c0dff42df 100644 --- a/sonoff/xnrg_03_pzem004t.ino +++ b/sonoff/xnrg_03_pzem004t.ino @@ -172,13 +172,13 @@ void PzemEvery200ms(void) Energy.data_valid = 0; switch (pzem_read_state) { case 1: // Voltage as 230.2V - Energy.voltage = value; + Energy.voltage[0] = value; break; case 2: // Current as 17.32A - Energy.current = value; + Energy.current[0] = value; break; case 3: // Power as 20W - Energy.active_power = value; + Energy.active_power[0] = value; break; case 4: // Total energy as 99999Wh EnergyUpdateTotal(value, false); diff --git a/sonoff/xnrg_04_mcp39f501.ino b/sonoff/xnrg_04_mcp39f501.ino index 01b915954..462049b0d 100644 --- a/sonoff/xnrg_04_mcp39f501.ino +++ b/sonoff/xnrg_04_mcp39f501.ino @@ -455,19 +455,19 @@ void McpParseData(void) mcp_line_frequency = McpExtractInt(mcp_buffer, 22, 2); if (Energy.power_on) { // Powered on - Energy.frequency = (float)mcp_line_frequency / 1000; - Energy.voltage = (float)mcp_voltage_rms / 10; - Energy.active_power = (float)mcp_active_power / 100; - if (0 == Energy.active_power) { - Energy.current = 0; + Energy.frequency[0] = (float)mcp_line_frequency / 1000; + Energy.voltage[0] = (float)mcp_voltage_rms / 10; + Energy.active_power[0] = (float)mcp_active_power / 100; + if (0 == Energy.active_power[0]) { + Energy.current[0] = 0; } else { - Energy.current = (float)mcp_current_rms / 10000; + Energy.current[0] = (float)mcp_current_rms / 10000; } } else { // Powered off - Energy.frequency = 0; - Energy.voltage = 0; - Energy.active_power = 0; - Energy.current = 0; + Energy.frequency[0] = 0; + Energy.voltage[0] = 0; + Energy.active_power[0] = 0; + Energy.current[0] = 0; } Energy.data_valid = 0; } diff --git a/sonoff/xnrg_05_pzem_ac.ino b/sonoff/xnrg_05_pzem_ac.ino index 12b08eb8d..788838c5e 100644 --- a/sonoff/xnrg_05_pzem_ac.ino +++ b/sonoff/xnrg_05_pzem_ac.ino @@ -56,11 +56,11 @@ void PzemAcEverySecond(void) // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 01 04 14 08 D1 00 6C 00 00 00 F4 00 00 00 26 00 00 01 F4 00 64 00 00 51 34 // Id Cc Sz Volt- Current---- Power------ Energy----- Frequ PFact Alarm Crc-- - Energy.voltage = (float)((buffer[3] << 8) + buffer[4]) / 10.0; // 6553.0 V - Energy.current = (float)((buffer[7] << 24) + (buffer[8] << 16) + (buffer[5] << 8) + buffer[6]) / 1000.0; // 4294967.000 A - Energy.active_power = (float)((buffer[11] << 24) + (buffer[12] << 16) + (buffer[9] << 8) + buffer[10]) / 10.0; // 429496729.0 W - Energy.frequency = (float)((buffer[17] << 8) + buffer[18]) / 10.0; // 50.0 Hz - Energy.power_factor = (float)((buffer[19] << 8) + buffer[20]) / 100.0; // 1.00 + Energy.voltage[0] = (float)((buffer[3] << 8) + buffer[4]) / 10.0; // 6553.0 V + Energy.current[0] = (float)((buffer[7] << 24) + (buffer[8] << 16) + (buffer[5] << 8) + buffer[6]) / 1000.0; // 4294967.000 A + Energy.active_power[0] = (float)((buffer[11] << 24) + (buffer[12] << 16) + (buffer[9] << 8) + buffer[10]) / 10.0; // 429496729.0 W + Energy.frequency[0] = (float)((buffer[17] << 8) + buffer[18]) / 10.0; // 50.0 Hz + Energy.power_factor[0] = (float)((buffer[19] << 8) + buffer[20]) / 100.0; // 1.00 float energy = (float)((buffer[15] << 24) + (buffer[16] << 16) + (buffer[13] << 8) + buffer[14]); // 4294967295 Wh EnergyUpdateTotal(energy, false); diff --git a/sonoff/xnrg_06_pzem_dc.ino b/sonoff/xnrg_06_pzem_dc.ino index 855d36457..9b9dd06a1 100644 --- a/sonoff/xnrg_06_pzem_dc.ino +++ b/sonoff/xnrg_06_pzem_dc.ino @@ -56,9 +56,9 @@ void PzemDcEverySecond(void) // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 01 04 10 05 40 00 0A 00 0D 00 00 00 02 00 00 00 00 00 00 D6 29 // Id Cc Sz Volt- Curre Power------ Energy----- HiAlm LoAlm Crc-- - Energy.voltage = (float)((buffer[3] << 8) + buffer[4]) / 100.0; // 655.00 V - Energy.current = (float)((buffer[5] << 8) + buffer[6]) / 100.0; // 655.00 A - Energy.active_power = (float)((buffer[9] << 24) + (buffer[10] << 16) + (buffer[7] << 8) + buffer[8]) / 10.0; // 429496729.0 W + Energy.voltage[0] = (float)((buffer[3] << 8) + buffer[4]) / 100.0; // 655.00 V + Energy.current[0] = (float)((buffer[5] << 8) + buffer[6]) / 100.0; // 655.00 A + Energy.active_power[0] = (float)((buffer[9] << 24) + (buffer[10] << 16) + (buffer[7] << 8) + buffer[8]) / 10.0; // 429496729.0 W float energy = (float)((buffer[13] << 24) + (buffer[14] << 16) + (buffer[11] << 8) + buffer[12]); // 4294967295 Wh EnergyUpdateTotal(energy, false); diff --git a/sonoff/xnrg_07_ade7953.ino b/sonoff/xnrg_07_ade7953.ino index dcb958f63..4d2b3f3e4 100644 --- a/sonoff/xnrg_07_ade7953.ino +++ b/sonoff/xnrg_07_ade7953.ino @@ -36,14 +36,22 @@ #define ADE7953_ADDR 0x38 +const uint8_t Ade7953Registers[] { + 0x1B, // RMS current channel B (Relay 1) + 0x13, // Active power channel B + 0x11, // Apparent power channel B + 0x15, // Reactive power channel B + 0x1A, // RMS current channel A (Relay 2) + 0x12, // Active power channel A + 0x10, // Apparent power channel A + 0x14, // Reactive power channel A + 0x1C // RMS voltage (Both relays) +}; + struct Ade7953 { - uint32_t active_power = 0; - uint32_t active_power1 = 0; - uint32_t active_power2 = 0; - uint32_t current_rms = 0; - uint32_t current_rms1 = 0; - uint32_t current_rms2 = 0; uint32_t voltage_rms = 0; + uint32_t current_rms[2] = { 0, 0 }; + uint32_t active_power[2] = { 0, 0 }; uint8_t init_step = 0; } Ade7953; @@ -80,7 +88,7 @@ void Ade7953Write(uint16_t reg, uint32_t val) } } -uint32_t Ade7953Read(uint16_t reg) +int32_t Ade7953Read(uint16_t reg) { uint32_t response = 0; @@ -109,48 +117,65 @@ void Ade7953Init(void) void Ade7953GetData(void) { - int32_t active_power; + int32_t reg[2][4]; + for (uint32_t i = 0; i < sizeof(Ade7953Registers); i++) { + int32_t value = Ade7953Read(0x300 + Ade7953Registers[i]); + if (8 == i) { + Ade7953.voltage_rms = value; // RMS voltage (Both relays) + } else { + reg[i >> 2][i &3] = value; + } + } + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ADE: %d, [%d, %d, %d, %d], [%d, %d, %d, %d]"), + Ade7953.voltage_rms, reg[0][0], reg[0][1], reg[0][2], reg[0][3], + reg[1][0], reg[1][1], reg[1][2], reg[1][3]); - Ade7953.voltage_rms = Ade7953Read(0x31C); // Both relays - Ade7953.current_rms1 = Ade7953Read(0x31B); // Relay 1 - if (Ade7953.current_rms1 < 2000) { // No load threshold (20mA) - Ade7953.current_rms1 = 0; - Ade7953.active_power1 = 0; - } else { - active_power = (int32_t)Ade7953Read(0x313) * -1; // Relay 1 - Ade7953.active_power1 = (active_power > 0) ? active_power : 0; + uint32_t apparent_power[2] = { 0, 0 }; + uint32_t reactive_power[2] = { 0, 0 }; + + for (uint32_t channel = 0; channel < 2; channel++) { + Ade7953.current_rms[channel] = reg[channel][0]; + if (Ade7953.current_rms[channel] < 2000) { // No load threshold (20mA) + Ade7953.current_rms[channel] = 0; + Ade7953.active_power[channel] = 0; + } else { + Ade7953.active_power[channel] = abs(reg[channel][1]); + apparent_power[channel] = abs(reg[channel][2]); + reactive_power[channel] = abs(reg[channel][3]); + } } - Ade7953.current_rms2 = Ade7953Read(0x31A); // Relay 2 - if (Ade7953.current_rms2 < 2000) { // No load threshold (20mA) - Ade7953.current_rms2 = 0; - Ade7953.active_power2 = 0; - } else { - active_power = (int32_t)Ade7953Read(0x312); // Relay 2 - Ade7953.active_power2 = (active_power > 0) ? active_power : 0; - } - // First phase only supports accumulated Current and Power - Ade7953.current_rms = Ade7953.current_rms1 + Ade7953.current_rms2; - Ade7953.active_power = Ade7953.active_power1 + Ade7953.active_power2; + + uint32_t current_rms_sum = Ade7953.current_rms[0] + Ade7953.current_rms[1]; + uint32_t active_power_sum = Ade7953.active_power[0] + Ade7953.active_power[1]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ADE: U %d, I %d + %d = %d, P %d + %d = %d"), - Ade7953.voltage_rms, Ade7953.current_rms1, Ade7953.current_rms2, Ade7953.current_rms, Ade7953.active_power1, Ade7953.active_power2, Ade7953.active_power); + Ade7953.voltage_rms, Ade7953.current_rms[0], Ade7953.current_rms[1], current_rms_sum, Ade7953.active_power[0], Ade7953.active_power[1], active_power_sum); if (Energy.power_on) { // Powered on - Energy.voltage = (float)Ade7953.voltage_rms / Settings.energy_voltage_calibration; - Energy.active_power = (float)Ade7953.active_power / (Settings.energy_power_calibration / 10); - if (0 == Energy.active_power) { - Energy.current = 0; - } else { - Energy.current = (float)Ade7953.current_rms / (Settings.energy_current_calibration * 10); + Energy.voltage[0] = (float)Ade7953.voltage_rms / Settings.energy_voltage_calibration; + + for (uint32_t channel = 0; channel < 2; channel++) { + Energy.active_power[channel] = (float)Ade7953.active_power[channel] / (Settings.energy_power_calibration / 10); + Energy.reactive_power[channel] = (float)reactive_power[channel] / (Settings.energy_power_calibration / 10); + Energy.apparent_power[channel] = (float)apparent_power[channel] / (Settings.energy_power_calibration / 10); + if (0 == Energy.active_power[channel]) { + Energy.current[channel] = 0; + } else { + Energy.current[channel] = (float)Ade7953.current_rms[channel] / (Settings.energy_current_calibration * 10); + } } } else { // Powered off - Energy.voltage = 0; - Energy.active_power = 0; - Energy.current = 0; + Energy.voltage[0] = 0; + for (uint32_t channel = 0; channel < 2; channel++) { + Energy.current[channel] = 0; + Energy.active_power[channel] = 0; + Energy.reactive_power[channel] = 0; + Energy.apparent_power[channel] = 0; + } } - if (Ade7953.active_power) { - Energy.kWhtoday_delta += ((Ade7953.active_power * (100000 / (Settings.energy_power_calibration / 10))) / 3600); + if (active_power_sum) { + Energy.kWhtoday_delta += ((active_power_sum * (100000 / (Settings.energy_power_calibration / 10))) / 3600); EnergyUpdateToday(); } } @@ -179,6 +204,10 @@ void Ade7953DrvInit(void) } AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "ADE7953", ADE7953_ADDR); Ade7953.init_step = 2; + + Energy.phase_count = 2; // Handle two channels as two phases + Energy.voltage_common = true; // Use common voltage + energy_flg = XNRG_07; } } @@ -188,6 +217,7 @@ bool Ade7953Command(void) { bool serviced = true; + uint32_t channel = (2 == XdrvMailbox.index) ? 1 : 0; uint32_t value = (uint32_t)(CharToFloat(XdrvMailbox.data) * 100); // 1.23 = 123 if (CMND_POWERCAL == Energy.command_code) { @@ -203,9 +233,9 @@ bool Ade7953Command(void) // Service in xdrv_03_energy.ino } else if (CMND_POWERSET == Energy.command_code) { - if (XdrvMailbox.data_len && Ade7953.active_power) { + if (XdrvMailbox.data_len && Ade7953.active_power[channel]) { if ((value > 100) && (value < 200000)) { // Between 1W and 2000W - Settings.energy_power_calibration = (Ade7953.active_power * 1000) / value; // 0.00 W + Settings.energy_power_calibration = (Ade7953.active_power[channel] * 1000) / value; // 0.00 W } } } @@ -217,9 +247,9 @@ bool Ade7953Command(void) } } else if (CMND_CURRENTSET == Energy.command_code) { - if (XdrvMailbox.data_len && Ade7953.current_rms) { + if (XdrvMailbox.data_len && Ade7953.current_rms[channel]) { if ((value > 2000) && (value < 1000000)) { // Between 20mA and 10A - Settings.energy_current_calibration = ((Ade7953.current_rms * 100) / value) * 100; // 0.00 mA + Settings.energy_current_calibration = ((Ade7953.current_rms[channel] * 100) / value) * 100; // 0.00 mA } } } diff --git a/sonoff/xnrg_08_sdm120.ino b/sonoff/xnrg_08_sdm120.ino index c7644b740..592edc2f8 100644 --- a/sonoff/xnrg_08_sdm120.ino +++ b/sonoff/xnrg_08_sdm120.ino @@ -98,31 +98,31 @@ void SDM120Every250ms(void) switch(Sdm120.read_state) { case 0: - Energy.voltage = value; // 230.2 V + Energy.voltage[0] = value; // 230.2 V break; case 1: - Energy.current = value; // 1.260 A + Energy.current[0] = value; // 1.260 A break; case 2: - Energy.active_power = value; // -196.3 W + Energy.active_power[0] = value; // -196.3 W break; case 3: - Energy.apparent_power = value; // 223.4 VA + Energy.apparent_power[0] = value; // 223.4 VA break; case 4: - Energy.reactive_power = value; // 92.2 + Energy.reactive_power[0] = value; // 92.2 break; case 5: - Energy.power_factor = value; // -0.91 + Energy.power_factor[0] = value; // -0.91 break; case 6: - Energy.frequency = value; // 50.0 Hz + Energy.frequency[0] = value; // 50.0 Hz break; case 7: diff --git a/sonoff/xnrg_09_dds2382.ino b/sonoff/xnrg_09_dds2382.ino index 668f21358..c2ae02746 100644 --- a/sonoff/xnrg_09_dds2382.ino +++ b/sonoff/xnrg_09_dds2382.ino @@ -57,12 +57,12 @@ void Dds2382EverySecond(void) // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // SA FC BC EnergyTotal ExportActiv ImportActiv Volta Curre APowe RPowe PFact Frequ Crc-- - Energy.voltage = (float)((buffer[27] << 8) + buffer[28]) / 10.0; - Energy.current = (float)((buffer[29] << 8) + buffer[30]) / 100.0; - Energy.active_power = (float)((buffer[31] << 8) + buffer[32]); - Energy.reactive_power = (float)((buffer[33] << 8) + buffer[34]); - Energy.power_factor = (float)((buffer[35] << 8) + buffer[36]) / 1000.0; // 1.00 - Energy.frequency = (float)((buffer[37] << 8) + buffer[38]) / 100.0; // 50.0 Hz + Energy.voltage[0] = (float)((buffer[27] << 8) + buffer[28]) / 10.0; + Energy.current[0] = (float)((buffer[29] << 8) + buffer[30]) / 100.0; + Energy.active_power[0] = (float)((buffer[31] << 8) + buffer[32]); + Energy.reactive_power[0] = (float)((buffer[33] << 8) + buffer[34]); + Energy.power_factor[0] = (float)((buffer[35] << 8) + buffer[36]) / 1000.0; // 1.00 + Energy.frequency[0] = (float)((buffer[37] << 8) + buffer[38]) / 100.0; // 50.0 Hz Energy.export_active = (float)((buffer[11] << 24) + (buffer[12] << 16) + (buffer[13] << 8) + buffer[14]) / 100.0; // 429496729.0 W // float energy_total = (float)((buffer[3] << 24) + (buffer[4] << 16) + (buffer[5] << 8) + buffer[6]) / 100.0; // 429496729.0 W float import_active = (float)((buffer[15] << 24) + (buffer[16] << 16) + (buffer[17] << 8) + buffer[18]) / 100.0; // 429496729.0 W