@ -1,423 +0,0 @@ | |||||
/* | |||||
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 "filters/MedianFilter.h" | |||||
#include <Hash.h> | |||||
#include <ArduinoJson.h> | |||||
bool _power_enabled = false; | |||||
bool _power_newdata = false; | |||||
bool _power_realtime = API_REAL_TIME_VALUES; | |||||
unsigned long _power_read_interval = POWER_READ_INTERVAL; | |||||
unsigned long _power_report_interval = POWER_REPORT_INTERVAL; | |||||
double _power_current = 0; | |||||
double _power_voltage = 0; | |||||
double _power_apparent = 0; | |||||
double _power_energy = 0; | |||||
MedianFilter _filter_current = MedianFilter(); | |||||
#if POWER_HAS_ACTIVE | |||||
double _power_active = 0; | |||||
double _power_reactive = 0; | |||||
double _power_factor = 0; | |||||
MedianFilter _filter_voltage = MedianFilter(); | |||||
MedianFilter _filter_active = MedianFilter(); | |||||
MedianFilter _filter_apparent = MedianFilter(); | |||||
#endif | |||||
#if POWER_HAS_ENERGY | |||||
double _power_last_energy = 0; | |||||
#endif | |||||
// ----------------------------------------------------------------------------- | |||||
// PRIVATE METHODS | |||||
// ----------------------------------------------------------------------------- | |||||
#if WEB_SUPPORT | |||||
void _powerWebSocketOnSend(JsonObject& root) { | |||||
root["pwrVisible"] = 1; | |||||
root["pwrCurrent"] = getCurrent(); | |||||
root["pwrVoltage"] = getVoltage(); | |||||
root["pwrApparent"] = getApparentPower(); | |||||
root["pwrEnergy"] = getPowerEnergy(); | |||||
root["pwrReadEvery"] = powerReadInterval(); | |||||
root["pwrReportEvery"] = powerReportInterval(); | |||||
#if POWER_HAS_ACTIVE | |||||
root["pwrActive"] = getActivePower(); | |||||
root["pwrReactive"] = getReactivePower(); | |||||
root["pwrFactor"] = int(100 * getPowerFactor()); | |||||
#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["ech1560fVisible"] = 1; | |||||
#endif | |||||
} | |||||
void _powerAPISetup() { | |||||
apiRegister(MQTT_TOPIC_CURRENT, MQTT_TOPIC_CURRENT, [](char * buffer, size_t len) { | |||||
dtostrf(_power_realtime ? _powerCurrent() : getCurrent(), 1-len, POWER_CURRENT_DECIMALS, buffer); | |||||
}); | |||||
apiRegister(MQTT_TOPIC_VOLTAGE, MQTT_TOPIC_VOLTAGE, [](char * buffer, size_t len) { | |||||
snprintf_P(buffer, len, PSTR("%d"), (int) (_power_realtime ? _powerVoltage() : getVoltage())); | |||||
}); | |||||
apiRegister(MQTT_TOPIC_POWER_APPARENT, MQTT_TOPIC_POWER_APPARENT, [](char * buffer, size_t len) { | |||||
snprintf_P(buffer, len, PSTR("%d"), (int) (_power_realtime ? _powerApparentPower() : getApparentPower())); | |||||
}); | |||||
#if POWER_HAS_ENERGY | |||||
apiRegister(MQTT_TOPIC_ENERGY_TOTAL, MQTT_TOPIC_ENERGY_TOTAL, [](char * buffer, size_t len) { | |||||
snprintf_P(buffer, len, PSTR("%lu"), (int) (_power_realtime ? _powerEnergy() : getPowerEnergy())); | |||||
}); | |||||
#endif | |||||
#if POWER_HAS_ACTIVE | |||||
apiRegister(MQTT_TOPIC_POWER_ACTIVE, MQTT_TOPIC_POWER_ACTIVE, [](char * buffer, size_t len) { | |||||
snprintf_P(buffer, len, PSTR("%d"), (int) (_power_realtime ? _powerActivePower() : getActivePower())); | |||||
}); | |||||
apiRegister(MQTT_TOPIC_POWER_FACTOR, MQTT_TOPIC_POWER_FACTOR, [](char * buffer, size_t len) { | |||||
snprintf_P(buffer, len, PSTR("%d"), (int) (100 * (_power_realtime ? _powerPowerFactor() : getPowerFactor()))); | |||||
}); | |||||
#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 | |||||
#if POWER_HAS_ENERGY | |||||
_power_energy = _powerEnergy(); // Due to its nature this value doesn't have to be filtered | |||||
#endif | |||||
// Filters | |||||
_filter_current.add(current); | |||||
#if POWER_HAS_ACTIVE | |||||
_filter_apparent.add(apparent); | |||||
_filter_voltage.add(voltage); | |||||
_filter_active.add(active); | |||||
#endif | |||||
// Debug | |||||
/* | |||||
char current_buffer[10]; | |||||
dtostrf(current, 1-sizeof(current_buffer), POWER_CURRENT_DECIMALS, current_buffer); | |||||
DEBUG_MSG_P(PSTR("[POWER] Current: %sA\n"), current_buffer); | |||||
DEBUG_MSG_P(PSTR("[POWER] Voltage: %dV\n"), (int) voltage); | |||||
DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), (int) apparent); | |||||
DEBUG_MSG_P(PSTR("[POWER] Energy: %dJ\n"), (int) _power_energy); | |||||
#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); | |||||
root["pwrEnergy"] = roundTo(_power_energy, POWER_ENERGY_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 | |||||
#if POWER_HAS_ACTIVE | |||||
double max_power = _filter_active.max(); | |||||
_power_current = _filter_current.result(); | |||||
_power_voltage = _filter_voltage.result(); | |||||
_power_active = _filter_active.result(); | |||||
_power_apparent = _filter_apparent.result(); | |||||
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 | |||||
double max_power = _filter_current.max() * _power_voltage; | |||||
_power_current = _filter_current.result(); | |||||
_power_apparent = _power_current * _power_voltage; | |||||
double power = _power_apparent; | |||||
#endif | |||||
#if POWER_HAS_ENERGY | |||||
double energy_delta = _power_energy - _power_last_energy; | |||||
_power_last_energy = _power_energy; | |||||
#else | |||||
double energy_delta = power * (_power_report_interval / 1000.); | |||||
_power_energy += energy_delta; | |||||
#endif | |||||
char buf_current[10]; | |||||
char buf_energy_delta[20]; | |||||
char buf_energy_total[20]; | |||||
dtostrf(_power_current, 1-sizeof(buf_current), POWER_CURRENT_DECIMALS, buf_current); | |||||
dtostrf(energy_delta * POWER_ENERGY_FACTOR, 1-sizeof(buf_energy_delta), POWER_ENERGY_DECIMALS, buf_energy_delta); | |||||
dtostrf(_power_energy * POWER_ENERGY_FACTOR, 1-sizeof(buf_energy_total), POWER_ENERGY_DECIMALS, buf_energy_total); | |||||
#if MQTT_SUPPORT | |||||
{ | |||||
mqttSend(MQTT_TOPIC_CURRENT, buf_current); | |||||
mqttSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str()); | |||||
mqttSend(MQTT_TOPIC_MAX_POWER, String((int) max_power).c_str()); | |||||
mqttSend(MQTT_TOPIC_ENERGY_DELTA, buf_energy_delta); | |||||
mqttSend(MQTT_TOPIC_ENERGY_TOTAL, buf_energy_total); | |||||
#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 | |||||
} | |||||
#endif | |||||
#if DOMOTICZ_SUPPORT | |||||
if (domoticzEnabled()) { | |||||
// Domoticz expects energy in kWh | |||||
char buf_energy_kwh[10]; | |||||
dtostrf(energy_delta * POWER_ENERGY_FACTOR_KWH, 1-sizeof(buf_energy_kwh), POWER_ENERGY_DECIMALS_KWH, buf_energy_kwh); | |||||
char buffer[20]; | |||||
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), (int) power, buf_energy_kwh); | |||||
domoticzSend("dczPowIdx", 0, buffer); | |||||
domoticzSend("dczCurrentIdx", 0, buf_current); | |||||
domoticzSend("dczEnergyIdx", 0, buf_energy_kwh); | |||||
#if POWER_HAS_ACTIVE | |||||
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), (int) _power_voltage); | |||||
domoticzSend("dczVoltIdx", 0, buffer); | |||||
#endif | |||||
} | |||||
#endif | |||||
#if INFLUXDB_SUPPORT | |||||
if (idbEnabled()) { | |||||
idbSend(MQTT_TOPIC_CURRENT, buf_current); | |||||
idbSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str()); | |||||
idbSend(MQTT_TOPIC_MAX_POWER, String((int) max_power).c_str()); | |||||
idbSend(MQTT_TOPIC_ENERGY_DELTA, buf_energy_delta); | |||||
idbSend(MQTT_TOPIC_ENERGY_TOTAL, buf_energy_total); | |||||
#if POWER_HAS_ACTIVE | |||||
idbSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str()); | |||||
idbSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str()); | |||||
idbSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str()); | |||||
idbSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str()); | |||||
#endif | |||||
} | |||||
#endif | |||||
_powerReset(); | |||||
} | |||||
void _powerConfigure() { | |||||
_power_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1; | |||||
_power_read_interval = atol(getSetting("pwrReadEvery", POWER_READ_INTERVAL).c_str()); | |||||
_power_report_interval = atol(getSetting("pwrReportEvery", POWER_REPORT_INTERVAL).c_str()); | |||||
if (_power_read_interval < POWER_MIN_READ_INTERVAL) { | |||||
_power_read_interval = POWER_MIN_READ_INTERVAL; | |||||
setSetting("pwrReadEvery", _power_read_interval); | |||||
} | |||||
if (_power_report_interval < _power_read_interval) { | |||||
_power_report_interval = _power_read_interval; | |||||
setSetting("pwrReportEvery", _power_report_interval); | |||||
} | |||||
_powerConfigureProvider(); | |||||
} | |||||
// ----------------------------------------------------------------------------- | |||||
// 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); | |||||
} | |||||
double getPowerEnergy() { | |||||
roundTo(_power_energy, POWER_ENERGY_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 | |||||
// ----------------------------------------------------------------------------- | |||||
unsigned long powerReadInterval() { | |||||
return _power_read_interval; | |||||
} | |||||
unsigned long powerReportInterval() { | |||||
return _power_report_interval; | |||||
} | |||||
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 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(); | |||||
_powerConfigure(); | |||||
// API | |||||
#if WEB_SUPPORT | |||||
wsOnSendRegister(_powerWebSocketOnSend); | |||||
wsOnAfterParseRegister(_powerConfigure); | |||||
_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 |
@ -1,207 +0,0 @@ | |||||
/* | |||||
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 |
@ -1,184 +0,0 @@ | |||||
/* | |||||
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 | |||||
#if I2C_USE_BRZO | |||||
#include "brzo_i2c.h" | |||||
#else | |||||
#include "Wire.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 | |||||
#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 | |||||
unsigned int value; | |||||
#if I2C_USE_BRZO | |||||
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(); | |||||
value = (buffer[0] & 0x0F) << 8; | |||||
value |= buffer[1]; | |||||
#else | |||||
Wire.beginTransmission(ADC121_I2C_ADDRESS); | |||||
Wire.write(ADC121_REG_RESULT); | |||||
Wire.endTransmission(); | |||||
Wire.requestFrom(ADC121_I2C_ADDRESS, 2); | |||||
value = (Wire.read() & 0x0F) << 8; | |||||
value = value + Wire.read(); | |||||
#endif | |||||
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 | |||||
#if I2C_USE_BRZO | |||||
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(); | |||||
#else | |||||
Wire.beginTransmission(ADC121_I2C_ADDRESS); | |||||
Wire.write(ADC121_REG_CONFIG); | |||||
Wire.write(0x00); | |||||
Wire.endTransmission(); | |||||
#endif | |||||
#endif | |||||
_emon.warmup(); | |||||
} | |||||
void _powerLoopProvider(bool before) { | |||||
if (before) { | |||||
static unsigned long last = 0; | |||||
if (millis() - last > powerReadInterval()) { | |||||
last = millis(); | |||||
_power_newdata = true; | |||||
} | |||||
} | |||||
} | |||||
#endif // (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121) |
@ -1,170 +0,0 @@ | |||||
/* | |||||
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(); | |||||
} | |||||
double _powerEnergy() { | |||||
return _hlw8012.getEnergy(); | |||||
} | |||||
void _powerEnabledProvider() { | |||||
#if 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 _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(); | |||||
#if HLW8012_USE_INTERRUPTS | |||||
powerEnabled(true); //Always keep measurement active to keep track of energy used | |||||
#else | |||||
_power_wifi_onconnect = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) { | |||||
powerEnabled(true); | |||||
}); | |||||
_power_wifi_ondisconnect = WiFi.onStationModeDisconnected([](WiFiEventStationModeDisconnected ipInfo) { | |||||
powerEnabled(false); | |||||
}); | |||||
#endif | |||||
} | |||||
void _powerLoopProvider(bool before) { | |||||
if (before) { | |||||
static unsigned long last = 0; | |||||
if (millis() - last > powerReadInterval()) { | |||||
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 |
@ -1,234 +0,0 @@ | |||||
/* | |||||
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 |
@ -0,0 +1,258 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// Event Counter Sensor | |||||
// Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com> | |||||
// ----------------------------------------------------------------------------- | |||||
#pragma once | |||||
#include "Arduino.h" | |||||
#include "BaseSensor.h" | |||||
#include <ESP8266WiFi.h> | |||||
#include <HLW8012.h> | |||||
class HLW8012Sensor : public BaseSensor { | |||||
public: | |||||
// --------------------------------------------------------------------- | |||||
// Public | |||||
// --------------------------------------------------------------------- | |||||
HLW8012Sensor(): BaseSensor() { | |||||
_count = 7; | |||||
_sensor_id = SENSOR_HLW8012_ID; | |||||
} | |||||
~HLW8012Sensor() { | |||||
detach(_interrupt_cf); | |||||
detach(_interrupt_cf1); | |||||
} | |||||
void expectedCurrent(double expected) { | |||||
_hlw8012->expectedCurrent(expected); | |||||
} | |||||
void expectedVoltage(unsigned int expected) { | |||||
_hlw8012->expectedVoltage(expected); | |||||
} | |||||
void expectedPower(unsigned int expected) { | |||||
_hlw8012->expectedActivePower(expected); | |||||
} | |||||
void resetRatios() { | |||||
_hlw8012->resetMultipliers(); | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
void setSEL(unsigned char sel) { | |||||
if (_sel == sel) return; | |||||
_sel = sel; | |||||
_dirty = true; | |||||
} | |||||
void setCF(unsigned char cf) { | |||||
if (_cf == cf) return; | |||||
_cf = cf; | |||||
_dirty = true; | |||||
} | |||||
void setCF1(unsigned char cf1) { | |||||
if (_cf1 == cf1) return; | |||||
_cf1 = cf1; | |||||
_dirty = true; | |||||
} | |||||
void setSELCurrent(bool value) { | |||||
_sel_current = value; | |||||
} | |||||
void setCurrentRatio(double value) { | |||||
_hlw8012->setCurrentMultiplier(value); | |||||
}; | |||||
void setVoltageRatio(double value) { | |||||
_hlw8012->setVoltageMultiplier(value); | |||||
}; | |||||
void setPowerRatio(double value) { | |||||
_hlw8012->setPowerMultiplier(value); | |||||
}; | |||||
// --------------------------------------------------------------------- | |||||
unsigned char getSEL() { | |||||
return _sel; | |||||
} | |||||
unsigned char getCF() { | |||||
return _cf; | |||||
} | |||||
unsigned char getCF1() { | |||||
return _cf1; | |||||
} | |||||
unsigned char getSELCurrent() { | |||||
return _sel_current; | |||||
} | |||||
double getCurrentRatio() { | |||||
return _hlw8012->getCurrentMultiplier(); | |||||
}; | |||||
double getVoltageRatio() { | |||||
return _hlw8012->getVoltageMultiplier(); | |||||
}; | |||||
double getPowerRatio() { | |||||
return _hlw8012->getPowerMultiplier(); | |||||
}; | |||||
// --------------------------------------------------------------------- | |||||
// Sensors API | |||||
// --------------------------------------------------------------------- | |||||
// Initialization method, must be idempotent | |||||
// Defined outside the class body | |||||
void begin() { | |||||
if (_hlw8012) delete _hlw8012; | |||||
_hlw8012 = new 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(_cf, _cf1, _sel, _sel_current, true); | |||||
#else | |||||
_hlw8012->begin(_cf, _cf1, _sel, _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); | |||||
// Handle interrupts | |||||
#if HLW8012_USE_INTERRUPTS | |||||
_enable(true); | |||||
#else | |||||
_onconnect_handler = WiFi.onStationModeGotIP([this](WiFiEventStationModeGotIP ipInfo) { | |||||
_enable(true); | |||||
}); | |||||
_ondisconnect_handler = WiFi.onStationModeDisconnected([this](WiFiEventStationModeDisconnected ipInfo) { | |||||
_enable(false); | |||||
}); | |||||
#endif | |||||
} | |||||
// Descriptive name of the sensor | |||||
String description() { | |||||
char buffer[25]; | |||||
snprintf(buffer, sizeof(buffer), "HLW8012 @ GPIO(%i,%i,%i)", _sel, _cf, _cf1); | |||||
return String(buffer); | |||||
} | |||||
// Type for slot # index | |||||
magnitude_t type(unsigned char index) { | |||||
_error = SENSOR_ERROR_OK; | |||||
if (index == 0) return MAGNITUDE_CURRENT; | |||||
if (index == 1) return MAGNITUDE_VOLTAGE; | |||||
if (index == 2) return MAGNITUDE_POWER_ACTIVE; | |||||
if (index == 3) return MAGNITUDE_POWER_REACTIVE; | |||||
if (index == 4) return MAGNITUDE_POWER_APPARENT; | |||||
if (index == 5) return MAGNITUDE_POWER_FACTOR; | |||||
if (index == 6) return MAGNITUDE_ENERGY; | |||||
_error = SENSOR_ERROR_OUT_OF_RANGE; | |||||
return MAGNITUDE_NONE; | |||||
} | |||||
// Current value for slot # index | |||||
double value(unsigned char index) { | |||||
_error = SENSOR_ERROR_OK; | |||||
if (index == 0) return _hlw8012->getCurrent(); | |||||
if (index == 1) return _hlw8012->getVoltage(); | |||||
if (index == 2) return _hlw8012->getActivePower(); | |||||
if (index == 3) return _hlw8012->getReactivePower(); | |||||
if (index == 4) return _hlw8012->getApparentPower(); | |||||
if (index == 5) return _hlw8012->getPowerFactor(); | |||||
if (index == 6) return _hlw8012->getEnergy(); | |||||
_error = SENSOR_ERROR_OUT_OF_RANGE; | |||||
return 0; | |||||
} | |||||
// Post-read hook (usually to reset things) | |||||
void post() { | |||||
// Toggle between current and voltage monitoring | |||||
#if (HLW8012_USE_INTERRUPTS == 0) | |||||
_hlw8012->toggleMode(); | |||||
#endif // (HLW8012_USE_INTERRUPTS == 0) | |||||
} | |||||
// Handle interrupt calls | |||||
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) { | |||||
if (gpio == _interrupt_cf) _hlw8012->cf_interrupt(); | |||||
if (gpio == _interrupt_cf1) _hlw8012->cf1_interrupt(); | |||||
} | |||||
// Interrupt attach callback | |||||
void attached(unsigned char gpio) { | |||||
BaseSensor::attached(gpio); | |||||
if (_cf == gpio) _interrupt_cf = gpio; | |||||
if (_cf1 == gpio) _interrupt_cf1 = gpio; | |||||
} | |||||
// Interrupt detach callback | |||||
void detached(unsigned char gpio) { | |||||
BaseSensor::detached(gpio); | |||||
if (_interrupt_cf == gpio) _interrupt_cf = GPIO_NONE; | |||||
if (_interrupt_cf1 == gpio) _interrupt_cf1 = GPIO_NONE; | |||||
} | |||||
protected: | |||||
// --------------------------------------------------------------------- | |||||
// Protected | |||||
// --------------------------------------------------------------------- | |||||
void _enable(bool value) { | |||||
if (value) { | |||||
if (_interrupt_cf != _cf) { | |||||
detach(_interrupt_cf); | |||||
attach(this, _cf, CHANGE); | |||||
} | |||||
if (_interrupt_cf1 != _cf1) { | |||||
detach(_interrupt_cf1); | |||||
attach(this, _cf1, CHANGE); | |||||
} | |||||
} else { | |||||
detach(_interrupt_cf); | |||||
detach(_interrupt_cf1); | |||||
} | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
unsigned char _sel; | |||||
unsigned char _cf; | |||||
unsigned char _cf1; | |||||
bool _sel_current; | |||||
HLW8012 * _hlw8012 = NULL; | |||||
WiFiEventHandler _onconnect_handler; | |||||
WiFiEventHandler _ondisconnect_handler; | |||||
unsigned char _interrupt_cf = GPIO_NONE; | |||||
unsigned char _interrupt_cf1 = GPIO_NONE; | |||||
}; |