Browse Source

Power calibration

fastled
Xose Pérez 6 years ago
parent
commit
e02c249c31
14 changed files with 3593 additions and 4377 deletions
  1. +2
    -2
      code/espurna/config/general.h
  2. BIN
      code/espurna/data/index.html.gz
  3. +0
    -217
      code/espurna/emon.ino
  4. +0
    -348
      code/espurna/hlw8012.ino
  5. +17
    -1
      code/espurna/power.ino
  6. +18
    -0
      code/espurna/power_emon.ino
  7. +13
    -26
      code/espurna/power_hlw8012.ino
  8. +48
    -5
      code/espurna/power_v9261f.ino
  9. +1
    -1
      code/espurna/settings.ino
  10. +3440
    -3434
      code/espurna/static/index.html.gz.h
  11. +0
    -305
      code/espurna/v9261f.ino
  12. +22
    -13
      code/espurna/web.ino
  13. +2
    -2
      code/html/custom.js
  14. +30
    -23
      code/html/index.html

+ 2
- 2
code/espurna/config/general.h View File

@ -528,9 +528,9 @@ PROGMEM const char* const custom_reset_string[] = {
#define POWER_CURRENT_PRECISION 3
#define POWER_VOLTAGE 230
#define POWER_READ_INTERVAL 10000
#define POWER_READ_INTERVAL 6000
#define POWER_REPORT_INTERVAL 60000
#define POWER_REPORT_BUFFER 10
#define POWER_REPORT_BUFFER 12
#define POWER_ENERGY_FACTOR (POWER_REPORT_INTERVAL / 1000.0 / 3600.0)
#define POWER_CURRENT_DECIMALS 2
#define POWER_VOLTAGE_DECIMALS 0


BIN
code/espurna/data/index.html.gz View File


+ 0
- 217
code/espurna/emon.ino View File

@ -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
*/

+ 0
- 348
code/espurna/hlw8012.ino View File

@ -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
*/

+ 17
- 1
code/espurna/power.ino View File

@ -132,11 +132,19 @@ void _powerRead() {
root["pwrVoltage"] = roundTo(voltage, POWER_VOLTAGE_DECIMALS);
root["pwrApparent"] = roundTo(apparent, POWER_POWER_DECIMALS);
#if POWER_HAS_ACTIVE
root["pwrFullVisible"] = 1;
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
root["emonVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
root["hlwVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
root["v9261fVisible"] = 1;
#endif
String output;
root.printTo(output);
wsSend(output.c_str());
@ -258,6 +266,14 @@ void powerEnabled(bool enabled) {
_powerEnabledProvider();
}
void powerCalibrate(unsigned char magnitude, double value) {
_powerCalibrateProvider(magnitude, value);
}
void powerResetCalibration() {
_powerResetCalibrationProvider();
}
void powerConfigure() {
_powerConfigureProvider();
}


+ 18
- 0
code/espurna/power_emon.ino View File

@ -100,6 +100,24 @@ void _powerEnabledProvider() {
// Nothing to do
}
void _powerCalibrateProvider(unsigned char magnitude, double value) {
if (value <= 0) return;
if (magnitude == POWER_MAGNITUDE_ACTIVE) {
double power = _powerActivePower();
double ratio = getSetting("powerRatioC", EMON_CURRENT_RATIO).toFloat();
ratio = ratio * (value / power);
_emon.setCurrentRatio(ratio);
setSetting("powerRatioC", ratio);
saveSettings();
}
}
void _powerResetCalibrationProvider() {
delSetting("powerRatioC");
_powerConfigureProvider();
saveSettings();
}
void _powerConfigureProvider() {
_emon.setCurrentRatio(getSetting("powerRatioC", EMON_CURRENT_RATIO).toFloat());
_power_voltage = getSetting("powerVoltage", POWER_VOLTAGE).toFloat();


+ 13
- 26
code/espurna/power_hlw8012.ino View File

@ -47,32 +47,6 @@ void _hlwGetCalibration() {
saveSettings();
}
void _hlwResetCalibration() {
_hlw8012.resetMultipliers();
_hlwGetCalibration();
}
void _hlwExpectedPower(unsigned int power) {
if (power > 0) {
_hlw8012.expectedActivePower(power);
_hlwGetCalibration();
}
}
void _hlwExpectedCurrent(double current) {
if (current > 0) {
_hlw8012.expectedCurrent(current);
_hlwGetCalibration();
}
}
void _hlwExpectedVoltage(unsigned int voltage) {
if (voltage > 0) {
_hlw8012.expectedVoltage(voltage);
_hlwGetCalibration();
}
}
// -----------------------------------------------------------------------------
// POWER API
// -----------------------------------------------------------------------------
@ -111,6 +85,19 @@ void _powerEnabledProvider() {
}
}
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);
_hlwGetCalibration();
}
void _powerResetCalibrationProvider() {
_hlw8012.resetMultipliers();
_hlwGetCalibration();
}
void _powerConfigureProvider() {
_hlwSetCalibration();
_hlwGetCalibration();


+ 48
- 5
code/espurna/power_v9261f.ino View File

@ -19,6 +19,12 @@ 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];
// -----------------------------------------------------------------------------
@ -77,28 +83,28 @@ void _v9261fRead() {
(_v9261f_data[4] << 8) +
(_v9261f_data[5] << 16) +
(_v9261f_data[6] << 24)
) / V9261F_POWER_FACTOR;
) / _v9261f_ratioP;
_v9261f_reactive = (double) (
(_v9261f_data[7]) +
(_v9261f_data[8] << 8) +
(_v9261f_data[9] << 16) +
(_v9261f_data[10] << 24)
) / V9261F_RPOWER_FACTOR;
) / _v9261f_ratioR;
_v9261f_voltage = (double) (
(_v9261f_data[11]) +
(_v9261f_data[12] << 8) +
(_v9261f_data[13] << 16) +
(_v9261f_data[14] << 24)
) / V9261F_VOLTAGE_FACTOR;
) / _v9261f_ratioV;
_v9261f_current = (double) (
(_v9261f_data[15]) +
(_v9261f_data[16] << 8) +
(_v9261f_data[17] << 16) +
(_v9261f_data[18] << 24)
) / V9261F_CURRENT_FACTOR;
) / _v9261f_ratioC;
if (_v9261f_active < 0) _v9261f_active = 0;
if (_v9261f_reactive < 0) _v9261f_reactive = 0;
@ -172,7 +178,44 @@ void _powerEnabledProvider() {
}
void _powerConfigureProvider() {
// Nothing to do
_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() {


+ 1
- 1
code/espurna/settings.ino View File

@ -292,7 +292,7 @@ void settingsDump() {
void settingsLoop() {
if (_settings_save) {
DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
//DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
EEPROM.commit();
_settings_save = false;
}


+ 3440
- 3434
code/espurna/static/index.html.gz.h
File diff suppressed because it is too large
View File


+ 0
- 305
code/espurna/v9261f.ino View File

@ -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
*/

+ 22
- 13
code/espurna/web.ino View File

@ -229,32 +229,40 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
// Skip firmware filename
if (key.equals("filename")) continue;
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
#if POWER_PROVIDER != POWER_PROVIDER_NONE
if (key == "pwrExpectedP") {
powerCalibrate(POWER_MAGNITUDE_ACTIVE, value.toFloat());
changed = true;
continue;
}
if (key == "hlwExpectedPower") {
_hlwExpectedPower(value.toInt());
if (key == "pwrExpectedV") {
powerCalibrate(POWER_MAGNITUDE_VOLTAGE, value.toFloat());
changed = true;
continue;
}
if (key == "hlwExpectedVoltage") {
_hlwExpectedVoltage(value.toInt());
if (key == "pwrExpectedC") {
powerCalibrate(POWER_MAGNITUDE_CURRENT, value.toFloat());
changed = true;
continue;
}
if (key == "hlwExpectedCurrent") {
_hlwExpectedCurrent(value.toFloat());
if (key == "pwrExpectedF") {
powerCalibrate(POWER_MAGNITUDE_POWER_FACTOR, value.toFloat());
changed = true;
continue;
}
if (key == "hlwExpectedReset") {
if (key == "pwrResetCalibration") {
if (value.toInt() == 1) {
_hlwResetCalibration();
powerResetCalibration();
changed = true;
}
continue;
}
if (key.startsWith("hlw")) continue;
#endif
#if DOMOTICZ_SUPPORT
@ -596,18 +604,19 @@ void _wsStart(uint32_t client_id) {
root["pwrVoltage"] = getVoltage();
root["pwrApparent"] = getApparentPower();
#if POWER_HAS_ACTIVE
root["pwrFullVisible"] = 1;
root["pwrActive"] = getActivePower();
root["pwrReactive"] = getReactivePower();
root["pwrFactor"] = int(100 * getPowerFactor());
#endif
#if POWER_PROVIDER & POWER_PROVIDER_EMON
root["emonVisible"] = 1;
root["pwrRatioC"] = getSetting("pwrRatioC", EMON_CURRENT_RATIO);
#endif
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
root["hlwVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
root["v9261fVisible"] = 1;
#endif
#endif
#if NOFUSS_SUPPORT


+ 2
- 2
code/html/custom.js View File

@ -66,8 +66,8 @@ function doUpdate() {
websock.send(JSON.stringify({'config': data}));
$(".hlwExpected").val(0);
$("input[name='hlwExpectedReset']")
$(".pwrExpected").val(0);
$("input[name='pwrResetCalibration']")
.prop("checked", false)
.iphoneStyle("refresh");


+ 30
- 23
code/html/index.html View File

@ -111,8 +111,8 @@
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
</li>
<li class="pure-menu-item module module-hlw">
<a href="#" class="pure-menu-link" data="panel-hlw">HLW8012</a>
<li class="pure-menu-item module module-hlw module-emon module-v9261f">
<a href="#" class="pure-menu-link" data="panel-power">POWER</a>
</li>
<li class="pure-menu-item module module-rfb">
@ -190,32 +190,32 @@
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="dhtHum" readonly />
</div>
<div class="pure-g module module-pwr">
<div class="pure-g module module-emon module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrCurrent">Current</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrCurrent" post=" A" readonly />
</div>
<div class="pure-g module module-pwr">
<div class="pure-g module module-emon module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrVoltage">Voltage</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrVoltage" post=" V" readonly />
</div>
<div class="pure-g module module-pwrFull">
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrActive">Active Power</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrActive" post=" W" readonly />
</div>
<div class="pure-g module module-pwr">
<div class="pure-g module module-emon module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrApparent">Apparent Power</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrApparent" post=" VA" readonly />
</div>
<div class="pure-g module module-pwrFull">
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrReactive">Reactive Power</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrReactive" post=" VAR" readonly />
</div>
<div class="pure-g module module-pwrFull">
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrFactor">Power Factor</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrFactor" post="%" readonly />
</div>
@ -771,10 +771,10 @@
</div>
<div class="panel" id="panel-hlw">
<div class="panel" id="panel-power">
<div class="header">
<h1>HLW8012 CALIBRATION</h1>
<h1>POWER CALIBRATION</h1>
<h2>
Calibrate your power monitor device. Use a pure resistive load and introduce the expected values for active power, current and voltage. Use the nominal values or a multimeter to get the proper numbers. Set any field to 0 to leave the calibration value untouched.
</h2>
@ -784,32 +784,39 @@
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="hlwExpectedPower">AC RMS Active Power</label>
<input class="pure-u-1 pure-u-md-3-4 hlwExpected" name="hlwExpectedPower" type="text" size="8" tabindex="51" placeholder="0" />
<div class="pure-g module module-hlw module-emon module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedP">AC RMS Active Power</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedP" type="text" size="8" tabindex="51" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In Watts (W). If you are using a pure resistive load like a bulb this will be writen on it, otherwise use a socket multimeter to get this value.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="hlwExpectedVoltage">AC RMS Voltage</label>
<input class="pure-u-1 pure-u-md-3-4 hlwExpected" name="hlwExpectedVoltage" type="text" size="8" tabindex="52" placeholder="0" />
<div class="pure-g module module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedF">AC Power Factor</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedF" type="text" size="8" tabindex="51" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In percentage (%). You will need to use a calibrated multimeter to get this value and a mixed resistive and reactive load.</div>
</div>
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedV">AC RMS Voltage</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedV" type="text" size="8" tabindex="52" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In Volts (V). Enter your the nominal AC voltage for your household or facility, or use multimeter to get this value.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="hlwExpectedCurrent">AC RMS Current</label>
<input class="pure-u-1 pure-u-md-3-4 hlwExpected" name="hlwExpectedCurrent" type="text" size="8" tabindex="55" placeholder="0" />
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedC">AC RMS Current</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedC" type="text" size="8" tabindex="55" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In Ampers (A). If you are using a pure resistive load like a bulb this will the ratio between the two previous values, i.e. power / voltage. You can also use a current clamp around one fo the power wires to get this value.</div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-sm-1-4"><label for="hlwExpectedReset">Reset calibration</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="hlwExpectedReset" /></div>
<div class="pure-g module module-hlw module-emon module-v9261f">
<div class="pure-u-1 pure-u-sm-1-4"><label for="pwrResetCalibration">Reset calibration</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="pwrResetCalibration" /></div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Move this switch to ON and press "Update" to revert to factory values.</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Move this switch to ON and press "Update" to revert to factory calibration values.</div>
</div>
</fieldset>


Loading…
Cancel
Save