diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index de5d477e..7c172cdb 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -426,13 +426,13 @@ PROGMEM const char* const custom_reset_string[] = { #define MQTT_TOPIC_RFLEARN "rflearn" // Power module -#define MQTT_TOPIC_POWER "power" -#define MQTT_TOPIC_CURRENT "current" -#define MQTT_TOPIC_VOLTAGE "voltage" -#define MQTT_TOPIC_APPARENT "apower" -#define MQTT_TOPIC_REACTIVE "rpower" -#define MQTT_TOPIC_POWER_FACTOR "pfactor" -#define MQTT_TOPIC_ENERGY "energy" +#define MQTT_TOPIC_POWER_ACTIVE "power" +#define MQTT_TOPIC_CURRENT "current" +#define MQTT_TOPIC_VOLTAGE "voltage" +#define MQTT_TOPIC_POWER_APPARENT "apparent" +#define MQTT_TOPIC_POWER_REACTIVE "reactive" +#define MQTT_TOPIC_POWER_FACTOR "factor" +#define MQTT_TOPIC_ENERGY "energy" // Light module #define MQTT_TOPIC_CHANNEL "channel" @@ -510,6 +510,9 @@ PROGMEM const char* const custom_reset_string[] = { #define POWER_MAGNITUDE_VOLTAGE 2 #define POWER_MAGNITUDE_ACTIVE 4 #define POWER_MAGNITUDE_APPARENT 8 +#define POWER_MAGNITUDE_REACTIVE 16 +#define POWER_MAGNITUDE_POWER_FACTOR 32 +#define POWER_MAGNITUDE_ALL 63 // No power provider defined #ifndef POWER_PROVIDER @@ -527,12 +530,12 @@ PROGMEM const char* const custom_reset_string[] = { #define POWER_VOLTAGE 230 #define POWER_CURRENT_RATIO 30 #define POWER_SAMPLES 1000 -#define POWER_INTERVAL 10000 -#define POWER_REPORT_EVERY 6 +#define POWER_READ_INTERVAL 10000 #define POWER_REPORT_INTERVAL 60000 -#define POWER_ENERGY_FACTOR (POWER_INTERVAL * POWER_REPORT_EVERY / 1000.0 / 3600.0) +#define POWER_ENERGY_FACTOR (POWER_REPORT_INTERVAL / 1000.0 / 3600.0) #if POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG + #define POWER_REPORT_BUFFER 10 #define POWER_ADC_BITS 10 #define POWER_REFERENCE_VOLTAGE 1.0 #define POWER_CURRENT_OFFSET 0.25 @@ -541,6 +544,7 @@ PROGMEM const char* const custom_reset_string[] = { #endif #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 + #define POWER_REPORT_BUFFER 10 #define POWER_I2C_ADDRESS 0x50 #define POWER_ADC_BITS 12 #define POWER_REFERENCE_VOLTAGE 3.3 @@ -550,6 +554,7 @@ PROGMEM const char* const custom_reset_string[] = { #endif #if POWER_PROVIDER == POWER_PROVIDER_HLW8012 + #define POWER_REPORT_BUFFER 10 #define HLW8012_USE_INTERRUPTS 1 #define HLW8012_SEL_CURRENT HIGH #define HLW8012_CURRENT_R 0.001 @@ -558,6 +563,7 @@ PROGMEM const char* const custom_reset_string[] = { #endif #if POWER_PROVIDER == POWER_PROVIDER_V9261F + #define POWER_REPORT_BUFFER 60 #define V9261F_SYNC_INTERVAL 600 #define V9261F_BAUDRATE 4800 #define V9261F_CURRENT_FACTOR 81156358 diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index ecd9fab3..a8447390 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -861,6 +861,7 @@ #define DEVICE "V9261F" // V9261F + #define POWER_PROVIDER POWER_PROVIDER_V9261F #define V9261F_SUPPORT 1 #define V9261F_PIN 2 #define V9261F_PIN_INVERSE 1 diff --git a/code/espurna/power.h b/code/espurna/power.h index 02c7bd00..22dadb59 100644 --- a/code/espurna/power.h +++ b/code/espurna/power.h @@ -4,54 +4,6 @@ #pragma once -/* -class SpikesFilter { - - public: - - SpikesFilter() { - reset(); - } - - virtual void reset() { - _sum = 0; - _spike = false; - } - - virtual void add(double value) { - - // add previous value - if (_last > 0) { - _sum += _last; - } - - // flag new possible spike - if (value > 0) { - _spike = (_last == 0); - - // delete previous spike - } else if (_spike) { - _sum -= _last; - _spike = false; - } - - _last = value; - - } - - virtual double sum() { - return _sum; - } - - private: - - double _last = 0; - double _sum = 0; - bool _spike = false; - -}; -*/ - class MedianFilter { public: @@ -78,23 +30,30 @@ class MedianFilter { virtual double average(bool do_reset = false) { double sum = 0; - for (unsigned char i = 1; i<_size; i++) { - double previous = _data[i-1]; - double current = _data[i]; - double next = _data[i+1]; + if (_pointer > 2) { + + for (unsigned char i = 1; i<_pointer-1; i++) { + + double previous = _data[i-1]; + double current = _data[i]; + double next = _data[i+1]; + + if (previous > current) std::swap(previous, current); + if (current > next) std::swap(current, next); + if (previous > current) std::swap(previous, current); + + sum += current; - if (previous > current) std::swap(previous, current); - if (current > next) std::swap(current, next); - if (previous > current) std::swap(previous, current); + } - sum += current; + sum /= (_pointer - 1); } if (do_reset) reset(); - return sum / (_size-1); + return sum; } diff --git a/code/espurna/power.ino b/code/espurna/power.ino index b198643b..66ff56a8 100644 --- a/code/espurna/power.ino +++ b/code/espurna/power.ino @@ -18,179 +18,22 @@ Copyright (C) 2016-2017 by Xose Pérez bool _power_enabled = false; bool _power_ready = false; +bool _power_newdata = false; double _power_current = 0; double _power_voltage = 0; double _power_apparent = 0; -MedianFilter _filter_current = MedianFilter(POWER_REPORT_EVERY); +MedianFilter _filter_current = MedianFilter(POWER_REPORT_BUFFER); #if POWER_HAS_ACTIVE double _power_active = 0; - MedianFilter _filter_voltage = MedianFilter(POWER_REPORT_EVERY); - MedianFilter _filter_active = MedianFilter(POWER_REPORT_EVERY); - MedianFilter _filter_apparent = MedianFilter(POWER_REPORT_EVERY); + double _power_reactive = 0; + double _power_factor = 0; + MedianFilter _filter_voltage = MedianFilter(POWER_REPORT_BUFFER); + MedianFilter _filter_active = MedianFilter(POWER_REPORT_BUFFER); + MedianFilter _filter_apparent = MedianFilter(POWER_REPORT_BUFFER); #endif -#if POWER_PROVIDER & POWER_PROVIDER_EMON - #include - EmonLiteESP _emon; -#endif - -#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 - - #include "brzo_i2c.h" - - // ADC121 Registers - #define ADC121_REG_RESULT 0x00 - #define ADC121_REG_ALERT 0x01 - #define ADC121_REG_CONFIG 0x02 - #define ADC121_REG_LIMITL 0x03 - #define ADC121_REG_LIMITH 0x04 - #define ADC121_REG_HYST 0x05 - #define ADC121_REG_CONVL 0x06 - #define ADC121_REG_CONVH 0x07 - -#endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 - -#if POWER_PROVIDER == POWER_PROVIDER_HLW8012 - #include - #include - HLW8012 _hlw8012; - WiFiEventHandler _power_wifi_onconnect; - WiFiEventHandler _power_wifi_ondisconnect; -#endif // POWER_PROVIDER == POWER_PROVIDER_HLW8012 - -// ----------------------------------------------------------------------------- -// PROVIDERS -// ----------------------------------------------------------------------------- - -#if POWER_PROVIDER & POWER_PROVIDER_EMON - -unsigned int currentCallback() { - - #if POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG - - return analogRead(0); - - #endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG - - #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 - - uint8_t buffer[2]; - brzo_i2c_start_transaction(POWER_I2C_ADDRESS, I2C_SCL_FREQUENCY); - buffer[0] = ADC121_REG_RESULT; - brzo_i2c_write(buffer, 1, false); - brzo_i2c_read(buffer, 2, false); - brzo_i2c_end_transaction(); - unsigned int value; - value = (buffer[0] & 0x0F) << 8; - value |= buffer[1]; - return value; - - #endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 - -} - -#endif // POWER_PROVIDER & POWER_PROVIDER_EMON - -#if POWER_PROVIDER == POWER_PROVIDER_HLW8012 - -void ICACHE_RAM_ATTR _hlw_cf1_isr() { - _hlw8012.cf1_interrupt(); -} - -void ICACHE_RAM_ATTR _hlw_cf_isr() { - _hlw8012.cf_interrupt(); -} - -void _hlwSetCalibration() { - double value; - value = getSetting("powerRatioP", 0).toFloat(); - if (value > 0) _hlw8012.setPowerMultiplier(value); - value = getSetting("powerRatioC", 0).toFloat(); - if (value > 0) _hlw8012.setCurrentMultiplier(value); - value = getSetting("powerRatioV", 0).toFloat(); - if (value > 0) _hlw8012.setVoltageMultiplier(value); -} - -void _hlwGetCalibration() { - setSetting("powerRatioP", _hlw8012.getPowerMultiplier()); - setSetting("powerRatioC", _hlw8012.getCurrentMultiplier()); - setSetting("powerRatioV", _hlw8012.getVoltageMultiplier()); - saveSettings(); -} - -void _hlwResetCalibration() { - _hlw8012.resetMultipliers(); - _hlwGetCalibration(); -} - -void _hlwExpectedPower(unsigned int power) { - if (power > 0) { - _hlw8012.expectedActivePower(power); - _hlwGetCalibration(); - } -} - -void _hlwExpectedCurrent(double current) { - if (current > 0) { - _hlw8012.expectedCurrent(current); - _hlwGetCalibration(); - } -} - -void _hlwExpectedVoltage(unsigned int voltage) { - if (voltage > 0) { - _hlw8012.expectedVoltage(voltage); - _hlwGetCalibration(); - } -} - -#endif - -double _powerCurrent() { - #if POWER_PROVIDER & POWER_PROVIDER_EMON - double current = _emon.getCurrent(POWER_SAMPLES); - current -= POWER_CURRENT_OFFSET; - if (current < 0) current = 0; - return current; - #elif POWER_PROVIDER == POWER_PROVIDER_HLW8012 - return _hlw8012.getCurrent(); - #else - return 0; - #endif -} - -double _powerVoltage() { - #if POWER_PROVIDER & POWER_PROVIDER_EMON - return _power_voltage; - #elif POWER_PROVIDER == POWER_PROVIDER_HLW8012 - return _hlw8012.getVoltage(); - #else - return 0; - #endif -} - -#if POWER_HAS_ACTIVE -double _powerActivePower() { - #if POWER_PROVIDER == POWER_PROVIDER_HLW8012 - return _hlw8012.getActivePower(); - #else - return 0; - #endif -} -#endif - -double _powerApparentPower() { - #if POWER_PROVIDER & POWER_PROVIDER_EMON - return _powerCurrent() * _powerVoltage(); - #elif POWER_PROVIDER == POWER_PROVIDER_HLW8012 - return _hlw8012.getApparentPower(); - #else - return 0; - #endif -} - // ----------------------------------------------------------------------------- // PRIVATE METHODS // ----------------------------------------------------------------------------- @@ -215,7 +58,7 @@ void _powerAPISetup() { } }); - apiRegister(MQTT_TOPIC_APPARENT, MQTT_TOPIC_APPARENT, [](char * buffer, size_t len) { + apiRegister(MQTT_TOPIC_POWER_APPARENT, MQTT_TOPIC_POWER_APPARENT, [](char * buffer, size_t len) { if (_power_ready) { snprintf_P(buffer, len, PSTR("%d"), getApparentPower()); } else { @@ -224,7 +67,7 @@ void _powerAPISetup() { }); #if POWER_HAS_ACTIVE - apiRegister(MQTT_TOPIC_POWER, MQTT_TOPIC_POWER, [](char * buffer, size_t len) { + apiRegister(MQTT_TOPIC_POWER_ACTIVE, MQTT_TOPIC_POWER_ACTIVE, [](char * buffer, size_t len) { if (_power_ready) { snprintf_P(buffer, len, PSTR("%d"), getActivePower()); } else { @@ -266,24 +109,17 @@ double getApparentPower() { return _power_apparent; } -#if POWER_HAS_ACTIVE double getActivePower() { return _power_active; } double getReactivePower() { - if (_power_apparent > _power_active) { - return sqrt(_power_apparent * _power_apparent - _power_active * _power_active); - } - return 0; + return _power_reactive; } double getPowerFactor() { - if (_power_active > _power_apparent) return 1; - if (_power_apparent == 0) return 0; - return (double) _power_active / _power_apparent; + return _power_factor; } -#endif // ----------------------------------------------------------------------------- // PUBLIC API @@ -294,121 +130,16 @@ bool powerEnabled() { } void powerEnabled(bool enabled) { + if (enabled & !_power_enabled) _powerReset(); _power_enabled = enabled; - #if (POWER_PROVIDER == POWER_PROVIDER_HLW8012) && HLW8012_USE_INTERRUPTS - if (_power_enabled) { - attachInterrupt(HLW8012_CF1_PIN, _hlw_cf1_isr, CHANGE); - attachInterrupt(HLW8012_CF_PIN, _hlw_cf_isr, CHANGE); - } else { - detachInterrupt(HLW8012_CF1_PIN); - detachInterrupt(HLW8012_CF_PIN); - } - #endif + powerEnabledProvider(); } void powerConfigure() { - - #if POWER_PROVIDER & POWER_PROVIDER_EMON - _emon.setCurrentRatio(getSetting("powerRatioC", POWER_CURRENT_RATIO).toFloat()); - _power_voltage = getSetting("powerVoltage", POWER_VOLTAGE).toFloat(); - #endif - - #if POWER_PROVIDER == POWER_PROVIDER_HLW8012 - _hlwSetCalibration(); - _hlwGetCalibration(); - #endif - -} - -void powerSetup() { - - // backwards compatibility - moveSetting("pwMainsVoltage", "powerVoltage"); - moveSetting("emonMains", "powerVoltage"); - moveSetting("emonVoltage", "powerVoltage"); - moveSetting("pwCurrentRatio", "powerRatioC"); - moveSetting("emonRatio", "powerRatioC"); - moveSetting("powPowerMult", "powerRatioP"); - moveSetting("powCurrentMult", "powerRatioC"); - moveSetting("powVoltageMult", "powerRatioV"); - - #if POWER_PROVIDER == POWER_PROVIDER_HLW8012 - - // Initialize HLW8012 - // void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT); - // * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC - // * currentWhen is the value in sel_pin to select current sampling - // * set use_interrupts to true to use interrupts to monitor pulse widths - // * leave pulse_timeout to the default value, recommended when using interrupts - #if HLW8012_USE_INTERRUPTS - _hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, true); - #else - _hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, false, 1000000); - #endif - - // These values are used to calculate current, voltage and power factors as per datasheet formula - // These are the nominal values for the Sonoff POW resistors: - // * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line - // * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012 - // * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012 - _hlw8012.setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN); - - #endif // POWER_PROVIDER == POWER_PROVIDER_HLW8012 - - #if POWER_PROVIDER & POWER_PROVIDER_EMON - _emon.initCurrent(currentCallback, POWER_ADC_BITS, POWER_REFERENCE_VOLTAGE, POWER_CURRENT_RATIO); - #endif - - #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 - uint8_t buffer[2]; - buffer[0] = ADC121_REG_CONFIG; - buffer[1] = 0x00; - brzo_i2c_start_transaction(POWER_I2C_ADDRESS, I2C_SCL_FREQUENCY); - brzo_i2c_write(buffer, 2, false); - brzo_i2c_end_transaction(); - #endif - - powerConfigure(); - - #if POWER_PROVIDER & POWER_PROVIDER_EMON - _emon.warmup(); - #endif - - #if POWER_PROVIDER == POWER_PROVIDER_HLW8012 - _power_wifi_onconnect = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) { - powerEnabled(true); - }); - _power_wifi_ondisconnect = WiFi.onStationModeDisconnected([](WiFiEventStationModeDisconnected ipInfo) { - powerEnabled(false); - }); - #endif - - // API - #if WEB_SUPPORT - _powerAPISetup(); - #endif - - DEBUG_MSG_P(PSTR("[POWER] POWER_PROVIDER = %d\n"), POWER_PROVIDER); - + powerConfigureProvider(); } -void powerLoop() { - - static unsigned long last = 0; - static bool was_disabled = false; - - if (!_power_enabled) { - was_disabled = true; - return; - } - if (was_disabled) { - was_disabled = false; - last = millis(); - _powerReset(); - } - - if (millis() - last < POWER_INTERVAL) return; - last = millis(); +void powerRead() { // Get instantaneous values from HAL double current = _powerCurrent(); @@ -457,70 +188,113 @@ void powerLoop() { } #endif - // Send MQTT messages averaged every POWER_REPORT_EVERY measurements - if (_filter_current.count() == POWER_REPORT_EVERY) { +} + +void powerReport() { - // Get the fitered values - _power_current = _filter_current.average(true); + // Get the fitered values + _power_current = _filter_current.average(true); + #if POWER_HAS_ACTIVE + _power_apparent = _filter_apparent.average(true); + _power_voltage = _filter_voltage.average(true); + _power_active = _filter_active.average(true); + #else + _power_apparent = _power_current * _power_voltage; + _power_active = _power_apparent; + #endif + _power_reactive = (_power_apparent > _power_active) ? sqrt(_power_apparent * _power_apparent - _power_active * _power_active) : 0; + _power_factor = (_power_apparent > 0) ? _power_active / _power_apparent : 1; + _power_ready = true; + + char buf_current[10]; + dtostrf(_power_current, 6, POWER_CURRENT_PRECISION, buf_current); + double energy_delta = _power_active * POWER_ENERGY_FACTOR; + char buf_energy[10]; + dtostrf(energy_delta, 6, POWER_CURRENT_PRECISION, buf_energy); + + { + mqttSend(MQTT_TOPIC_CURRENT, buf_current); + mqttSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str()); + mqttSend(MQTT_TOPIC_ENERGY, buf_energy); #if POWER_HAS_ACTIVE - _power_apparent = _filter_apparent.average(true); - _power_voltage = _filter_voltage.average(true); - _power_active = _filter_active.average(true); - double power = _power_active; - #else - _power_apparent = _power_current * _power_voltage; - double power = _power_apparent; + mqttSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str()); + mqttSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str()); + mqttSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str()); + mqttSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str()); #endif - double delta_energy = power * POWER_ENERGY_FACTOR; - char delta_energy_buffer[10]; - dtostrf(delta_energy, sizeof(delta_energy_buffer)-1, POWER_CURRENT_PRECISION, delta_energy_buffer); - _power_ready = true; - - // Report values to MQTT broker - { - mqttSend(MQTT_TOPIC_CURRENT, current_buffer); - mqttSend(MQTT_TOPIC_APPARENT, String((int) _power_apparent).c_str()); - mqttSend(MQTT_TOPIC_ENERGY, delta_energy_buffer); - #if POWER_HAS_ACTIVE - mqttSend(MQTT_TOPIC_POWER, String((int) _power_active).c_str()); - mqttSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str()); - #endif - } - - // Report values to Domoticz - #if DOMOTICZ_SUPPORT - { - char buffer[20]; - snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, delta_energy_buffer); - domoticzSend("dczPowIdx", 0, buffer); - domoticzSend("dczEnergyIdx", 0, delta_energy_buffer); - domoticzSend("dczCurrentIdx", 0, current_buffer); - #if POWER_HAS_ACTIVE - snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _power_voltage); - domoticzSend("dczVoltIdx", 0, buffer); - #endif + } - } + #if DOMOTICZ_SUPPORT + { + char buffer[20]; + snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), _power_active, buf_energy); + domoticzSend("dczPowIdx", 0, buffer); + domoticzSend("dczCurrentIdx", 0, buf_current); + domoticzSend("dczEnergyIdx", 0, buf_energy); + #if POWER_HAS_ACTIVE + snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _power_voltage); + domoticzSend("dczVoltIdx", 0, buffer); #endif - #if INFLUXDB_SUPPORT - { - influxDBSend(MQTT_TOPIC_CURRENT, current_buffer); - influxDBSend(MQTT_TOPIC_APPARENT, String((int) _power_apparent).c_str()); - influxDBSend(MQTT_TOPIC_ENERGY, delta_energy_buffer); - #if POWER_HAS_ACTIVE - influxDBSend(MQTT_TOPIC_POWER, String((int) _power_active).c_str()); - influxDBSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str()); - #endif - } + } + #endif + + #if INFLUXDB_SUPPORT + { + influxDBSend(MQTT_TOPIC_CURRENT, buf_current); + influxDBSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str()); + influxDBSend(MQTT_TOPIC_ENERGY, buf_energy); + #if POWER_HAS_ACTIVE + influxDBSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str()); + influxDBSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str()); + influxDBSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str()); + influxDBSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str()); #endif + } + #endif + +} + +void powerSetup() { + + // backwards compatibility + moveSetting("pwMainsVoltage", "powerVoltage"); + moveSetting("emonMains", "powerVoltage"); + moveSetting("emonVoltage", "powerVoltage"); + moveSetting("pwCurrentRatio", "powerRatioC"); + moveSetting("emonRatio", "powerRatioC"); + moveSetting("powPowerMult", "powerRatioP"); + moveSetting("powCurrentMult", "powerRatioC"); + moveSetting("powVoltageMult", "powerRatioV"); + + powerSetupProvider(); + + // API + #if WEB_SUPPORT + _powerAPISetup(); + #endif + + DEBUG_MSG_P(PSTR("[POWER] POWER_PROVIDER = %d\n"), POWER_PROVIDER); +} + +void powerLoop() { + + powerLoopProvider(true); + + if (_power_newdata) { + _power_newdata = false; + powerRead(); } - // Toggle between current and voltage monitoring - #if (POWER_PROVIDER == POWER_PROVIDER_HLW8012) && (HLW8012_USE_INTERRUPTS == 0) - _hlw8012.toggleMode(); - #endif // (POWER_PROVIDER == POWER_PROVIDER_HLW8012) && (HLW8012_USE_INTERRUPTS == 0) + static unsigned long last = 0; + if (millis() - last > POWER_REPORT_INTERVAL) { + last = millis(); + powerReport(); + } + + powerLoopProvider(false); + } #endif // POWER_PROVIDER != POWER_PROVIDER_NONE diff --git a/code/espurna/power_emon.ino b/code/espurna/power_emon.ino new file mode 100644 index 00000000..c4c6d70a --- /dev/null +++ b/code/espurna/power_emon.ino @@ -0,0 +1,144 @@ +/* + +POWER EMON MODULE + +Copyright (C) 2016-2017 by Xose Pérez + +*/ + +#if (POWER_PROVIDER & POWER_PROVIDER_EMON == POWER_PROVIDER_EMON) + +// ----------------------------------------------------------------------------- +// MODULE GLOBALS AND CACHE +// ----------------------------------------------------------------------------- + +#include +EmonLiteESP _emon; + +#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 + + #include "brzo_i2c.h" + + // ADC121 Registers + #define ADC121_REG_RESULT 0x00 + #define ADC121_REG_ALERT 0x01 + #define ADC121_REG_CONFIG 0x02 + #define ADC121_REG_LIMITL 0x03 + #define ADC121_REG_LIMITH 0x04 + #define ADC121_REG_HYST 0x05 + #define ADC121_REG_CONVL 0x06 + #define ADC121_REG_CONVH 0x07 + +#endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 + +// ----------------------------------------------------------------------------- +// HAL +// ----------------------------------------------------------------------------- + +unsigned int currentCallback() { + + #if POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG + + return analogRead(0); + + #endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG + + #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 + + uint8_t buffer[2]; + brzo_i2c_start_transaction(POWER_I2C_ADDRESS, I2C_SCL_FREQUENCY); + buffer[0] = ADC121_REG_RESULT; + brzo_i2c_write(buffer, 1, false); + brzo_i2c_read(buffer, 2, false); + brzo_i2c_end_transaction(); + unsigned int value; + value = (buffer[0] & 0x0F) << 8; + value |= buffer[1]; + return value; + + #endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 + +} + +// ----------------------------------------------------------------------------- + +double _powerCurrent() { + static unsigned long last = 0; + static double current = 0; + if (millis() - last > 1000) { + last = millis(); + current = _emon.getCurrent(POWER_SAMPLES); + current -= POWER_CURRENT_OFFSET; + if (current < 0) current = 0; + } + return current; +} + +double _powerVoltage() { + return _power_voltage; +} + +double _powerActivePower() { + return _powerApparentPower(); +} + +double _powerApparentPower() { + return _powerCurrent() * _powerVoltage(); +} + +double _powerReactivePower() { + return 0; +} + +double _powerPowerFactor() { + return 1; +} + + +// ----------------------------------------------------------------------------- +// PUBLIC API +// ----------------------------------------------------------------------------- + +void powerEnabledProvider() { + // Nothing to do +} + +void powerConfigureProvider() { + _emon.setCurrentRatio(getSetting("powerRatioC", POWER_CURRENT_RATIO).toFloat()); + _power_voltage = getSetting("powerVoltage", POWER_VOLTAGE).toFloat(); +} + +void powerSetupProvider() { + + _emon.initCurrent(currentCallback, POWER_ADC_BITS, POWER_REFERENCE_VOLTAGE, POWER_CURRENT_RATIO); + + #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 + uint8_t buffer[2]; + buffer[0] = ADC121_REG_CONFIG; + buffer[1] = 0x00; + brzo_i2c_start_transaction(POWER_I2C_ADDRESS, I2C_SCL_FREQUENCY); + brzo_i2c_write(buffer, 2, false); + brzo_i2c_end_transaction(); + #endif + + powerConfigureProvider(); + + _emon.warmup(); + +} + +void powerLoopProvider(bool before) { + + if (before) { + + static unsigned long last = 0; + if (millis() - last > POWER_READ_INTERVAL) { + last = millis(); + _power_newdata = true; + } + + } + +} + +#endif // (POWER_PROVIDER & POWER_PROVIDER_EMON == POWER_PROVIDER_EMON) diff --git a/code/espurna/power_hlw8012.ino b/code/espurna/power_hlw8012.ino new file mode 100644 index 00000000..6a606d0e --- /dev/null +++ b/code/espurna/power_hlw8012.ino @@ -0,0 +1,179 @@ +/* + +POWER HLW8012 MODULE + +Copyright (C) 2016-2017 by Xose Pérez + +*/ + +#if POWER_PROVIDER == POWER_PROVIDER_HLW8012 + +// ----------------------------------------------------------------------------- +// MODULE GLOBALS AND CACHE +// ----------------------------------------------------------------------------- + +#include +#include +HLW8012 _hlw8012; +WiFiEventHandler _power_wifi_onconnect; +WiFiEventHandler _power_wifi_ondisconnect; + +// ----------------------------------------------------------------------------- +// HAL +// ----------------------------------------------------------------------------- + +void ICACHE_RAM_ATTR _hlw_cf1_isr() { + _hlw8012.cf1_interrupt(); +} + +void ICACHE_RAM_ATTR _hlw_cf_isr() { + _hlw8012.cf_interrupt(); +} + +void _hlwSetCalibration() { + double value; + value = getSetting("powerRatioP", 0).toFloat(); + if (value > 0) _hlw8012.setPowerMultiplier(value); + value = getSetting("powerRatioC", 0).toFloat(); + if (value > 0) _hlw8012.setCurrentMultiplier(value); + value = getSetting("powerRatioV", 0).toFloat(); + if (value > 0) _hlw8012.setVoltageMultiplier(value); +} + +void _hlwGetCalibration() { + setSetting("powerRatioP", _hlw8012.getPowerMultiplier()); + setSetting("powerRatioC", _hlw8012.getCurrentMultiplier()); + setSetting("powerRatioV", _hlw8012.getVoltageMultiplier()); + saveSettings(); +} + +void _hlwResetCalibration() { + _hlw8012.resetMultipliers(); + _hlwGetCalibration(); +} + +void _hlwExpectedPower(unsigned int power) { + if (power > 0) { + _hlw8012.expectedActivePower(power); + _hlwGetCalibration(); + } +} + +void _hlwExpectedCurrent(double current) { + if (current > 0) { + _hlw8012.expectedCurrent(current); + _hlwGetCalibration(); + } +} + +void _hlwExpectedVoltage(unsigned int voltage) { + if (voltage > 0) { + _hlw8012.expectedVoltage(voltage); + _hlwGetCalibration(); + } +} + +// ----------------------------------------------------------------------------- + +double _powerCurrent() { + return _hlw8012.getCurrent(); +} + +double _powerVoltage() { + return _hlw8012.getVoltage(); +} + +double _powerActivePower() { + return _hlw8012.getActivePower(); +} + +double _powerApparentPower() { + return _hlw8012.getApparentPower(); +} + +double _powerReactivePower() { + double active = _powerActivePower(); + double apparent = _powerApparentPower(); + if (apparent > active) return sqrt(apparent * apparent - active * active); + return 0; +} + +double _powerPowerFactor() { + double apparent = _powerApparentPower(); + if (apparent > 0) return _powerActivePower() / apparent; + return 1; +} + +// ----------------------------------------------------------------------------- +// PUBLIC API +// ----------------------------------------------------------------------------- + +void powerEnabledProvider() { + if (_power_enabled) { + attachInterrupt(HLW8012_CF1_PIN, _hlw_cf1_isr, CHANGE); + attachInterrupt(HLW8012_CF_PIN, _hlw_cf_isr, CHANGE); + } else { + detachInterrupt(HLW8012_CF1_PIN); + detachInterrupt(HLW8012_CF_PIN); + } +} + +void powerConfigureProvider() { + _hlwSetCalibration(); + _hlwGetCalibration(); +} + +void powerSetupProvider() { + + // Initialize HLW8012 + // void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT); + // * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC + // * currentWhen is the value in sel_pin to select current sampling + // * set use_interrupts to true to use interrupts to monitor pulse widths + // * leave pulse_timeout to the default value, recommended when using interrupts + #if HLW8012_USE_INTERRUPTS + _hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, true); + #else + _hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, false, 1000000); + #endif + + // These values are used to calculate current, voltage and power factors as per datasheet formula + // These are the nominal values for the Sonoff POW resistors: + // * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line + // * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012 + // * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012 + _hlw8012.setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN); + + powerConfigureProvider(); + + _power_wifi_onconnect = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) { + powerEnabled(true); + }); + _power_wifi_ondisconnect = WiFi.onStationModeDisconnected([](WiFiEventStationModeDisconnected ipInfo) { + powerEnabled(false); + }); + +} + +void powerLoopProvider(bool before) { + + if (before) { + + static unsigned long last = 0; + if (millis() - last > POWER_READ_INTERVAL) { + last = millis(); + _power_newdata = true; + } + + } else { + + // Toggle between current and voltage monitoring + #if (HLW8012_USE_INTERRUPTS == 0) + _hlw8012.toggleMode(); + #endif // (HLW8012_USE_INTERRUPTS == 0) + + } + +} + +#endif // POWER_PROVIDER == POWER_PROVIDER_HLW8012 diff --git a/code/espurna/power_v9261f.ino b/code/espurna/power_v9261f.ino new file mode 100644 index 00000000..61e59b2a --- /dev/null +++ b/code/espurna/power_v9261f.ino @@ -0,0 +1,188 @@ +/* + +POWER V9261F MODULE + +Copyright (C) 2016-2017 by Xose Pérez + +*/ + +#if POWER_PROVIDER == POWER_PROVIDER_V9261F + +// ----------------------------------------------------------------------------- +// MODULE GLOBALS AND CACHE +// ----------------------------------------------------------------------------- + +#include +SoftwareSerial * _v9261f_uart; + +double _v9261f_active = 0; +double _v9261f_reactive = 0; +double _v9261f_voltage = 0; +double _v9261f_current = 0; +unsigned char _v9261f_data[24]; + +// ----------------------------------------------------------------------------- +// HAL +// ----------------------------------------------------------------------------- + +void _v9261fRead() { + + static unsigned char state = 0; + static unsigned long last = 0; + static bool found = false; + static unsigned char index = 0; + + if (state == 0) { + + while (_v9261f_uart->available()) { + _v9261f_uart->flush(); + found = true; + last = millis(); + } + + if (found && (millis() - last > V9261F_SYNC_INTERVAL)) { + _v9261f_uart->flush(); + index = 0; + state = 1; + } + + } else if (state == 1) { + + while (_v9261f_uart->available()) { + _v9261f_uart->read(); + if (index++ >= 7) { + _v9261f_uart->flush(); + index = 0; + state = 2; + } + } + + } else if (state == 2) { + + while (_v9261f_uart->available()) { + _v9261f_data[index] = _v9261f_uart->read(); + if (index++ >= 19) { + _v9261f_uart->flush(); + last = millis(); + state = 3; + } + } + + } else if (state == 3) { + + if (checksumOK()) { + + _v9261f_active = (double) ( + (_v9261f_data[3]) + + (_v9261f_data[4] << 8) + + (_v9261f_data[5] << 16) + + (_v9261f_data[6] << 24) + ) / V9261F_POWER_FACTOR; + + _v9261f_reactive = (double) ( + (_v9261f_data[7]) + + (_v9261f_data[8] << 8) + + (_v9261f_data[9] << 16) + + (_v9261f_data[10] << 24) + ) / V9261F_RPOWER_FACTOR; + + _v9261f_voltage = (double) ( + (_v9261f_data[11]) + + (_v9261f_data[12] << 8) + + (_v9261f_data[13] << 16) + + (_v9261f_data[14] << 24) + ) / V9261F_VOLTAGE_FACTOR; + + _v9261f_current = (double) ( + (_v9261f_data[15]) + + (_v9261f_data[16] << 8) + + (_v9261f_data[17] << 16) + + (_v9261f_data[18] << 24) + ) / V9261F_CURRENT_FACTOR; + + _power_newdata = true; + + } + + last = millis(); + index = 0; + state = 4; + + } else if (state == 4) { + + while (_v9261f_uart->available()) { + _v9261f_uart->flush(); + last = millis(); + } + + if (millis() - last > V9261F_SYNC_INTERVAL) { + state = 1; + } + + } + +} + +boolean checksumOK() { + unsigned char checksum = 0; + for (unsigned char i = 0; i < 19; i++) { + checksum = checksum + _v9261f_data[i]; + } + checksum = ~checksum + 0x33; + return checksum == _v9261f_data[19]; +} + +// ----------------------------------------------------------------------------- + +double _powerCurrent() { + return _v9261f_current; +} + +double _powerVoltage() { + return _v9261f_voltage; +} + +double _powerActivePower() { + return _v9261f_active; +} + +double _powerApparentPower() { + return sqrt(_v9261f_reactive * _v9261f_reactive + _v9261f_active * _v9261f_active); +} + +double _powerReactivePower() { + return _v9261f_reactive; +} + +double _powerPowerFactor() { + double apparent = _powerApparentPower(); + if (apparent > 0) return _powerActivePower() / apparent; + return 1; +} + +// ----------------------------------------------------------------------------- +// PUBLIC API +// ----------------------------------------------------------------------------- + +void powerEnabledProvider() { + // Nothing to do +} + +void powerConfigureProvider() { + // Nothing to do +} + +void powerSetupProvider() { + _v9261f_uart = new SoftwareSerial(V9261F_PIN, SW_SERIAL_UNUSED_PIN, V9261F_PIN_INVERSE, 256); + _v9261f_uart->begin(V9261F_BAUDRATE); +} + +void powerLoopProvider(bool before) { + + if (before) { + _v9261fRead(); + } + +} + +#endif // POWER_PROVIDER & POWER_PROVIDER_EMON diff --git a/code/espurna/v9261f.ino b/code/espurna/v9261f.ino index 3a2aea7f..f08eb48f 100644 --- a/code/espurna/v9261f.ino +++ b/code/espurna/v9261f.ino @@ -75,13 +75,6 @@ void v9261fRead() { } else if (state == 3) { - /* - for (unsigned char i=0; i