@ -1,4 +1,4 @@ | |||
#define APP_NAME "ESPURNA" | |||
#define APP_VERSION "1.9.4b" | |||
#define APP_VERSION "1.9.5" | |||
#define APP_AUTHOR "xose.perez@gmail.com" | |||
#define APP_WEBSITE "http://tinkerman.cat" |
@ -1,217 +0,0 @@ | |||
/* | |||
EMON MODULE | |||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if EMON_SUPPORT | |||
#include <EmonLiteESP.h> | |||
#include <EEPROM.h> | |||
#if EMON_PROVIDER == EMON_ADC121_PROVIDER | |||
#include "brzo_i2c.h" | |||
#endif | |||
// 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 | |||
EmonLiteESP emon; | |||
bool _emonReady = false; | |||
double _emonCurrent = 0; | |||
unsigned int _emonPower = 0; | |||
unsigned int _emonVoltage = 0; | |||
// ----------------------------------------------------------------------------- | |||
// Provider | |||
// ----------------------------------------------------------------------------- | |||
unsigned int currentCallback() { | |||
#if EMON_PROVIDER == EMON_ANALOG_PROVIDER | |||
return analogRead(EMON_CURRENT_PIN); | |||
#endif | |||
#if EMON_PROVIDER == EMON_ADC121_PROVIDER | |||
uint8_t buffer[2]; | |||
brzo_i2c_start_transaction(EMON_ADC121_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 | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// HAL | |||
// ----------------------------------------------------------------------------- | |||
void setCurrentRatio(float value) { | |||
emon.setCurrentRatio(value); | |||
} | |||
unsigned int getApparentPower() { | |||
return int(getCurrent() * getVoltage()); | |||
} | |||
double getCurrent() { | |||
double current = emon.getCurrent(EMON_SAMPLES); | |||
current -= EMON_CURRENT_OFFSET; | |||
if (current < 0) current = 0; | |||
return current; | |||
} | |||
unsigned int getVoltage() { | |||
return getSetting("emonVoltage", EMON_MAINS_VOLTAGE).toInt(); | |||
} | |||
// ----------------------------------------------------------------------------- | |||
void powerMonitorSetup() { | |||
// backwards compatibility | |||
String tmp; | |||
moveSetting("pwMainsVoltage", "emonVoltage"); | |||
moveSetting("emonMains", "emonVoltage"); | |||
moveSetting("pwCurrentRatio", "emonRatio"); | |||
emon.initCurrent( | |||
currentCallback, | |||
EMON_ADC_BITS, | |||
EMON_REFERENCE_VOLTAGE, | |||
getSetting("emonRatio", EMON_CURRENT_RATIO).toFloat() | |||
); | |||
#if EMON_PROVIDER == EMON_ADC121_PROVIDER | |||
uint8_t buffer[2]; | |||
buffer[0] = ADC121_REG_CONFIG; | |||
buffer[1] = 0x00; | |||
brzo_i2c_start_transaction(EMON_ADC121_ADDRESS, I2C_SCL_FREQUENCY); | |||
brzo_i2c_write(buffer, 2, false); | |||
brzo_i2c_end_transaction(); | |||
#endif | |||
#if WEB_SUPPORT | |||
apiRegister(EMON_APOWER_TOPIC, EMON_APOWER_TOPIC, [](char * buffer, size_t len) { | |||
if (_emonReady) { | |||
snprintf_P(buffer, len, PSTR("%d"), _emonPower); | |||
} else { | |||
buffer = NULL; | |||
} | |||
}); | |||
apiRegister(EMON_CURRENT_TOPIC, EMON_CURRENT_TOPIC, [](char * buffer, size_t len) { | |||
if (_emonReady) { | |||
dtostrf(_emonCurrent, len-1, 3, buffer); | |||
} else { | |||
buffer = NULL; | |||
} | |||
}); | |||
#endif // WEB_SUPPORT | |||
} | |||
void powerMonitorLoop() { | |||
static unsigned long next_measurement = millis(); | |||
static bool warmup = true; | |||
static byte measurements = 0; | |||
static double max = 0; | |||
static double min = 0; | |||
static double sum = 0; | |||
if (warmup) { | |||
warmup = false; | |||
emon.warmup(); | |||
} | |||
if (millis() > next_measurement) { | |||
int voltage = getVoltage(); | |||
{ | |||
double current = getCurrent(); | |||
if (measurements == 0) { | |||
max = min = current; | |||
} else { | |||
if (_emonCurrent > max) max = current; | |||
if (_emonCurrent < min) min = current; | |||
} | |||
sum += current; | |||
++measurements; | |||
DEBUG_MSG_P(PSTR("[ENERGY] Current: %sA\n"), String(current, 3).c_str()); | |||
DEBUG_MSG_P(PSTR("[ENERGY] Power: %dW\n"), int(current * voltage)); | |||
// Update websocket clients | |||
#if WEB_SUPPORT | |||
char buffer[100]; | |||
snprintf_P(buffer, sizeof(buffer), PSTR("{\"emonVisible\": 1, \"emonApparentPower\": %d, \"emonCurrent\": %s}"), int(current * voltage), String(current, 3).c_str()); | |||
wsSend(buffer); | |||
#endif | |||
} | |||
// Send MQTT messages averaged every EMON_MEASUREMENTS | |||
if (measurements == EMON_MEASUREMENTS) { | |||
// Calculate average current (removing max and min values) | |||
_emonCurrent = (sum - max - min) / (measurements - 2); | |||
_emonPower = (int) (_emonCurrent * voltage); | |||
_emonReady = true; | |||
// Calculate energy increment (ppower times time) | |||
double energy_delta = (double) _emonPower * EMON_INTERVAL * EMON_MEASUREMENTS / 1000.0 / 3600.0; | |||
// Report values to MQTT broker | |||
mqttSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), String(_emonPower).c_str()); | |||
mqttSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), String(_emonCurrent, 3).c_str()); | |||
mqttSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str()); | |||
// Report values to Domoticz | |||
#if DOMOTICZ_SUPPORT | |||
{ | |||
char buffer[20]; | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), _emonPower, String(energy_delta, 3).c_str()); | |||
domoticzSend("dczPowIdx", 0, buffer); | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(energy_delta, 3).c_str()); | |||
domoticzSend("dczEnergyIdx", 0, buffer); | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(_emonCurrent, 3).c_str()); | |||
domoticzSend("dczCurrentIdx", 0, buffer); | |||
} | |||
#endif | |||
#if INFLUXDB_SUPPORT | |||
influxDBSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), _emonPower); | |||
influxDBSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), String(_emonCurrent, 3).c_str()); | |||
influxDBSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str()); | |||
#endif | |||
// Reset counters | |||
sum = measurements = 0; | |||
} | |||
next_measurement += EMON_INTERVAL; | |||
} | |||
} | |||
#endif |
@ -1,344 +0,0 @@ | |||
/* | |||
POW MODULE | |||
Support for Sonoff POW HLW8012-based power monitor | |||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if HLW8012_SUPPORT | |||
#include <HLW8012.h> | |||
#include <Hash.h> | |||
#include <ArduinoJson.h> | |||
HLW8012 hlw8012; | |||
bool _hlw8012Enabled = false; | |||
bool _hlwReady = false; | |||
int _hlwPower = 0; | |||
double _hlwCurrent = 0; | |||
int _hlwVoltage = 0; | |||
// ----------------------------------------------------------------------------- | |||
// POW | |||
// ----------------------------------------------------------------------------- | |||
// When using interrupts we have to call the library entry point | |||
// whenever an interrupt is triggered | |||
void ICACHE_RAM_ATTR hlw8012_cf1_interrupt() { | |||
hlw8012.cf1_interrupt(); | |||
} | |||
void ICACHE_RAM_ATTR hlw8012_cf_interrupt() { | |||
hlw8012.cf_interrupt(); | |||
} | |||
void hlw8012Enable(bool status) { | |||
_hlw8012Enabled = status; | |||
if (_hlw8012Enabled) { | |||
#if HLW8012_USE_INTERRUPTS == 1 | |||
attachInterrupt(HLW8012_CF1_PIN, hlw8012_cf1_interrupt, CHANGE); | |||
attachInterrupt(HLW8012_CF_PIN, hlw8012_cf_interrupt, CHANGE); | |||
#endif | |||
DEBUG_MSG_P(PSTR("[POW] Enabled\n")); | |||
} else { | |||
#if HLW8012_USE_INTERRUPTS == 1 | |||
detachInterrupt(HLW8012_CF1_PIN); | |||
detachInterrupt(HLW8012_CF_PIN); | |||
#endif | |||
DEBUG_MSG_P(PSTR("[POW] Disabled\n")); | |||
} | |||
} | |||
// ----------------------------------------------------------------------------- | |||
void hlw8012SaveCalibration() { | |||
setSetting("powPowerMult", hlw8012.getPowerMultiplier()); | |||
setSetting("powCurrentMult", hlw8012.getCurrentMultiplier()); | |||
setSetting("powVoltageMult", hlw8012.getVoltageMultiplier()); | |||
saveSettings(); | |||
} | |||
void hlw8012RetrieveCalibration() { | |||
double value; | |||
value = getSetting("powPowerMult", 0).toFloat(); | |||
if (value > 0) hlw8012.setPowerMultiplier(value); | |||
value = getSetting("powCurrentMult", 0).toFloat(); | |||
if (value > 0) hlw8012.setCurrentMultiplier(value); | |||
value = getSetting("powVoltageMult", 0).toFloat(); | |||
if (value > 0) hlw8012.setVoltageMultiplier(value); | |||
} | |||
void hlw8012SetExpectedActivePower(unsigned int power) { | |||
if (power > 0) { | |||
hlw8012.expectedActivePower(power); | |||
hlw8012SaveCalibration(); | |||
} | |||
} | |||
void hlw8012SetExpectedCurrent(double current) { | |||
if (current > 0) { | |||
hlw8012.expectedCurrent(current); | |||
hlw8012SaveCalibration(); | |||
} | |||
} | |||
void hlw8012SetExpectedVoltage(unsigned int voltage) { | |||
if (voltage > 0) { | |||
hlw8012.expectedVoltage(voltage); | |||
hlw8012SaveCalibration(); | |||
} | |||
} | |||
void hlw8012Reset() { | |||
hlw8012.resetMultipliers(); | |||
hlw8012SaveCalibration(); | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// HAL | |||
// ----------------------------------------------------------------------------- | |||
unsigned int getActivePower() { | |||
unsigned int power = hlw8012.getActivePower(); | |||
if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0; | |||
return power; | |||
} | |||
unsigned int getApparentPower() { | |||
unsigned int power = hlw8012.getApparentPower(); | |||
if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0; | |||
return power; | |||
} | |||
unsigned int getReactivePower() { | |||
unsigned int power = hlw8012.getReactivePower(); | |||
if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0; | |||
return power; | |||
} | |||
double getCurrent() { | |||
double current = hlw8012.getCurrent(); | |||
if (HLW8012_MIN_CURRENT > current || current > HLW8012_MAX_CURRENT) current = 0; | |||
return current; | |||
} | |||
unsigned int getVoltage() { | |||
return hlw8012.getVoltage(); | |||
} | |||
double getPowerFactor() { | |||
return hlw8012.getPowerFactor(); | |||
} | |||
// ----------------------------------------------------------------------------- | |||
void hlw8012Setup() { | |||
// 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); | |||
// Retrieve calibration values | |||
hlw8012RetrieveCalibration(); | |||
// API definitions | |||
#if WEB_SUPPORT | |||
apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) { | |||
if (_hlwReady) { | |||
snprintf_P(buffer, len, PSTR("%d"), _hlwPower); | |||
} else { | |||
buffer = NULL; | |||
} | |||
}); | |||
apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) { | |||
if (_hlwReady) { | |||
dtostrf(_hlwCurrent, len-1, 3, buffer); | |||
} else { | |||
buffer = NULL; | |||
} | |||
}); | |||
apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) { | |||
if (_hlwReady) { | |||
snprintf_P(buffer, len, PSTR("%d"), _hlwVoltage); | |||
} else { | |||
buffer = NULL; | |||
} | |||
}); | |||
#endif // WEB_SUPPORT | |||
} | |||
void hlw8012Loop() { | |||
static unsigned long last_update = 0; | |||
static unsigned char report_count = HLW8012_REPORT_EVERY; | |||
static bool power_spike = false; | |||
static unsigned long power_sum = 0; | |||
static unsigned long power_previous = 0; | |||
static bool current_spike = false; | |||
static double current_sum = 0; | |||
static double current_previous = 0; | |||
static bool voltage_spike = false; | |||
static unsigned long voltage_sum = 0; | |||
static unsigned long voltage_previous = 0; | |||
static bool powWasEnabled = false; | |||
// POW is disabled while there is no internet connection | |||
// When the HLW8012 measurements are enabled back we reset the timer | |||
if (!_hlw8012Enabled) { | |||
powWasEnabled = false; | |||
return; | |||
} | |||
if (!powWasEnabled) { | |||
last_update = millis(); | |||
powWasEnabled = true; | |||
} | |||
if (millis() - last_update > HLW8012_UPDATE_INTERVAL) { | |||
last_update = millis(); | |||
unsigned int power = getActivePower(); | |||
unsigned int voltage = getVoltage(); | |||
double current = getCurrent(); | |||
if (power > 0) { | |||
power_spike = (power_previous == 0); | |||
} else if (power_spike) { | |||
power_sum -= power_previous; | |||
power_spike = false; | |||
} | |||
power_previous = power; | |||
if (current > 0) { | |||
current_spike = (current_previous == 0); | |||
} else if (current_spike) { | |||
current_sum -= current_previous; | |||
current_spike = false; | |||
} | |||
current_previous = current; | |||
if (voltage > 0) { | |||
voltage_spike = (voltage_previous == 0); | |||
} else if (voltage_spike) { | |||
voltage_sum -= voltage_previous; | |||
voltage_spike = false; | |||
} | |||
voltage_previous = voltage; | |||
#if WEB_SUPPORT | |||
{ | |||
unsigned int apparent = getApparentPower(); | |||
double factor = getPowerFactor(); | |||
unsigned int reactive = getReactivePower(); | |||
DynamicJsonBuffer jsonBuffer; | |||
JsonObject& root = jsonBuffer.createObject(); | |||
root["powVisible"] = 1; | |||
root["powActivePower"] = power; | |||
root["powCurrent"] = String(current, 3); | |||
root["powVoltage"] = voltage; | |||
root["powApparentPower"] = apparent; | |||
root["powReactivePower"] = reactive; | |||
root["powPowerFactor"] = String(factor, 2); | |||
String output; | |||
root.printTo(output); | |||
wsSend(output.c_str()); | |||
} | |||
#endif | |||
if (--report_count == 0) { | |||
// Update globals | |||
_hlwPower = power_sum / HLW8012_REPORT_EVERY; | |||
_hlwCurrent = current_sum / HLW8012_REPORT_EVERY; | |||
_hlwVoltage = voltage_sum / HLW8012_REPORT_EVERY; | |||
_hlwReady = true; | |||
// Calculate subproducts (apparent and reactive power, power factor and delta energy) | |||
unsigned int apparent = _hlwCurrent * _hlwVoltage; | |||
unsigned int reactive = (apparent > _hlwPower) ? sqrt(apparent * apparent - _hlwPower * _hlwPower) : 0; | |||
double factor = (apparent > 0) ? (double) _hlwPower / apparent : 1; | |||
if (factor > 1) factor = 1; | |||
double energy_delta = (double) _hlwPower * HLW8012_REPORT_EVERY * HLW8012_UPDATE_INTERVAL / 1000.0 / 3600.0; | |||
// Report values to MQTT broker | |||
mqttSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str()); | |||
mqttSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str()); | |||
mqttSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str()); | |||
mqttSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str()); | |||
mqttSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str()); | |||
mqttSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str()); | |||
mqttSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str()); | |||
// Report values to Domoticz | |||
#if DOMOTICZ_SUPPORT | |||
{ | |||
char buffer[20]; | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), _hlwPower, String(energy_delta, 3).c_str()); | |||
domoticzSend("dczPowIdx", 0, buffer); | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(energy_delta, 3).c_str()); | |||
domoticzSend("dczEnergyIdx", 0, buffer); | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _hlwVoltage); | |||
domoticzSend("dczVoltIdx", 0, buffer); | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(_hlwCurrent).c_str()); | |||
domoticzSend("dczCurrentIdx", 0, buffer); | |||
} | |||
#endif | |||
#if INFLUXDB_SUPPORT | |||
influxDBSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str()); | |||
influxDBSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str()); | |||
influxDBSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str()); | |||
influxDBSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str()); | |||
influxDBSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str()); | |||
influxDBSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str()); | |||
influxDBSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str()); | |||
#endif | |||
// Reset counters | |||
power_sum = current_sum = voltage_sum = 0; | |||
report_count = HLW8012_REPORT_EVERY; | |||
} | |||
// Post - Accumulators | |||
power_sum += power_previous; | |||
current_sum += current_previous; | |||
voltage_sum += voltage_previous; | |||
// Toggle between current and voltage monitoring | |||
#if HLW8012_USE_INTERRUPTS == 0 | |||
hlw8012.toggleMode(); | |||
#endif | |||
} | |||
} | |||
#endif |
@ -0,0 +1,70 @@ | |||
// ----------------------------------------------------------------------------- | |||
// Stream Injector | |||
// ----------------------------------------------------------------------------- | |||
#pragma once | |||
class MedianFilter { | |||
public: | |||
MedianFilter(unsigned char size) { | |||
_size = size; | |||
_data = new double[_size+1]; | |||
reset(); | |||
} | |||
virtual void reset() { | |||
if (_pointer > 1) _data[0] = _data[_pointer-1]; | |||
_pointer = 1; | |||
for (unsigned char i=1; 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; | |||
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; | |||
} | |||
sum /= (_pointer - 2); | |||
} | |||
if (do_reset) reset(); | |||
return sum; | |||
} | |||
virtual unsigned char count() { | |||
return _pointer - 1; | |||
} | |||
private: | |||
double *_data; | |||
unsigned char _size = 0; | |||
unsigned char _pointer = 0; | |||
}; |
@ -0,0 +1,334 @@ | |||
/* | |||
POWER MODULE | |||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if POWER_PROVIDER != POWER_PROVIDER_NONE | |||
// ----------------------------------------------------------------------------- | |||
// MODULE GLOBALS AND CACHE | |||
// ----------------------------------------------------------------------------- | |||
#include "power.h" | |||
#include <Hash.h> | |||
#include <ArduinoJson.h> | |||
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_BUFFER); | |||
#if POWER_HAS_ACTIVE | |||
double _power_active = 0; | |||
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 | |||
// ----------------------------------------------------------------------------- | |||
// 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_DECIMALS, 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_POWER_APPARENT, MQTT_TOPIC_POWER_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_ACTIVE, MQTT_TOPIC_POWER_ACTIVE, [](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 | |||
} | |||
void _powerRead() { | |||
// Get instantaneous values from HAL | |||
double current = _powerCurrent(); | |||
double voltage = _powerVoltage(); | |||
double apparent = _powerApparentPower(); | |||
#if POWER_HAS_ACTIVE | |||
double active = _powerActivePower(); | |||
double reactive = (apparent > active) ? sqrt(apparent * apparent - active * active) : 0; | |||
double factor = (apparent > 0) ? active / apparent : 1; | |||
if (factor > 1) factor = 1; | |||
#endif | |||
// Filters | |||
_filter_current.add(current); | |||
#if POWER_HAS_ACTIVE | |||
_filter_apparent.add(apparent); | |||
_filter_voltage.add(voltage); | |||
_filter_active.add(active); | |||
#endif | |||
/* THERE IS A BUG HERE SOMEWHERE :) | |||
char current_buffer[10]; | |||
dtostrf(current, sizeof(current_buffer)-1, POWER_CURRENT_DECIMALS, current_buffer); | |||
DEBUG_MSG_P(PSTR("[POWER] Current: %sA\n"), current_buffer); | |||
DEBUG_MSG_P(PSTR("[POWER] Voltage: %sA\n"), int(voltage)); | |||
DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), int(apparent)); | |||
#if POWER_HAS_ACTIVE | |||
DEBUG_MSG_P(PSTR("[POWER] Active Power: %dW\n"), int(active)); | |||
DEBUG_MSG_P(PSTR("[POWER] Reactive Power: %dW\n"), int(reactive)); | |||
DEBUG_MSG_P(PSTR("[POWER] Power Factor: %d%%\n"), int(100 * factor)); | |||
#endif | |||
*/ | |||
// Update websocket clients | |||
#if WEB_SUPPORT | |||
if (wsConnected()) { | |||
DynamicJsonBuffer jsonBuffer; | |||
JsonObject& root = jsonBuffer.createObject(); | |||
root["pwrVisible"] = 1; | |||
root["pwrCurrent"] = roundTo(current, POWER_CURRENT_DECIMALS); | |||
root["pwrVoltage"] = roundTo(voltage, POWER_VOLTAGE_DECIMALS); | |||
root["pwrApparent"] = roundTo(apparent, POWER_POWER_DECIMALS); | |||
#if POWER_HAS_ACTIVE | |||
root["pwrActive"] = roundTo(active, POWER_POWER_DECIMALS); | |||
root["pwrReactive"] = roundTo(reactive, POWER_POWER_DECIMALS); | |||
root["pwrFactor"] = int(100 * factor); | |||
#endif | |||
#if (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121) | |||
root["emonVisible"] = 1; | |||
#endif | |||
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012 | |||
root["hlwVisible"] = 1; | |||
#endif | |||
#if POWER_PROVIDER == POWER_PROVIDER_V9261F | |||
root["v9261fVisible"] = 1; | |||
#endif | |||
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560 | |||
root["ech1560Visible"] = 1; | |||
#endif | |||
String output; | |||
root.printTo(output); | |||
wsSend(output.c_str()); | |||
} | |||
#endif | |||
} | |||
void _powerReport() { | |||
// 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); | |||
if (_power_active > _power_apparent) _power_apparent = _power_active; | |||
_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; | |||
if (_power_factor > 1) _power_factor = 1; | |||
double power = _power_active; | |||
#else | |||
_power_apparent = _power_current * _power_voltage; | |||
double power = _power_apparent; | |||
#endif | |||
double energy_delta = power * POWER_ENERGY_FACTOR; | |||
_power_ready = true; | |||
char buf_current[10]; | |||
char buf_energy[10]; | |||
dtostrf(_power_current, 1-sizeof(buf_current), POWER_CURRENT_DECIMALS, buf_current); | |||
dtostrf(energy_delta, 1-sizeof(buf_energy), POWER_CURRENT_DECIMALS, 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 | |||
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 | |||
} | |||
#if DOMOTICZ_SUPPORT | |||
if (domoticzEnabled()) { | |||
char buffer[20]; | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, 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 | |||
} | |||
#endif | |||
#if INFLUXDB_SUPPORT | |||
if (influxdbEnabled()) { | |||
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 | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// MAGNITUDE API | |||
// ----------------------------------------------------------------------------- | |||
bool hasActivePower() { | |||
return POWER_HAS_ACTIVE; | |||
} | |||
double getCurrent() { | |||
return roundTo(_power_current, POWER_CURRENT_DECIMALS); | |||
} | |||
double getVoltage() { | |||
return roundTo(_power_voltage, POWER_VOLTAGE_DECIMALS); | |||
} | |||
double getApparentPower() { | |||
return roundTo(_power_apparent, POWER_POWER_DECIMALS); | |||
} | |||
#if POWER_HAS_ACTIVE | |||
double getActivePower() { | |||
return roundTo(_power_active, POWER_POWER_DECIMALS); | |||
} | |||
double getReactivePower() { | |||
return roundTo(_power_reactive, POWER_POWER_DECIMALS); | |||
} | |||
double getPowerFactor() { | |||
return roundTo(_power_factor, 2); | |||
} | |||
#endif | |||
// ----------------------------------------------------------------------------- | |||
// PUBLIC API | |||
// ----------------------------------------------------------------------------- | |||
bool powerEnabled() { | |||
return _power_enabled; | |||
} | |||
void powerEnabled(bool enabled) { | |||
if (enabled & !_power_enabled) _powerReset(); | |||
_power_enabled = enabled; | |||
_powerEnabledProvider(); | |||
} | |||
void powerCalibrate(unsigned char magnitude, double value) { | |||
_powerCalibrateProvider(magnitude, value); | |||
} | |||
void powerResetCalibration() { | |||
_powerResetCalibrationProvider(); | |||
} | |||
void powerConfigure() { | |||
_powerConfigureProvider(); | |||
} | |||
void powerSetup() { | |||
// backwards compatibility | |||
moveSetting("pwMainsVoltage", "pwrVoltage"); | |||
moveSetting("emonMains", "pwrVoltage"); | |||
moveSetting("emonVoltage", "pwrVoltage"); | |||
moveSetting("pwCurrentRatio", "pwrRatioC"); | |||
moveSetting("emonRatio", "pwrRatioC"); | |||
moveSetting("powPowerMult", "pwrRatioP"); | |||
moveSetting("powCurrentMult", "pwrRatioC"); | |||
moveSetting("powVoltageMult", "pwrRatioV"); | |||
moveSetting("powerVoltage", "pwrVoltage"); | |||
moveSetting("powerRatioC", "pwrRatioC"); | |||
moveSetting("powerRatioV", "pwrRatioV"); | |||
moveSetting("powerRatioP", "pwrRatioP"); | |||
_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(); | |||
} | |||
static unsigned long last = 0; | |||
if (millis() - last > POWER_REPORT_INTERVAL) { | |||
last = millis(); | |||
_powerReport(); | |||
} | |||
_powerLoopProvider(false); | |||
} | |||
#endif // POWER_PROVIDER != POWER_PROVIDER_NONE |
@ -0,0 +1,207 @@ | |||
/* | |||
POWER ECH1560 MODULE | |||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560 | |||
// ----------------------------------------------------------------------------- | |||
// MODULE GLOBALS AND CACHE | |||
// ----------------------------------------------------------------------------- | |||
volatile long _ech1560_bits_count = 0; | |||
volatile long _ech1560_clk_count = 0; | |||
volatile bool _ech1560_dosync = false; | |||
volatile bool _ech1560_nextbit = true; | |||
double _ech1560_apparent = 0; | |||
double _ech1560_voltage = 0; | |||
double _ech1560_current = 0; | |||
// ----------------------------------------------------------------------------- | |||
// HAL | |||
// ----------------------------------------------------------------------------- | |||
void _ech1560_sync() { | |||
unsigned int byte1 = 0; | |||
unsigned int byte2 = 0; | |||
unsigned int byte3 = 0; | |||
_ech1560_bits_count = 0; | |||
while (_ech1560_bits_count < 40); // skip the uninteresting 5 first bytes | |||
_ech1560_bits_count = 0; | |||
while (_ech1560_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in Ba and Bb | |||
if (_ech1560_nextbit) { | |||
if (_ech1560_bits_count < 9) { // first Byte/8 bits in Ba | |||
byte1 = byte1 << 1; | |||
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte1 |= 1; | |||
_ech1560_nextbit = false; | |||
} else if (_ech1560_bits_count < 17) { // bit 9-16 is byte 7, stor in Bb | |||
byte2 = byte2 << 1; | |||
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte2 |= 1; | |||
_ech1560_nextbit = false; | |||
} | |||
} | |||
} | |||
if (byte2 != 3) { // if bit Bb is not 3, we have reached the important part, U is allready in Ba and Bb and next 8 Bytes will give us the Power. | |||
// voltage = 2 * (Ba + Bb / 255) | |||
_ech1560_voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0); | |||
// power: | |||
_ech1560_bits_count = 0; | |||
while (_ech1560_bits_count < 40); // skip the uninteresting 5 first bytes | |||
_ech1560_bits_count = 0; | |||
byte1 = 0; | |||
byte2 = 0; | |||
byte3 = 0; | |||
while (_ech1560_bits_count < 24) { //store byte 6, 7 and 8 in Ba and Bb & Bc. | |||
if (_ech1560_nextbit) { | |||
if (_ech1560_bits_count < 9) { | |||
byte1 = byte1 << 1; | |||
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte1 |= 1; | |||
_ech1560_nextbit = false; | |||
} else if (_ech1560_bits_count < 17) { | |||
byte2 = byte2 << 1; | |||
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte2 |= 1; | |||
_ech1560_nextbit = false; | |||
} else { | |||
byte3 = byte3 << 1; | |||
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte3 |= 1; | |||
_ech1560_nextbit = false; | |||
} | |||
} | |||
} | |||
#if ECH1560_INVERTED | |||
byte1 = 255 - byte1; | |||
byte2 = 255 - byte2; | |||
byte3 = 255 - byte3; | |||
#endif | |||
// power = (Ba*255+Bb+Bc/255)/2 | |||
_ech1560_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2; | |||
_ech1560_current = _ech1560_apparent / _ech1560_voltage; | |||
_power_newdata = true; | |||
_ech1560_dosync = false; | |||
} | |||
// If Bb is not 3 or something else than 0, something is wrong! | |||
if (byte2 == 0) _ech1560_dosync = false; | |||
} | |||
void ICACHE_RAM_ATTR _ech1560_isr() { | |||
// if we are trying to find the sync-time (CLK goes high for 1-2ms) | |||
if (_ech1560_dosync == false) { | |||
_ech1560_clk_count = 0; | |||
// register how long the ClkHigh is high to evaluate if we are at the part wher clk goes high for 1-2 ms | |||
while (digitalRead(ECH1560_CLK_PIN) == HIGH) { | |||
_ech1560_clk_count += 1; | |||
delayMicroseconds(30); //can only use delayMicroseconds in an interrupt. | |||
} | |||
// if the Clk was high between 1 and 2 ms than, its a start of a SPI-transmission | |||
if (_ech1560_clk_count >= 33 && _ech1560_clk_count <= 67) { | |||
_ech1560_dosync = true; | |||
} | |||
// we are in sync and logging CLK-highs | |||
} else { | |||
// increment an integer to keep track of how many bits we have read. | |||
_ech1560_bits_count += 1; | |||
_ech1560_nextbit = true; | |||
} | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// POWER API | |||
// ----------------------------------------------------------------------------- | |||
double _powerCurrent() { | |||
return _ech1560_current; | |||
} | |||
double _powerVoltage() { | |||
return _ech1560_voltage; | |||
} | |||
double _powerActivePower() { | |||
return 0; | |||
} | |||
double _powerApparentPower() { | |||
return _ech1560_apparent; | |||
} | |||
double _powerReactivePower() { | |||
return 0; | |||
} | |||
double _powerPowerFactor() { | |||
return 1; | |||
} | |||
void _powerEnabledProvider() { | |||
// Nothing to do | |||
} | |||
void _powerConfigureProvider() { | |||
// Nothing to do | |||
} | |||
void _powerCalibrateProvider(unsigned char magnitude, double value) { | |||
// Nothing to do | |||
} | |||
void _powerResetCalibrationProvider() { | |||
// Nothing to do | |||
} | |||
void _powerSetupProvider() { | |||
pinMode(ECH1560_CLK_PIN, INPUT); | |||
pinMode(ECH1560_MISO_PIN, INPUT); | |||
attachInterrupt(ECH1560_CLK_PIN, _ech1560_isr, RISING); | |||
} | |||
void _powerLoopProvider(bool before) { | |||
if (!before) { | |||
if (_ech1560_dosync) _ech1560_sync(); | |||
} | |||
} | |||
#endif // POWER_PROVIDER == POWER_PROVIDER_ECH1560 |
@ -0,0 +1,164 @@ | |||
/* | |||
POWER EMON MODULE | |||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121) | |||
// ----------------------------------------------------------------------------- | |||
// MODULE GLOBALS AND CACHE | |||
// ----------------------------------------------------------------------------- | |||
#include <EmonLiteESP.h> | |||
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(A0); | |||
#endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG | |||
#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121 | |||
uint8_t buffer[2]; | |||
brzo_i2c_start_transaction(ADC121_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 | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// POWER API | |||
// ----------------------------------------------------------------------------- | |||
double _powerCurrent() { | |||
static unsigned long last = 0; | |||
static double current = 0; | |||
if (millis() - last > 1000) { | |||
last = millis(); | |||
current = _emon.getCurrent(EMON_SAMPLES); | |||
current -= EMON_CURRENT_OFFSET; | |||
if (current < 0) current = 0; | |||
} | |||
return current; | |||
} | |||
double _powerVoltage() { | |||
return _power_voltage; | |||
} | |||
double _powerActivePower() { | |||
return 0; | |||
} | |||
double _powerApparentPower() { | |||
return _powerCurrent() * _powerVoltage(); | |||
} | |||
double _powerReactivePower() { | |||
return 0; | |||
} | |||
double _powerPowerFactor() { | |||
return 1; | |||
} | |||
void _powerEnabledProvider() { | |||
// Nothing to do | |||
} | |||
void _powerCalibrateProvider(unsigned char magnitude, double value) { | |||
if (value <= 0) return; | |||
if (magnitude == POWER_MAGNITUDE_ACTIVE) { | |||
double power = _powerApparentPower(); | |||
double ratio = getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat(); | |||
ratio = ratio * (value / power); | |||
_emon.setCurrentRatio(ratio); | |||
setSetting("pwrRatioC", ratio); | |||
saveSettings(); | |||
} | |||
if (magnitude == POWER_MAGNITUDE_VOLTAGE) { | |||
_power_voltage = value; | |||
setSetting("pwrVoltage", value); | |||
saveSettings(); | |||
} | |||
} | |||
void _powerResetCalibrationProvider() { | |||
delSetting("pwrRatioC"); | |||
_powerConfigureProvider(); | |||
saveSettings(); | |||
} | |||
void _powerConfigureProvider() { | |||
_emon.setCurrentRatio(getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat()); | |||
_power_voltage = getSetting("pwrVoltage", POWER_VOLTAGE).toFloat(); | |||
} | |||
void _powerSetupProvider() { | |||
_emon.initCurrent(currentCallback, EMON_ADC_BITS, EMON_REFERENCE_VOLTAGE, EMON_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(ADC121_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_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121) |
@ -0,0 +1,161 @@ | |||
/* | |||
POWER HLW8012 MODULE | |||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012 | |||
// ----------------------------------------------------------------------------- | |||
// MODULE GLOBALS AND CACHE | |||
// ----------------------------------------------------------------------------- | |||
#include <HLW8012.h> | |||
#include <ESP8266WiFi.h> | |||
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 _hlwRestoreCalibration() { | |||
double value; | |||
value = getSetting("pwrRatioP", 0).toFloat(); | |||
if (value > 0) _hlw8012.setPowerMultiplier(value); | |||
value = getSetting("pwrRatioC", 0).toFloat(); | |||
if (value > 0) _hlw8012.setCurrentMultiplier(value); | |||
value = getSetting("pwrRatioV", 0).toFloat(); | |||
if (value > 0) _hlw8012.setVoltageMultiplier(value); | |||
} | |||
void _hlwPersistCalibration() { | |||
setSetting("pwrRatioP", _hlw8012.getPowerMultiplier()); | |||
setSetting("pwrRatioC", _hlw8012.getCurrentMultiplier()); | |||
setSetting("pwrRatioV", _hlw8012.getVoltageMultiplier()); | |||
saveSettings(); | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// POWER API | |||
// ----------------------------------------------------------------------------- | |||
double _powerCurrent() { | |||
return _hlw8012.getCurrent(); | |||
} | |||
double _powerVoltage() { | |||
return _hlw8012.getVoltage(); | |||
} | |||
double _powerActivePower() { | |||
return _hlw8012.getActivePower(); | |||
} | |||
double _powerApparentPower() { | |||
return _hlw8012.getApparentPower(); | |||
} | |||
double _powerReactivePower() { | |||
return _hlw8012.getReactivePower(); | |||
} | |||
double _powerPowerFactor() { | |||
return _hlw8012.getPowerFactor(); | |||
} | |||
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 _powerCalibrateProvider(unsigned char magnitude, double value) { | |||
if (value <= 0) return; | |||
if (magnitude == POWER_MAGNITUDE_ACTIVE) _hlw8012.expectedActivePower(value); | |||
if (magnitude == POWER_MAGNITUDE_CURRENT) _hlw8012.expectedCurrent(value); | |||
if (magnitude == POWER_MAGNITUDE_VOLTAGE) _hlw8012.expectedVoltage(value); | |||
_hlwPersistCalibration(); | |||
} | |||
void _powerResetCalibrationProvider() { | |||
_hlw8012.resetMultipliers(); | |||
delSetting("pwrRatioC"); | |||
delSetting("pwrRatioV"); | |||
delSetting("pwrRatioP"); | |||
saveSettings(); | |||
} | |||
void _powerConfigureProvider() { | |||
// Nothing to do | |||
} | |||
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); | |||
_hlwRestoreCalibration(); | |||
_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; | |||
// Toggle between current and voltage monitoring | |||
#if (HLW8012_USE_INTERRUPTS == 0) | |||
_hlw8012.toggleMode(); | |||
#endif // (HLW8012_USE_INTERRUPTS == 0) | |||
} | |||
} | |||
} | |||
#endif // POWER_PROVIDER == POWER_PROVIDER_HLW8012 |
@ -0,0 +1,234 @@ | |||
/* | |||
POWER V9261F MODULE | |||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if POWER_PROVIDER == POWER_PROVIDER_V9261F | |||
// ----------------------------------------------------------------------------- | |||
// MODULE GLOBALS AND CACHE | |||
// ----------------------------------------------------------------------------- | |||
#include <SoftwareSerial.h> | |||
SoftwareSerial * _v9261f_uart; | |||
double _v9261f_active = 0; | |||
double _v9261f_reactive = 0; | |||
double _v9261f_voltage = 0; | |||
double _v9261f_current = 0; | |||
double _v9261f_ratioP = V9261F_POWER_FACTOR; | |||
double _v9261f_ratioC = V9261F_CURRENT_FACTOR; | |||
double _v9261f_ratioV = V9261F_VOLTAGE_FACTOR; | |||
double _v9261f_ratioR = V9261F_RPOWER_FACTOR; | |||
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 (_v9261fChecksum()) { | |||
_v9261f_active = (double) ( | |||
(_v9261f_data[3]) + | |||
(_v9261f_data[4] << 8) + | |||
(_v9261f_data[5] << 16) + | |||
(_v9261f_data[6] << 24) | |||
) / _v9261f_ratioP; | |||
_v9261f_reactive = (double) ( | |||
(_v9261f_data[7]) + | |||
(_v9261f_data[8] << 8) + | |||
(_v9261f_data[9] << 16) + | |||
(_v9261f_data[10] << 24) | |||
) / _v9261f_ratioR; | |||
_v9261f_voltage = (double) ( | |||
(_v9261f_data[11]) + | |||
(_v9261f_data[12] << 8) + | |||
(_v9261f_data[13] << 16) + | |||
(_v9261f_data[14] << 24) | |||
) / _v9261f_ratioV; | |||
_v9261f_current = (double) ( | |||
(_v9261f_data[15]) + | |||
(_v9261f_data[16] << 8) + | |||
(_v9261f_data[17] << 16) + | |||
(_v9261f_data[18] << 24) | |||
) / _v9261f_ratioC; | |||
if (_v9261f_active < 0) _v9261f_active = 0; | |||
if (_v9261f_reactive < 0) _v9261f_reactive = 0; | |||
if (_v9261f_voltage < 0) _v9261f_voltage = 0; | |||
if (_v9261f_current < 0) _v9261f_current = 0; | |||
_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; | |||
} | |||
} | |||
} | |||
bool _v9261fChecksum() { | |||
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]; | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// POWER API | |||
// ----------------------------------------------------------------------------- | |||
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; | |||
} | |||
void _powerEnabledProvider() { | |||
// Nothing to do | |||
} | |||
void _powerConfigureProvider() { | |||
_v9261f_ratioP = getSetting("pwrRatioP", V9261F_POWER_FACTOR).toFloat(); | |||
_v9261f_ratioV = getSetting("pwrRatioV", V9261F_VOLTAGE_FACTOR).toFloat(); | |||
_v9261f_ratioC = getSetting("pwrRatioC", V9261F_CURRENT_FACTOR).toFloat(); | |||
_v9261f_ratioR = getSetting("pwrRatioR", V9261F_RPOWER_FACTOR).toFloat(); | |||
} | |||
void _powerCalibrateProvider(unsigned char magnitude, double value) { | |||
if (value <= 0) return; | |||
if (magnitude == POWER_MAGNITUDE_ACTIVE) { | |||
_v9261f_ratioP = _v9261f_ratioP * (_v9261f_active / value); | |||
setSetting("pwrRatioP", _v9261f_ratioP); | |||
} | |||
if (magnitude == POWER_MAGNITUDE_CURRENT) { | |||
_v9261f_ratioC = _v9261f_ratioC * (_v9261f_current / value); | |||
setSetting("pwrRatioC", _v9261f_ratioC); | |||
} | |||
if (magnitude == POWER_MAGNITUDE_VOLTAGE) { | |||
_v9261f_ratioV = _v9261f_ratioV * (_v9261f_voltage / value); | |||
setSetting("pwrRatioV", _v9261f_ratioV); | |||
} | |||
if (magnitude == POWER_MAGNITUDE_POWER_FACTOR) { | |||
if (value < 100) { | |||
double apparent = _v9261f_ratioP / (value / 100); | |||
value = sqrt(apparent * apparent - _v9261f_ratioP * _v9261f_ratioP); | |||
_v9261f_ratioR = _v9261f_ratioR * (_v9261f_reactive / value); | |||
setSetting("pwrRatioR", _v9261f_ratioR); | |||
} | |||
} | |||
saveSettings(); | |||
} | |||
void _powerResetCalibrationProvider() { | |||
delSetting("pwrRatioP"); | |||
delSetting("pwrRatioC"); | |||
delSetting("pwrRatioV"); | |||
delSetting("pwrRatioR"); | |||
_powerConfigureProvider(); | |||
saveSettings(); | |||
} | |||
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_V9261F |