6.2.1.9 Apparent/Reactive Power

6.2.1.9 20180928
 * Add Apparent Power and Reactive Power to Energy Monitoring devices (#251)
This commit is contained in:
Theo Arends 2018-09-28 15:48:42 +02:00
parent 1a0d630da2
commit 4b7c797fb7
9 changed files with 137 additions and 80 deletions

View File

@ -1,4 +1,7 @@
/* 6.2.1.8 20180926
/* 6.2.1.9 20180928
* Add Apparent Power and Reactive Power to Energy Monitoring devices (#251)
*
* 6.2.1.8 20180926
* Change status JSON message providing more switch and retain information
* Change pinmode for no-pullup defined switches to pullup when configured as switchmode PUSHBUTTON (=3 and up) (#3896)
* Add delay after restart before processing rule sensor data (#3811)

View File

@ -20,7 +20,7 @@
#ifndef _SONOFF_VERSION_H_
#define _SONOFF_VERSION_H_
#define VERSION 0x06020108
#define VERSION 0x06020109
#define D_PROGRAMNAME "Sonoff-Tasmota"
#define D_AUTHOR "Theo Arends"

View File

@ -494,6 +494,32 @@ double FastPrecisePow(double a, double b)
return r * u.d;
}
uint32_t SqrtInt(uint32_t num)
{
if (num <= 1) {
return num;
}
uint32_t x = num / 2;
uint32_t y;
do {
y = (x + num / x) / 2;
if (y >= x) {
return x;
}
x = y;
} while (true);
}
uint32_t RoundSqrtInt(uint32_t num)
{
uint32_t s = SqrtInt(4 * num);
if (s & 1) {
s++;
}
return s / 2;
}
char* GetTextIndexed(char* destination, size_t destination_size, uint16_t index, const char* haystack)
{
// Returns empty string if not found

View File

@ -41,23 +41,25 @@ const char kEnergyCommands[] PROGMEM =
D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|"
D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW ;
float energy_voltage = 0; // 123.1 V
float energy_current = 0; // 123.123 A
float energy_power = 0; // 123.1 W
float energy_power_factor = NAN; // 0.12
int energy_calc_power_factor = 0; // Do not calculate power factor from data
float energy_frequency = NAN; // 123.1 Hz
float energy_start = 0; // 12345.12345 kWh total previous
float energy_voltage = 0; // 123.1 V
float energy_current = 0; // 123.123 A
float energy_active_power = 0; // 123.1 W
float energy_apparent_power = NAN; // 123.1 VA
float energy_reactive_power = NAN; // 123.1 VAr
float energy_power_factor = NAN; // 0.12
float energy_frequency = NAN; // 123.1 Hz
float energy_start = 0; // 12345.12345 kWh total previous
float energy_daily = 0; // 123.123 kWh
float energy_total = 0; // 12345.12345 kWh
float energy_daily = 0; // 123.123 kWh
float energy_total = 0; // 12345.12345 kWh
unsigned long energy_kWhtoday_delta = 0; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to energy_kWhtoday (HLW and CSE only)
unsigned long energy_kWhtoday; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily
unsigned long energy_period = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily
unsigned long energy_kWhtoday; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily
unsigned long energy_period = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily
float energy_power_last[3] = { 0 };
uint8_t energy_power_delta = 0;
bool energy_type_dc = false;
bool energy_power_on = true;
byte energy_min_power_flag = 0;
@ -124,15 +126,6 @@ void Energy200ms()
}
XnrgCall(FUNC_EVERY_200_MSECOND);
if (energy_calc_power_factor) {
float power_factor = 0;
if (energy_voltage && energy_current && energy_power) {
power_factor = energy_power / (energy_voltage * energy_current);
if (power_factor > 1) power_factor = 1;
}
energy_power_factor = power_factor;
}
}
void EnergySaveState()
@ -178,21 +171,21 @@ void EnergyMarginCheck()
}
if (Settings.energy_power_delta) {
float delta = abs(energy_power_last[0] - energy_power);
float delta = abs(energy_power_last[0] - energy_active_power);
// Any delta compared to minimal delta
float min_power = (energy_power_last[0] > energy_power) ? energy_power : energy_power_last[0];
float min_power = (energy_power_last[0] > energy_active_power) ? energy_active_power : energy_power_last[0];
if (((delta / min_power) * 100) > Settings.energy_power_delta) {
energy_power_delta = 1;
energy_power_last[1] = energy_power; // We only want one report so reset history
energy_power_last[2] = energy_power;
energy_power_last[1] = energy_active_power; // We only want one report so reset history
energy_power_last[2] = energy_active_power;
}
}
energy_power_last[0] = energy_power_last[1]; // Shift in history every second allowing power changes to settle for up to three seconds
energy_power_last[1] = energy_power_last[2];
energy_power_last[2] = energy_power;
energy_power_last[2] = energy_active_power;
if (energy_power_on && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) {
energy_power_u = (uint16_t)(energy_power);
energy_power_u = (uint16_t)(energy_active_power);
energy_voltage_u = (uint16_t)(energy_voltage);
energy_current_u = (uint16_t)(energy_current * 1000);
@ -235,7 +228,7 @@ void EnergyMarginCheck()
#if FEATURE_POWER_LIMIT
// Max Power
if (Settings.energy_max_power_limit) {
if (energy_power > Settings.energy_max_power_limit) {
if (energy_active_power > Settings.energy_max_power_limit) {
if (!energy_mplh_counter) {
energy_mplh_counter = Settings.energy_max_power_limit_hold;
} else {
@ -535,6 +528,8 @@ const char HTTP_ENERGY_SNS1[] PROGMEM = "%s"
"{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}";
const char HTTP_ENERGY_SNS2[] PROGMEM = "%s"
"{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_SNS3[] PROGMEM = "%s"
@ -548,27 +543,64 @@ const char HTTP_ENERGY_SNS4[] PROGMEM = "%s"
void EnergyShow(boolean json)
{
char energy_total_chr[10];
char voltage_chr[10];
char current_chr[10];
char active_power_chr[10];
char apparent_power_chr[10];
char reactive_power_chr[10];
char power_factor_chr[10];
char frequency_chr[10];
char energy_daily_chr[10];
char energy_period_chr[10];
char energy_power_chr[10];
char energy_voltage_chr[10];
char energy_current_chr[10];
char energy_frequency_chr[10];
char energy_power_factor_chr[10];
char energy_yesterday_chr[10];
char energy_total_chr[10];
char speriod[20];
char spfactor[20];
char sfrequency[20];
bool show_energy_period = (0 == tele_period);
dtostrfd(energy_power, Settings.flag2.wattage_resolution, energy_power_chr);
dtostrfd(energy_voltage, Settings.flag2.voltage_resolution, energy_voltage_chr);
dtostrfd(energy_current, Settings.flag2.current_resolution, energy_current_chr);
dtostrfd(energy_total, Settings.flag2.energy_resolution, energy_total_chr);
if (!energy_type_dc) {
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;
}
float power_factor = energy_power_factor;
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);
snprintf_P(sfrequency, sizeof(sfrequency), PSTR(",\"" D_JSON_FREQUENCY "\":%s"), frequency_chr);
}
}
dtostrfd(energy_voltage, Settings.flag2.voltage_resolution, voltage_chr);
dtostrfd(energy_current, Settings.flag2.current_resolution, current_chr);
dtostrfd(energy_active_power, Settings.flag2.wattage_resolution, active_power_chr);
dtostrfd(energy_daily, Settings.flag2.energy_resolution, energy_daily_chr);
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
dtostrfd(energy_total, Settings.flag2.energy_resolution, energy_total_chr);
float energy = 0;
if (show_energy_period) {
@ -577,34 +609,30 @@ void EnergyShow(boolean json)
dtostrfd(energy, Settings.flag2.wattage_resolution, energy_period_chr);
snprintf_P(speriod, sizeof(speriod), PSTR(",\"" D_JSON_PERIOD "\":%s"), energy_period_chr);
}
if (!isnan(energy_frequency)) {
dtostrfd(energy_frequency, Settings.flag2.frequency_resolution, energy_frequency_chr);
snprintf_P(sfrequency, sizeof(sfrequency), PSTR(",\"" D_JSON_FREQUENCY "\":%s"), energy_frequency_chr);
}
if (!isnan(energy_power_factor)) {
dtostrfd(energy_power_factor, 2, energy_power_factor_chr);
snprintf_P(spfactor, sizeof(spfactor), PSTR(",\"" D_JSON_POWERFACTOR "\":%s"), energy_power_factor_chr);
}
if (json) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s%s,\""
D_JSON_POWERUSAGE "\":%s%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s%s}"),
mqtt_data, energy_total_chr, energy_yesterday_chr, energy_daily_chr, (show_energy_period) ? speriod : "",
energy_power_chr, (!isnan(energy_power_factor)) ? spfactor : "", energy_voltage_chr, energy_current_chr, (!isnan(energy_frequency)) ? sfrequency : "");
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s%s,\"" D_JSON_POWERUSAGE "\":%s"),
mqtt_data, energy_total_chr, energy_yesterday_chr, energy_daily_chr, (show_energy_period) ? speriod : "", active_power_chr);
if (!energy_type_dc) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_POWERFACTOR "\":%s%s"),
mqtt_data, apparent_power_chr, reactive_power_chr, power_factor_chr, (!isnan(energy_frequency)) ? sfrequency : "");
}
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s}"), mqtt_data, voltage_chr, current_chr);
#ifdef USE_DOMOTICZ
if (show_energy_period) { // Only send if telemetry
dtostrfd(energy_total * 1000, 1, energy_total_chr);
DomoticzSensorPowerEnergy((int)energy_power, energy_total_chr); // PowerUsage, EnergyToday
DomoticzSensor(DZ_VOLTAGE, energy_voltage_chr); // Voltage
DomoticzSensor(DZ_CURRENT, energy_current_chr); // Current
DomoticzSensorPowerEnergy((int)energy_active_power, energy_total_chr); // PowerUsage, EnergyToday
DomoticzSensor(DZ_VOLTAGE, voltage_chr); // Voltage
DomoticzSensor(DZ_CURRENT, current_chr); // Current
}
#endif // USE_DOMOTICZ
#ifdef USE_KNX
if (show_energy_period) {
KnxSensor(KNX_ENERGY_VOLTAGE, energy_voltage);
KnxSensor(KNX_ENERGY_CURRENT, energy_current);
KnxSensor(KNX_ENERGY_POWER, energy_power);
if (!isnan(energy_power_factor)) { KnxSensor(KNX_ENERGY_POWERFACTOR, energy_power_factor); }
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);
@ -612,9 +640,11 @@ void EnergyShow(boolean json)
#endif // USE_KNX
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS1, mqtt_data, energy_voltage_chr, energy_current_chr, energy_power_chr);
if (!isnan(energy_power_factor)) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS2, mqtt_data, energy_power_factor_chr); }
if (!isnan(energy_frequency)) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS3, mqtt_data, energy_frequency_chr); }
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS1, mqtt_data, voltage_chr, current_chr, active_power_chr);
if (!energy_type_dc) {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS2, mqtt_data, apparent_power_chr, reactive_power_chr, power_factor_chr);
if (!isnan(energy_frequency)) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS3, mqtt_data, frequency_chr); }
}
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS4, mqtt_data, energy_daily_chr, energy_yesterday_chr, energy_total_chr);
#endif // USE_WEBSERVER
}

View File

@ -111,9 +111,9 @@ void HlwEvery200ms()
if (hlw_cf_pulse_length && energy_power_on && !hlw_load_off) {
hlw_w = (hlw_power_ratio * Settings.energy_power_calibration) / hlw_cf_pulse_length;
energy_power = (float)hlw_w / 10;
energy_active_power = (float)hlw_w / 10;
} else {
energy_power = 0;
energy_active_power = 0;
}
hlw_cf1_timer++;
@ -142,7 +142,7 @@ void HlwEvery200ms()
hlw_cf1_current_pulse_length = hlw_cf1_pulse_length;
hlw_cf1_current_max_pulse_counter = hlw_cf1_pulse_counter;
if (hlw_cf1_current_pulse_length && energy_power) { // No current if no power being consumed
if (hlw_cf1_current_pulse_length && energy_active_power) { // No current if no power being consumed
hlw_i = (hlw_current_ratio * Settings.energy_current_calibration) / hlw_cf1_current_pulse_length;
energy_current = (float)hlw_i / 1000;
} else {
@ -217,7 +217,6 @@ void HlwDrvInit()
{
if (!energy_flg) {
if ((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)) { // Sonoff Pow or any HLW8012 based device
energy_calc_power_factor = 1; // Calculate power factor from data
energy_flg = XNRG_01;
}
}

View File

@ -95,14 +95,14 @@ void CseReceived()
if (adjustement & 0x10) { // Power valid
cse_power_invalid = 0;
if ((header & 0xF2) == 0xF2) { // Power cycle exceeds range
energy_power = 0;
energy_active_power = 0;
} else {
if (0 == power_cycle_first) { power_cycle_first = power_cycle; } // Skip first incomplete power_cycle
if (power_cycle_first != power_cycle) {
power_cycle_first = -1;
energy_power = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)power_cycle;
energy_active_power = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)power_cycle;
} else {
energy_power = 0;
energy_active_power = 0;
}
}
} else {
@ -110,11 +110,11 @@ void CseReceived()
cse_power_invalid++;
} else {
power_cycle_first = 0;
energy_power = 0; // Powered on but no load
energy_active_power = 0; // Powered on but no load
}
}
if (adjustement & 0x20) { // Current valid
if (0 == energy_power) {
if (0 == energy_active_power) {
energy_current = 0;
} else {
energy_current = (float)Settings.energy_current_calibration / (float)current_cycle;
@ -123,7 +123,7 @@ void CseReceived()
} else { // Powered off
power_cycle_first = 0;
energy_voltage = 0;
energy_power = 0;
energy_active_power = 0;
energy_current = 0;
}
}
@ -180,7 +180,7 @@ void CseEverySecond()
} else {
cf_frequency = cf_pulses - cf_pulses_last_time;
}
if (cf_frequency && energy_power) {
if (cf_frequency && energy_active_power) {
cf_pulses_last_time = cf_pulses;
energy_kWhtoday_delta += (cf_frequency * Settings.energy_power_calibration) / 36;
EnergyUpdateToday();
@ -194,7 +194,6 @@ void CseDrvInit()
if ((SONOFF_S31 == Settings.module) || (SONOFF_POW_R2 == Settings.module)) { // Sonoff S31 or Sonoff Pow R2
baudrate = 4800;
serial_config = SERIAL_8E1;
energy_calc_power_factor = 1; // Calculate power factor from data
energy_flg = XNRG_02;
}
}

View File

@ -177,7 +177,7 @@ void PzemEvery200ms()
energy_current = value;
break;
case 3: // Power as 20W
energy_power = value;
energy_active_power = value;
break;
case 4: // Total energy as 99999Wh
if (!energy_start || (value < energy_start)) energy_start = value; // Init after restart and hanlde roll-over if any
@ -215,7 +215,6 @@ void PzemDrvInit()
{
if (!energy_flg) {
if ((pin[GPIO_PZEM_RX] < 99) && (pin[GPIO_PZEM_TX] < 99)) { // Any device with a Pzem004T
energy_calc_power_factor = 1; // Calculate power factor from data
energy_flg = XNRG_03;
}
}

View File

@ -448,8 +448,8 @@ void McpParseData(void)
if (energy_power_on) { // Powered on
energy_frequency = (float)mcp_line_frequency / 1000;
energy_voltage = (float)mcp_voltage_rms / 10;
energy_power = (float)mcp_active_power / 100;
if (0 == energy_power) {
energy_active_power = (float)mcp_active_power / 100;
if (0 == energy_active_power) {
energy_current = 0;
} else {
energy_current = (float)mcp_current_rms / 10000;
@ -457,7 +457,7 @@ void McpParseData(void)
} else { // Powered off
energy_frequency = 0;
energy_voltage = 0;
energy_power = 0;
energy_active_power = 0;
energy_current = 0;
}
}
@ -557,7 +557,6 @@ void McpDrvInit(void)
mcp_calibrate = 0;
mcp_timeout = 2; // Initial wait
mcp_init = 2; // Initial setup steps
energy_calc_power_factor = 1; // Calculate power factor from data
energy_flg = XNRG_04;
}
}

View File

@ -141,12 +141,13 @@ void Pzem2Every200ms()
float energy = 0;
if (PZEM2_TYPES_003_017 == pzem2_type) {
energy_type_dc = true;
// 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
// FE 04 10 27 10 00 64 03 E8 00 00 00 00 00 00 00 00 00 00 HH LL = PZEM-017
// 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_power = (float)((uint32_t)buffer[9] << 24 + (uint32_t)buffer[10] << 16 + (uint32_t)buffer[7] << 8 + buffer[8]) / 10.0; // 429496729.0 W
energy_active_power = (float)((uint32_t)buffer[9] << 24 + (uint32_t)buffer[10] << 16 + (uint32_t)buffer[7] << 8 + buffer[8]) / 10.0; // 429496729.0 W
energy = (float)((uint32_t)buffer[13] << 24 + (uint32_t)buffer[14] << 16 + (uint32_t)buffer[11] << 8 + buffer[12]); // 4294967295 Wh
if (!energy_start || (energy < energy_start)) { energy_start = energy; } // Init after restart and hanlde roll-over if any
energy_kWhtoday += (energy - energy_start) * 100;
@ -154,12 +155,13 @@ void Pzem2Every200ms()
EnergyUpdateToday();
}
else if (PZEM2_TYPES_014_016 == pzem2_type) { // PZEM-014,016
energy_type_dc = false;
// 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
// FE 04 14 08 98 03 E8 00 00 08 98 00 00 00 00 00 00 01 F4 00 64 00 00 HH LL = PZEM-014
// 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)((uint32_t)buffer[7] << 24 + (uint32_t)buffer[8] << 16 + (uint32_t)buffer[5] << 8 + buffer[6]) / 1000.0; // 4294967.000 A
energy_power = (float)((uint32_t)buffer[11] << 24 + (uint32_t)buffer[12] << 16 + (uint32_t)buffer[9] << 8 + buffer[10]) / 10.0; // 429496729.0 W
energy_active_power = (float)((uint32_t)buffer[11] << 24 + (uint32_t)buffer[12] << 16 + (uint32_t)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 = (float)((uint32_t)buffer[15] << 24 + (uint32_t)buffer[16] << 16 + (uint32_t)buffer[13] << 8 + buffer[14]); // 4294967295 Wh