From 17418d85b2fbd065b6c57aae2cc4712d965daadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xose=20P=C3=A9rez?= Date: Sat, 9 Sep 2017 12:01:41 +0200 Subject: [PATCH] First power.ino module --- code/espurna/config/arduino.h | 1 - code/espurna/config/general.h | 99 ++++++- code/espurna/config/hardware.h | 10 +- code/espurna/config/sensors.h | 74 ----- code/espurna/emon.ino | 4 +- code/espurna/espurna.ino | 27 +- code/espurna/hlw8012.ino | 10 +- code/espurna/power.h | 111 +++++++ code/espurna/power.ino | 526 +++++++++++++++++++++++++++++++++ code/espurna/web.ino | 54 ++-- code/espurna/wifi.ino | 14 - 11 files changed, 770 insertions(+), 160 deletions(-) create mode 100644 code/espurna/power.h create mode 100644 code/espurna/power.ino diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index d2afba64..3fdd0451 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -54,7 +54,6 @@ //#define DHT_SUPPORT 1 //#define DOMOTICZ_SUPPORT 0 //#define DS18B20_SUPPORT 1 -//#define EMON_SUPPORT 1 //#define HOMEASSISTANT_SUPPORT 0 //#define I2C_SUPPORT 1 //#define INFLUXDB_SUPPORT 0 diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index e13234b1..a910a753 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -423,7 +423,16 @@ PROGMEM const char* const custom_reset_string[] = { #define MQTT_TOPIC_RFIN "rfin" #define MQTT_TOPIC_RFLEARN "rflearn" -// Lights +// 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" + +// Light module #define MQTT_TOPIC_CHANNEL "channel" #define MQTT_TOPIC_COLOR "color" #define MQTT_TOPIC_BRIGHTNESS "brightness" @@ -454,19 +463,6 @@ PROGMEM const char* const custom_reset_string[] = { #define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit #endif -// ----------------------------------------------------------------------------- -// I2C -// ----------------------------------------------------------------------------- - -#ifndef I2C_SUPPORT -#define I2C_SUPPORT 0 // I2C enabled -#endif - -#define I2C_SDA_PIN 4 // SDA GPIO -#define I2C_SCL_PIN 14 // SCL GPIO -#define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time -#define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency - // ----------------------------------------------------------------------------- // LIGHT // ----------------------------------------------------------------------------- @@ -495,6 +491,81 @@ PROGMEM const char* const custom_reset_string[] = { #define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value #define LIGHT_USE_GAMMA 0 // Use gamma correction for color channels +// ----------------------------------------------------------------------------- +// POWER METERING +// ----------------------------------------------------------------------------- + +// Available power-metering providers +#define POWER_PROVIDER_NONE 0x00 +#define POWER_PROVIDER_EMON 0x10 +#define POWER_PROVIDER_EMON_ANALOG 0x10 +#define POWER_PROVIDER_EMON_ADC121 0x11 +#define POWER_PROVIDER_HLW8012 0x20 + +// Available magnitudes +#define POWER_MAGNITUDE_CURRENT 1 +#define POWER_MAGNITUDE_VOLTAGE 2 +#define POWER_MAGNITUDE_ACTIVE 4 +#define POWER_MAGNITUDE_APPARENT 8 + +// No power provider defined +#ifndef POWER_PROVIDER +#define POWER_PROVIDER POWER_PROVIDER_NONE +#endif + +// Identify available magnitudes +#if POWER_PROVIDER == POWER_PROVIDER_HLW8012 +#define POWER_HAS_ACTIVE 1 +#else +#define POWER_HAS_ACTIVE 0 +#endif + +#define POWER_CURRENT_PRECISION 3 +#define POWER_VOLTAGE 230 +#define POWER_CURRENT_RATIO 30 +#define POWER_SAMPLES 1000 +#define POWER_INTERVAL 10000 +#define POWER_REPORT_EVERY 6 +#define POWER_ENERGY_FACTOR (POWER_INTERVAL * POWER_REPORT_EVERY / 1000.0 / 3600.0) + +#if POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG + #define POWER_ADC_BITS 10 + #define POWER_REFERENCE_VOLTAGE 1.0 + #define POWER_CURRENT_OFFSET 0.25 + #undef ADC_VCC_ENABLED + #define ADC_VCC_ENABLED 0 +#endif + +#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 + #define POWER_I2C_ADDRESS 0x50 + #define POWER_ADC_BITS 12 + #define POWER_REFERENCE_VOLTAGE 3.3 + #define POWER_CURRENT_OFFSET 0.10 + #undef I2C_SUPPORT + #define I2C_SUPPORT 1 +#endif + +#if POWER_PROVIDER == POWER_PROVIDER_HLW8012 + #define HLW8012_USE_INTERRUPTS 1 + #define HLW8012_SEL_CURRENT HIGH + #define HLW8012_CURRENT_R 0.001 + #define HLW8012_VOLTAGE_R_UP ( 5 * 470000 ) // Real: 2280k + #define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Real 1.009k +#endif + +// ----------------------------------------------------------------------------- +// I2C +// ----------------------------------------------------------------------------- + +#ifndef I2C_SUPPORT +#define I2C_SUPPORT 0 // I2C enabled +#endif + +#define I2C_SDA_PIN 4 // SDA GPIO +#define I2C_SCL_PIN 14 // SCL GPIO +#define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time +#define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency + // ----------------------------------------------------------------------------- // DOMOTICZ // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 0d65134f..f01392cb 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -42,6 +42,12 @@ #define LED1_PIN 2 #define LED1_PIN_INVERSE 1 + // HLW8012 + #define POWER_PROVIDER POWER_PROVIDER_EMON_ANALOG + #define HLW8012_SEL_PIN 2 + #define HLW8012_CF1_PIN 13 + #define HLW8012_CF_PIN 14 + #elif defined(WEMOS_D1_MINI_RELAYSHIELD) // Info @@ -83,7 +89,7 @@ #define LED1_PIN_INVERSE 0 // HLW8012 - #define HLW8012_SUPPORT 1 + #define POWER_PROVIDER POWER_PROVIDER_HLW8012 #define HLW8012_SEL_PIN 2 #define HLW8012_CF1_PIN 13 #define HLW8012_CF_PIN 14 @@ -245,7 +251,7 @@ #define LED1_PIN_INVERSE 0 // HLW8012 - #define HLW8012_SUPPORT 1 + #define POWER_PROVIDER POWER_PROVIDER_HLW8012 #define HLW8012_SEL_PIN 5 #define HLW8012_CF1_PIN 13 #define HLW8012_CF_PIN 14 diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index 33ad37f4..a3498dfc 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -81,80 +81,6 @@ #define DS18B20_UPDATE_INTERVAL 60000 #define DS18B20_TEMPERATURE_TOPIC "temperature" -//-------------------------------------------------------------------------------- -// Custom current sensor -// Check http://tinkerman.cat/your-laundry-is-done/ -// Check http://tinkerman.cat/power-monitoring-sonoff-th-adc121/ -// Enable support by passing EMON_SUPPORT=1 build flag -//-------------------------------------------------------------------------------- - -#ifndef EMON_SUPPORT -#define EMON_SUPPORT 0 -#endif - -#define EMON_ANALOG_PROVIDER 0 -#define EMON_ADC121_PROVIDER 1 - -// If you select EMON_ADC121_PROVIDER you need to enable and configure I2C in general.h -#define EMON_PROVIDER EMON_ANALOG_PROVIDER - -#if EMON_PROVIDER == EMON_ANALOG_PROVIDER - #define EMON_CURRENT_PIN 0 - #define EMON_ADC_BITS 10 - #define EMON_REFERENCE_VOLTAGE 1.0 - #define EMON_CURRENT_OFFSET 0.25 - #if EMON_SUPPORT - #undef ADC_VCC_ENABLED - #define ADC_VCC_ENABLED 0 - #endif -#endif - -#if EMON_PROVIDER == EMON_ADC121_PROVIDER - #define EMON_ADC121_ADDRESS 0x50 - #define EMON_ADC_BITS 12 - #define EMON_REFERENCE_VOLTAGE 3.3 - #define EMON_CURRENT_OFFSET 0.10 -#endif - -#define EMON_CURRENT_RATIO 30 -#define EMON_SAMPLES 1000 -#define EMON_INTERVAL 10000 -#define EMON_MEASUREMENTS 6 -#define EMON_MAINS_VOLTAGE 230 -#define EMON_APOWER_TOPIC "apower" -#define EMON_ENERGY_TOPIC "energy" -#define EMON_CURRENT_TOPIC "current" - -//-------------------------------------------------------------------------------- -// HLW8012 power sensor (Sonoff POW, Espurna H) -// Enable support by passing HLW8012_SUPPORT=1 build flag -//-------------------------------------------------------------------------------- - -#ifndef HLW8012_SUPPORT -#define HLW8012_SUPPORT 0 -#endif - -// GPIOs defined in the hardware.h file - -#define HLW8012_USE_INTERRUPTS 1 -#define HLW8012_SEL_CURRENT HIGH -#define HLW8012_CURRENT_R 0.001 -#define HLW8012_VOLTAGE_R_UP ( 5 * 470000 ) // Real: 2280k -#define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Real 1.009k -#define HLW8012_POWER_TOPIC "power" -#define HLW8012_CURRENT_TOPIC "current" -#define HLW8012_VOLTAGE_TOPIC "voltage" -#define HLW8012_APOWER_TOPIC "apower" -#define HLW8012_RPOWER_TOPIC "rpower" -#define HLW8012_PFACTOR_TOPIC "pfactor" -#define HLW8012_ENERGY_TOPIC "energy" -#define HLW8012_UPDATE_INTERVAL 5000 -#define HLW8012_REPORT_EVERY 12 -#define HLW8012_MIN_POWER 5 -#define HLW8012_MAX_POWER 2500 -#define HLW8012_MIN_CURRENT 0.05 -#define HLW8012_MAX_CURRENT 10 - //-------------------------------------------------------------------------------- // Internal power montior // Enable support by passing ADC_VCC_ENABLED=1 build flag diff --git a/code/espurna/emon.ino b/code/espurna/emon.ino index 160e698b..5baf3fae 100644 --- a/code/espurna/emon.ino +++ b/code/espurna/emon.ino @@ -6,10 +6,10 @@ Copyright (C) 2016-2017 by Xose Pérez */ +/* #if EMON_SUPPORT #include -#include #if EMON_PROVIDER == EMON_ADC121_PROVIDER #include "brzo_i2c.h" #endif @@ -83,7 +83,6 @@ unsigned int getVoltage() { void powerMonitorSetup() { // backwards compatibility - String tmp; moveSetting("pwMainsVoltage", "emonVoltage"); moveSetting("emonMains", "emonVoltage"); moveSetting("pwCurrentRatio", "emonRatio"); @@ -215,3 +214,4 @@ void powerMonitorLoop() { } #endif +*/ diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 5b6fa25e..f1012566 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -155,12 +155,6 @@ void welcome() { #if DS18B20_SUPPORT DEBUG_MSG_P(PSTR(" DS18B20")); #endif - #if EMON_SUPPORT - DEBUG_MSG_P(PSTR(" EMON")); - #endif - #if HLW8012_SUPPORT - DEBUG_MSG_P(PSTR(" HLW8012")); - #endif #if HOMEASSISTANT_SUPPORT DEBUG_MSG_P(PSTR(" HOMEASSISTANT")); #endif @@ -224,7 +218,6 @@ void setup() { settingsSetup(); if (getSetting("hostname").length() == 0) { setSetting("hostname", getIdentifier()); - saveSettings(); } delay(500); @@ -253,6 +246,9 @@ void setup() { rfbSetup(); #endif + #if POWER_PROVIDER != POWER_PROVIDER_NONE + powerSetup(); + #endif #if NTP_SUPPORT ntpSetup(); #endif @@ -268,9 +264,6 @@ void setup() { #if INFLUXDB_SUPPORT influxDBSetup(); #endif - #if HLW8012_SUPPORT - hlw8012Setup(); - #endif #if DS18B20_SUPPORT dsSetup(); #endif @@ -286,9 +279,6 @@ void setup() { #if RF_SUPPORT rfSetup(); #endif - #if EMON_SUPPORT - powerMonitorSetup(); - #endif #if DOMOTICZ_SUPPORT domoticzSetup(); #endif @@ -296,6 +286,8 @@ void setup() { // Prepare configuration for version 2.0 hwUpwardsCompatibility(); + saveSettings(); + } void loop() { @@ -317,6 +309,9 @@ void loop() { rfbLoop(); #endif + #if POWER_PROVIDER != POWER_PROVIDER_NONE + powerLoop(); + #endif #if NTP_SUPPORT ntpLoop(); #endif @@ -326,9 +321,6 @@ void loop() { #if NOFUSS_SUPPORT nofussLoop(); #endif - #if HLW8012_SUPPORT - hlw8012Loop(); - #endif #if DS18B20_SUPPORT dsLoop(); #endif @@ -344,8 +336,5 @@ void loop() { #if RF_SUPPORT rfLoop(); #endif - #if EMON_SUPPORT - powerMonitorLoop(); - #endif } diff --git a/code/espurna/hlw8012.ino b/code/espurna/hlw8012.ino index 928311bf..2380c094 100644 --- a/code/espurna/hlw8012.ino +++ b/code/espurna/hlw8012.ino @@ -7,6 +7,8 @@ Copyright (C) 2016-2017 by Xose Pérez */ +/* + #if HLW8012_SUPPORT #include @@ -124,9 +126,9 @@ double getCurrent() { return current; } -unsigned int getVoltage() { - return hlw8012.getVoltage(); -} +//unsigned int getVoltage() { +// return hlw8012.getVoltage(); +//} double getPowerFactor() { return hlw8012.getPowerFactor(); @@ -342,3 +344,5 @@ void hlw8012Loop() { } #endif + +*/ diff --git a/code/espurna/power.h b/code/espurna/power.h new file mode 100644 index 00000000..02c7bd00 --- /dev/null +++ b/code/espurna/power.h @@ -0,0 +1,111 @@ +// ----------------------------------------------------------------------------- +// Stream Injector +// ----------------------------------------------------------------------------- + +#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: + + MedianFilter(unsigned char size) { + _size = size; + _data = new double[_size+1]; + reset(); + } + + virtual void reset() { + _data[0] = _data[_size]; + _pointer = 1; + for (unsigned char i=_pointer; i<=_size; i++) _data[i] = 0; + } + + virtual void add(double value) { + if (_pointer <= _size) { + _data[_pointer] = value; + ++_pointer; + } + } + + 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 (previous > current) std::swap(previous, current); + if (current > next) std::swap(current, next); + if (previous > current) std::swap(previous, current); + + sum += current; + + } + + if (do_reset) reset(); + + return sum / (_size-1); + + } + + virtual unsigned char count() { + return _pointer - 1; + } + + private: + + double *_data; + unsigned char _size = 0; + unsigned char _pointer = 0; + +}; diff --git a/code/espurna/power.ino b/code/espurna/power.ino new file mode 100644 index 00000000..b198643b --- /dev/null +++ b/code/espurna/power.ino @@ -0,0 +1,526 @@ +/* + +POWER MODULE + +Copyright (C) 2016-2017 by Xose Pérez + +*/ + +#if POWER_PROVIDER != POWER_PROVIDER_NONE + +// ----------------------------------------------------------------------------- +// MODULE GLOBALS AND CACHE +// ----------------------------------------------------------------------------- + +#include "power.h" +#include +#include + +bool _power_enabled = false; +bool _power_ready = false; + +double _power_current = 0; +double _power_voltage = 0; +double _power_apparent = 0; +MedianFilter _filter_current = MedianFilter(POWER_REPORT_EVERY); + +#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); +#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 +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +void _powerAPISetup() { + + apiRegister(MQTT_TOPIC_CURRENT, MQTT_TOPIC_CURRENT, [](char * buffer, size_t len) { + if (_power_ready) { + dtostrf(getCurrent(), len-1, POWER_CURRENT_PRECISION, buffer); + } else { + buffer = NULL; + } + }); + + apiRegister(MQTT_TOPIC_VOLTAGE, MQTT_TOPIC_VOLTAGE, [](char * buffer, size_t len) { + if (_power_ready) { + snprintf_P(buffer, len, PSTR("%d"), getVoltage()); + } else { + buffer = NULL; + } + }); + + apiRegister(MQTT_TOPIC_APPARENT, MQTT_TOPIC_APPARENT, [](char * buffer, size_t len) { + if (_power_ready) { + snprintf_P(buffer, len, PSTR("%d"), getApparentPower()); + } else { + buffer = NULL; + } + }); + + #if POWER_HAS_ACTIVE + apiRegister(MQTT_TOPIC_POWER, MQTT_TOPIC_POWER, [](char * buffer, size_t len) { + if (_power_ready) { + snprintf_P(buffer, len, PSTR("%d"), getActivePower()); + } else { + buffer = NULL; + } + }); + #endif + +} + +#endif // WEB_SUPPORT + +void _powerReset() { + _filter_current.reset(); + #if POWER_HAS_ACTIVE + _filter_apparent.reset(); + _filter_voltage.reset(); + _filter_active.reset(); + #endif +} + +// ----------------------------------------------------------------------------- +// MAGNITUDE API +// ----------------------------------------------------------------------------- + +bool hasActivePower() { + return POWER_HAS_ACTIVE; +} + +double getCurrent() { + return _power_current; +} + +double getVoltage() { + return _power_voltage; +} + +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; +} + +double getPowerFactor() { + if (_power_active > _power_apparent) return 1; + if (_power_apparent == 0) return 0; + return (double) _power_active / _power_apparent; +} +#endif + +// ----------------------------------------------------------------------------- +// PUBLIC API +// ----------------------------------------------------------------------------- + +bool powerEnabled() { + return _power_enabled; +} + +void powerEnabled(bool enabled) { + _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 +} + +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); + +} + +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(); + + // Get instantaneous values from HAL + double current = _powerCurrent(); + double voltage = _powerVoltage(); + double apparent = _powerApparentPower(); + #if POWER_HAS_ACTIVE + double active = _powerActivePower(); + #endif + + // Filters + _filter_current.add(current); + #if POWER_HAS_ACTIVE + _filter_apparent.add(apparent); + _filter_voltage.add(voltage); + _filter_active.add(active); + #endif + + char current_buffer[10]; + dtostrf(current, sizeof(current_buffer)-1, POWER_CURRENT_PRECISION, current_buffer); + DEBUG_MSG_P(PSTR("[POWER] Current: %sA\n"), current_buffer); + DEBUG_MSG_P(PSTR("[POWER] Voltage: %sA\n"), voltage); + DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), apparent); + #if POWER_HAS_ACTIVE + DEBUG_MSG_P(PSTR("[POWER] Active Power: %dW\n"), active); + DEBUG_MSG_P(PSTR("[POWER] Reactive Power: %dW\n"), getReactivePower()); + DEBUG_MSG_P(PSTR("[POWER] Power Factor: %d%%\n"), 100 * getPowerFactor()); + #endif + + // Update websocket clients + #if WEB_SUPPORT + { + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + root["powerVisible"] = 1; + root["powerCurrent"] = String(current_buffer); + root["powerVoltage"] = voltage; + root["powerApparentPower"] = apparent; + #if POWER_HAS_ACTIVE + root["powerActivePower"] = active; + root["powerReactivePower"] = getReactivePower(); + root["powerPowerfactor"] = int(100 * getPowerFactor()); + #endif + String output; + root.printTo(output); + wsSend(output.c_str()); + } + #endif + + // Send MQTT messages averaged every POWER_REPORT_EVERY measurements + if (_filter_current.count() == POWER_REPORT_EVERY) { + + // 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); + double power = _power_active; + #else + _power_apparent = _power_current * _power_voltage; + double power = _power_apparent; + #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 + + } + #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 + + } + + // 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) +} + +#endif // POWER_PROVIDER != POWER_PROVIDER_NONE diff --git a/code/espurna/web.ino b/code/espurna/web.ino index eed32a45..17859769 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -10,6 +10,7 @@ Copyright (C) 2016-2017 by Xose Pérez #include #include +#include #include #include #include @@ -228,26 +229,26 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { // Skip firmware filename if (key.equals("filename")) continue; - #if HLW8012_SUPPORT + #if POWER_PROVIDER == POWER_PROVIDER_HLW8012 if (key == "powExpectedPower") { - hlw8012SetExpectedActivePower(value.toInt()); + _hlwExpectedPower(value.toInt()); changed = true; } if (key == "powExpectedVoltage") { - hlw8012SetExpectedVoltage(value.toInt()); + _hlwExpectedVoltage(value.toInt()); changed = true; } if (key == "powExpectedCurrent") { - hlw8012SetExpectedCurrent(value.toFloat()); + _hlwExpectedCurrent(value.toFloat()); changed = true; } if (key == "powExpectedReset") { if (value.toInt() == 1) { - hlw8012Reset(); + _hlwResetCalibration(); changed = true; } } @@ -388,8 +389,8 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { #if RF_SUPPORT rfBuildCodes(); #endif - #if EMON_SUPPORT - setCurrentRatio(getSetting("emonRatio").toFloat()); + #if POWER_PROVIDER != POWER_PROVIDER_NONE + powerConfigure(); #endif #if NTP_SUPPORT if (changedNTP) ntpConnect(); @@ -533,21 +534,17 @@ void _wsStart(uint32_t client_id) { root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt(); #endif - #if EMON_SUPPORT - root["dczPowIdx"] = getSetting("dczPowIdx").toInt(); - root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt(); - root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt(); - #endif - #if ANALOG_SUPPORT root["dczAnaIdx"] = getSetting("dczAnaIdx").toInt(); #endif - #if HLW8012_SUPPORT + #if POWER_PROVIDER != POWER_PROVIDER_NONE root["dczPowIdx"] = getSetting("dczPowIdx").toInt(); root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt(); - root["dczVoltIdx"] = getSetting("dczVoltIdx").toInt(); root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt(); + #if POWER_HAS_ACTIVE + root["dczVoltIdx"] = getSetting("dczVoltIdx").toInt(); + #endif #endif #endif @@ -583,14 +580,6 @@ void _wsStart(uint32_t client_id) { root["rfDevice"] = getSetting("rfDevice", RF_DEVICE); #endif - #if EMON_SUPPORT - root["emonVisible"] = 1; - root["emonApparentPower"] = getApparentPower(); - root["emonCurrent"] = getCurrent(); - root["emonVoltage"] = getVoltage(); - root["emonRatio"] = getSetting("emonRatio", EMON_CURRENT_RATIO); - #endif - #if ANALOG_SUPPORT root["analogVisible"] = 1; root["analogValue"] = getAnalog(); @@ -601,14 +590,17 @@ void _wsStart(uint32_t client_id) { root["counterValue"] = getCounter(); #endif - #if HLW8012_SUPPORT - root["powVisible"] = 1; - root["powActivePower"] = getActivePower(); - root["powApparentPower"] = getApparentPower(); - root["powReactivePower"] = getReactivePower(); - root["powVoltage"] = getVoltage(); - root["powCurrent"] = String(getCurrent(), 3); - root["powPowerFactor"] = String(getPowerFactor(), 2); + #if POWER_PROVIDER != POWER_PROVIDER_NONE + root["powerVisible"] = 1; + root["powerCurrent"] = getCurrent(); + root["powerVoltage"] = getVoltage(); + root["powerApparentPower"] = getApparentPower(); + #if POWER_HAS_ACTIVE + root["powerActivePower"] = getActivePower(); + root["powerReactivePower"] = getReactivePower(); + root["powerPowerfactor"] = int(100 * getPowerFactor()); + #endif + root["powerRatioC"] = getSetting("powerRatioC", POWER_CURRENT_RATIO); #endif #if NOFUSS_SUPPORT diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 09daf1da..41796d08 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -28,9 +28,6 @@ String getNetwork() { } void wifiDisconnect() { - #if HLW8012_SUPPORT - hlw8012Enable(false); - #endif jw.disconnect(); } @@ -263,17 +260,6 @@ void wifiSetup() { } #endif - // Manage POW - #if HLW8012_SUPPORT - if (code == MESSAGE_CONNECTED) { - hlw8012Enable(true); - } - if (code == MESSAGE_DISCONNECTED) { - hlw8012Enable(false); - } - #endif - - }); }