@ -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> | |||||
#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 | |||||
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,348 +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 | |||||
*/ |
@ -1,305 +0,0 @@ | |||||
/* | |||||
V9261F MODULE | |||||
Support for V9261D-based power monitors | |||||
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
/* | |||||
#if V9261F_SUPPORT | |||||
#include <SoftwareSerial.h> | |||||
#include <ArduinoJson.h> | |||||
SoftwareSerial * _v9261f_uart; | |||||
bool _v9261f_enabled = false; | |||||
bool _v9261f_ready = false; | |||||
bool _v9261f_newdata = false; | |||||
int _v9261f_power = 0; | |||||
int _v9261f_rpower = 0; | |||||
int _v9261f_voltage = 0; | |||||
double _v9261f_current = 0; | |||||
unsigned char _v9261f_data[24]; | |||||
// ----------------------------------------------------------------------------- | |||||
// PRIVATE | |||||
// ----------------------------------------------------------------------------- | |||||
void v9261fRead() { | |||||
static unsigned char state = 0; | |||||
static unsigned long last = 0; | |||||
static bool found = false; | |||||
static unsigned char index = 0; | |||||
if (state == 0) { | |||||
while (_v9261f_uart->available()) { | |||||
_v9261f_uart->flush(); | |||||
found = true; | |||||
last = millis(); | |||||
} | |||||
if (found && (millis() - last > V9261F_SYNC_INTERVAL)) { | |||||
_v9261f_uart->flush(); | |||||
index = 0; | |||||
state = 1; | |||||
} | |||||
} else if (state == 1) { | |||||
while (_v9261f_uart->available()) { | |||||
_v9261f_uart->read(); | |||||
if (index++ >= 7) { | |||||
_v9261f_uart->flush(); | |||||
index = 0; | |||||
state = 2; | |||||
} | |||||
} | |||||
} else if (state == 2) { | |||||
while (_v9261f_uart->available()) { | |||||
_v9261f_data[index] = _v9261f_uart->read(); | |||||
if (index++ >= 19) { | |||||
_v9261f_uart->flush(); | |||||
last = millis(); | |||||
state = 3; | |||||
} | |||||
} | |||||
} else if (state == 3) { | |||||
if (checksumOK()) { | |||||
_v9261f_power = (double) ( | |||||
(_v9261f_data[3]) + | |||||
(_v9261f_data[4] << 8) + | |||||
(_v9261f_data[5] << 16) + | |||||
(_v9261f_data[6] << 24) | |||||
) / V9261F_POWER_FACTOR; | |||||
_v9261f_rpower = (double) ( | |||||
(_v9261f_data[7]) + | |||||
(_v9261f_data[8] << 8) + | |||||
(_v9261f_data[9] << 16) + | |||||
(_v9261f_data[10] << 24) | |||||
) / V9261F_RPOWER_FACTOR; | |||||
_v9261f_voltage = (double) ( | |||||
(_v9261f_data[11]) + | |||||
(_v9261f_data[12] << 8) + | |||||
(_v9261f_data[13] << 16) + | |||||
(_v9261f_data[14] << 24) | |||||
) / V9261F_VOLTAGE_FACTOR; | |||||
_v9261f_current = (double) ( | |||||
(_v9261f_data[15]) + | |||||
(_v9261f_data[16] << 8) + | |||||
(_v9261f_data[17] << 16) + | |||||
(_v9261f_data[18] << 24) | |||||
) / V9261F_CURRENT_FACTOR; | |||||
_v9261f_newdata = true; | |||||
} | |||||
last = millis(); | |||||
index = 0; | |||||
state = 4; | |||||
} else if (state == 4) { | |||||
while (_v9261f_uart->available()) { | |||||
_v9261f_uart->flush(); | |||||
last = millis(); | |||||
} | |||||
if (millis() - last > V9261F_SYNC_INTERVAL) { | |||||
state = 1; | |||||
} | |||||
} | |||||
} | |||||
boolean checksumOK() { | |||||
unsigned char checksum = 0; | |||||
for (unsigned char i = 0; i < 19; i++) { | |||||
checksum = checksum + _v9261f_data[i]; | |||||
} | |||||
checksum = ~checksum + 0x33; | |||||
return checksum == _v9261f_data[19]; | |||||
} | |||||
// ----------------------------------------------------------------------------- | |||||
// HAL | |||||
// ----------------------------------------------------------------------------- | |||||
unsigned int getActivePower() { | |||||
return _v9261f_power; | |||||
} | |||||
unsigned int getReactivePower() { | |||||
return _v9261f_rpower; | |||||
} | |||||
unsigned int getApparentPower() { | |||||
return sqrt(_v9261f_rpower * _v9261f_rpower + _v9261f_power * _v9261f_power); | |||||
} | |||||
unsigned int getVoltage() { | |||||
return _v9261f_voltage; | |||||
} | |||||
double getCurrent() { | |||||
return _v9261f_current; | |||||
} | |||||
double getPowerFactor() { | |||||
unsigned int apparent = getApparentPower(); | |||||
if (apparent > 0) return getActivePower() / getApparentPower(); | |||||
return 1; | |||||
} | |||||
// ----------------------------------------------------------------------------- | |||||
void v9261fSetup() { | |||||
_v9261f_uart = new SoftwareSerial(V9261F_PIN, SW_SERIAL_UNUSED_PIN, V9261F_PIN_INVERSE, 256); | |||||
_v9261f_uart->begin(V9261F_BAUDRATE); | |||||
// API definitions | |||||
#if WEB_SUPPORT | |||||
apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) { | |||||
snprintf_P(buffer, len, PSTR("%d"), _v9261f_power); | |||||
}); | |||||
apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) { | |||||
dtostrf(_v9261f_current, len-1, 3, buffer); | |||||
}); | |||||
apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) { | |||||
snprintf_P(buffer, len, PSTR("%d"), _v9261f_voltage); | |||||
}); | |||||
#endif // WEB_SUPPORT | |||||
} | |||||
void v9261fLoop() { | |||||
static int sum_power = 0; | |||||
static int sum_rpower = 0; | |||||
static int sum_voltage = 0; | |||||
static double sum_current = 0; | |||||
static int count = 0; | |||||
// Sniff data in the UART interface | |||||
v9261fRead(); | |||||
// Do we have new data? | |||||
if (_v9261f_newdata) { | |||||
_v9261f_newdata = false; | |||||
sum_power += getActivePower(); | |||||
sum_rpower += getReactivePower(); | |||||
sum_voltage += getVoltage(); | |||||
sum_current += getCurrent(); | |||||
count++; | |||||
#if WEB_SUPPORT | |||||
{ | |||||
DynamicJsonBuffer jsonBuffer; | |||||
JsonObject& root = jsonBuffer.createObject(); | |||||
char buf_current[10]; | |||||
dtostrf(getCurrent(), 6, 3, buf_current); | |||||
root["powVisible"] = 1; | |||||
root["powActivePower"] = getActivePower(); | |||||
root["powCurrent"] = String(ltrim(buf_current)); | |||||
root["powVoltage"] = getVoltage(); | |||||
root["powApparentPower"] = getApparentPower(); | |||||
root["powReactivePower"] = getReactivePower(); | |||||
root["powPowerFactor"] = 100 * getPowerFactor(); | |||||
String output; | |||||
root.printTo(output); | |||||
wsSend(output.c_str()); | |||||
} | |||||
#endif | |||||
} | |||||
// Do we have to report? | |||||
static unsigned long last = 0; | |||||
if ((count == 0) || (millis() - last < V9261F_REPORT_INTERVAL)) return; | |||||
last = millis(); | |||||
{ | |||||
unsigned int power = sum_power / count; | |||||
unsigned int reactive = sum_rpower / count; | |||||
unsigned int voltage = sum_voltage / count; | |||||
double current = sum_current / count; | |||||
char buf_current[10]; | |||||
dtostrf(current, 6, 3, buf_current); | |||||
unsigned int apparent = sqrt(power * power + reactive * reactive); | |||||
double energy_delta = (double) power * V9261F_REPORT_INTERVAL / 1000.0 / 3600.0; | |||||
char buf_energy[10]; | |||||
dtostrf(energy_delta, 6, 3, buf_energy); | |||||
unsigned int factor = 100 * ((double) power / apparent); | |||||
// Report values to MQTT broker | |||||
mqttSend(HLW8012_POWER_TOPIC, String(power).c_str()); | |||||
mqttSend(HLW8012_CURRENT_TOPIC, buf_current); | |||||
mqttSend(HLW8012_VOLTAGE_TOPIC, String(voltage).c_str()); | |||||
mqttSend(HLW8012_ENERGY_TOPIC, buf_energy); | |||||
mqttSend(HLW8012_APOWER_TOPIC, String(apparent).c_str()); | |||||
mqttSend(HLW8012_RPOWER_TOPIC, String(reactive).c_str()); | |||||
mqttSend(HLW8012_PFACTOR_TOPIC, String(factor).c_str()); | |||||
// Report values to Domoticz | |||||
#if DOMOTICZ_SUPPORT | |||||
{ | |||||
char buffer[20]; | |||||
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, buf_energy); | |||||
domoticzSend("dczPowIdx", 0, buffer); | |||||
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), buf_energy); | |||||
domoticzSend("dczEnergyIdx", 0, buffer); | |||||
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), voltage); | |||||
domoticzSend("dczVoltIdx", 0, buffer); | |||||
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), buf_current); | |||||
domoticzSend("dczCurrentIdx", 0, buffer); | |||||
} | |||||
#endif | |||||
#if INFLUXDB_SUPPORT | |||||
{ | |||||
influxDBSend(HLW8012_POWER_TOPIC, String(power).c_str()); | |||||
influxDBSend(HLW8012_CURRENT_TOPIC, buf_current); | |||||
influxDBSend(HLW8012_VOLTAGE_TOPIC, String(voltage).c_str()); | |||||
influxDBSend(HLW8012_ENERGY_TOPIC, buf_energy); | |||||
influxDBSend(HLW8012_APOWER_TOPIC, String(apparent).c_str()); | |||||
influxDBSend(HLW8012_RPOWER_TOPIC, String(reactive).c_str()); | |||||
influxDBSend(HLW8012_PFACTOR_TOPIC, String(factor).c_str()); | |||||
} | |||||
#endif | |||||
// Reset counters | |||||
sum_power = sum_rpower = sum_voltage = sum_current = count = 0; | |||||
} | |||||
} | |||||
#endif | |||||
*/ |