Browse Source

API returns processed values for HLW and EMON. 404 means no data yet. Some clean up

fastled
Xose Pérez 6 years ago
parent
commit
41189a65b4
8 changed files with 192 additions and 119 deletions
  1. +2
    -0
      code/espurna/config/arduino.h
  2. +1
    -0
      code/espurna/config/prototypes.h
  3. +71
    -60
      code/espurna/emon.ino
  4. +67
    -47
      code/espurna/hlw8012.ino
  5. +13
    -0
      code/espurna/utils.ino
  6. +21
    -9
      code/espurna/web.ino
  7. +6
    -2
      code/html/custom.js
  8. +11
    -1
      code/html/index.html

+ 2
- 0
code/espurna/config/arduino.h View File

@ -44,3 +44,5 @@
//#define ENABLE_NOFUSS 1
//#define ENABLE_DOMOTICZ 0
//#define ENABLE_ANALOG 1
//#define ENABLE_INFLUXDB 0
//#define ENABLE_I2C 1

+ 1
- 0
code/espurna/config/prototypes.h View File

@ -17,3 +17,4 @@ template<typename T> String getSetting(const String& key, unsigned int index, T
template<typename T> void domoticzSend(const char * key, T value);
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue);
template<typename T> bool influxDBSend(const char * topic, T payload);
char * ltrim(char * s);

+ 71
- 60
code/espurna/emon.ino View File

@ -25,8 +25,10 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#define ADC121_REG_CONVH 0x07
EmonLiteESP emon;
double _current = 0;
unsigned int _power = 0;
bool _emonReady = false;
double _emonCurrent = 0;
unsigned int _emonPower = 0;
unsigned int _emonVoltage = 0;
// -----------------------------------------------------------------------------
// Provider
@ -54,28 +56,40 @@ unsigned int currentCallback() {
}
// -----------------------------------------------------------------------------
// EMON
// HAL
// -----------------------------------------------------------------------------
void setCurrentRatio(float value) {
emon.setCurrentRatio(value);
}
unsigned int getPower() {
return _power;
unsigned int getApparentPower() {
return int(getCurrent() * getVoltage());
}
double getCurrent() {
return _current;
double current = emon.getCurrent(EMON_SAMPLES);
current -= EMON_CURRENT_OFFSET;
if (current < 0) current = 0;
return current;
}
unsigned int getVoltage() {
return getSetting("emonVoltage", EMON_MAINS_VOLTAGE).toInt();
}
// -----------------------------------------------------------------------------
void powerMonitorSetup() {
// backwards compatibility
String tmp;
tmp = getSetting("pwMainsVoltage", EMON_MAINS_VOLTAGE);
setSetting("emonMains", tmp);
setSetting("emonVoltage", tmp);
delSetting("pwMainsVoltage");
tmp = getSetting("emonMains", EMON_MAINS_VOLTAGE);
setSetting("emonVoltage", tmp);
delSetting("emonMains");
tmp = getSetting("pwCurrentRatio", EMON_CURRENT_RATIO);
setSetting("emonRatio", tmp);
delSetting("pwCurrentRatio");
@ -98,7 +112,19 @@ void powerMonitorSetup() {
#endif
apiRegister(EMON_APOWER_TOPIC, EMON_APOWER_TOPIC, [](char * buffer, size_t len) {
snprintf(buffer, len, "%d", _power);
if (_emonReady) {
snprintf(buffer, len, "%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;
}
});
}
@ -119,80 +145,65 @@ void powerMonitorLoop() {
if (millis() > next_measurement) {
// Safety check: do not read current if relay is OFF
// You could be monitoring another line with the current clamp...
//if (!relayStatus(0)) {
// _current = 0;
//} else {
_current = emon.getCurrent(EMON_SAMPLES);
_current -= EMON_CURRENT_OFFSET;
if (_current < 0) _current = 0;
//}
if (measurements == 0) {
max = min = _current;
} else {
if (_current > max) max = _current;
if (_current < min) min = _current;
}
sum += _current;
++measurements;
int voltage = getVoltage();
float mainsVoltage = getSetting("emonMains", EMON_MAINS_VOLTAGE).toFloat();
{
char current[6];
dtostrf(_current, 5, 2, current);
DEBUG_MSG_P(PSTR("[ENERGY] Current: %sA\n"), current);
DEBUG_MSG_P(PSTR("[ENERGY] Power: %dW\n"), int(_current * mainsVoltage));
double current = getCurrent();
if (measurements == 0) {
max = min = current;
} else {
if (_emonCurrent > max) max = current;
if (_emonCurrent < min) min = current;
}
sum += current;
++measurements;
// Update websocket clients
char text[64];
sprintf_P(text, PSTR("{\"emonVisible\": 1, \"powApparentPower\": %d}"), int(_current * mainsVoltage));
wsSend(text);
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 (wsConnected()) {
char text[100];
sprintf_P(text, PSTR("{\"emonVisible\": 1, \"emonApparentPower\": %d, \"emonCurrent\": %s}"), int(current * voltage), String(current, 3).c_str());
wsSend(text);
}
}
// Send MQTT messages averaged every EMON_MEASUREMENTS
if (measurements == EMON_MEASUREMENTS) {
// Calculate average current (removing max and min values) and create C-string
double average = (sum - max - min) / (measurements - 2);
dtostrf(average, 5, 2, current);
char *c = current;
while ((unsigned char) *c == ' ') ++c;
// Calculate average apparent power from current and create C-string
_power = (int) (average * mainsVoltage);
char power[6];
snprintf(power, 6, "%d", _power);
// 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) and create C-string
double energy_inc = (double) _power * EMON_INTERVAL * EMON_MEASUREMENTS / 1000.0 / 3600.0;
char energy_buf[11];
dtostrf(energy_inc, 10, 3, energy_buf);
char *e = energy_buf;
while ((unsigned char) *e == ' ') ++e;
// 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(), power);
mqttSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), c);
mqttSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), e);
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 ENABLE_DOMOTICZ
{
char buffer[20];
snprintf(buffer, 20, "%s;%s", power, e);
snprintf(buffer, 20, "%d;%s", _emonPower, String(energy_delta, 3).c_str());
domoticzSend("dczPowIdx", 0, buffer);
snprintf(buffer, 20, "%s", e);
snprintf(buffer, 20, "%s", String(energy_delta, 3).c_str());
domoticzSend("dczEnergyIdx", 0, buffer);
snprintf(buffer, 20, "%s", c);
snprintf(buffer, 20, "%s", String(_emonCurrent, 3).c_str());
domoticzSend("dczCurrentIdx", 0, buffer);
}
#endif
#if ENABLE_INFLUXDB
influxDBSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), power);
//influxDBSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), c);
//influxDBSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), e);
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


+ 67
- 47
code/espurna/hlw8012.ino View File

@ -12,10 +12,13 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <HLW8012.h>
#include <Hash.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
HLW8012 hlw8012;
bool _hlw8012Enabled = false;
bool _hlwReady = false;
int _hlwPower = 0;
double _hlwCurrent = 0;
int _hlwVoltage = 0;
// -----------------------------------------------------------------------------
// POW
@ -92,6 +95,8 @@ void hlw8012Reset() {
hlw8012SaveCalibration();
}
// -----------------------------------------------------------------------------
// HAL
// -----------------------------------------------------------------------------
unsigned int getActivePower() {
@ -154,13 +159,25 @@ void hlw8012Setup() {
// API definitions
apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) {
snprintf(buffer, len, "%d", getActivePower());
if (_hlwReady) {
snprintf(buffer, len, "%d", _hlwPower);
} else {
buffer = NULL;
}
});
apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) {
dtostrf(getCurrent(), len-1, 3, buffer);
if (_hlwReady) {
dtostrf(_hlwCurrent, len-1, 3, buffer);
} else {
buffer = NULL;
}
});
apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) {
snprintf(buffer, len, "%d", getVoltage());
if (_hlwReady) {
snprintf(buffer, len, "%d", _hlwVoltage);
} else {
buffer = NULL;
}
});
}
@ -202,9 +219,6 @@ void hlw8012Loop() {
unsigned int power = getActivePower();
unsigned int voltage = getVoltage();
double current = getCurrent();
unsigned int apparent = getApparentPower();
double factor = getPowerFactor();
unsigned int reactive = getReactivePower();
if (power > 0) {
power_spike = (power_previous == 0);
@ -230,43 +244,49 @@ void hlw8012Loop() {
}
voltage_previous = voltage;
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (wsConnected()) {
unsigned int apparent = getApparentPower();
double factor = getPowerFactor();
unsigned int reactive = getReactivePower();
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);
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
String output;
root.printTo(output);
wsSend(output.c_str());
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());
}
if (--report_count == 0) {
power = power_sum / HLW8012_REPORT_EVERY;
current = current_sum / HLW8012_REPORT_EVERY;
voltage = voltage_sum / HLW8012_REPORT_EVERY;
apparent = current * voltage;
reactive = (apparent > power) ? sqrt(apparent * apparent - power * power) : 0;
factor = (apparent > 0) ? (double) power / apparent : 1;
if (factor > 1) factor = 1;
// Update globals
_hlwPower = power_sum / HLW8012_REPORT_EVERY;
_hlwCurrent = current_sum / HLW8012_REPORT_EVERY;
_hlwVoltage = voltage_sum / HLW8012_REPORT_EVERY;
_hlwReady = true;
// Calculate energy increment (ppower times time) and create C-string
double energy_inc = (double) power * HLW8012_REPORT_EVERY * HLW8012_UPDATE_INTERVAL / 1000.0 / 3600.0;
char energy_buf[11];
dtostrf(energy_inc, 11, 3, energy_buf);
char *e = energy_buf;
while ((unsigned char) *e == ' ') ++e;
// 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(power).c_str());
mqttSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), e);
mqttSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(current, 3).c_str());
mqttSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(voltage).c_str());
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());
@ -275,25 +295,25 @@ void hlw8012Loop() {
#if ENABLE_DOMOTICZ
{
char buffer[20];
snprintf(buffer, 20, "%d;%s", power, e);
snprintf(buffer, 20, "%d;%s", _hlwPower, String(energy_delta, 3).c_str());
domoticzSend("dczPowIdx", 0, buffer);
snprintf(buffer, 20, "%s", e);
snprintf(buffer, 20, "%s", String(energy_delta, 3).c_str());
domoticzSend("dczEnergyIdx", 0, buffer);
snprintf(buffer, 20, "%d", voltage);
snprintf(buffer, 20, "%d", _hlwVoltage);
domoticzSend("dczVoltIdx", 0, buffer);
snprintf(buffer, 20, "%s", String(current).c_str());
snprintf(buffer, 20, "%s", String(_hlwCurrent).c_str());
domoticzSend("dczCurrentIdx", 0, buffer);
}
#endif
#if ENABLE_INFLUXDB
influxDBSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(power).c_str());
//influxDBSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), e);
//influxDBSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(current, 3).c_str());
//influxDBSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(voltage).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());
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


+ 13
- 0
code/espurna/utils.ino View File

@ -0,0 +1,13 @@
/*
UTILS MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
char * ltrim(char * s) {
char *p = s;
while ((unsigned char) *p == ' ') ++p;
return p;
}

+ 21
- 9
code/espurna/web.ino View File

@ -43,6 +43,10 @@ char _last_modified[50];
// WEBSOCKETS
// -----------------------------------------------------------------------------
bool wsConnected() {
return (ws.count() > 0);
}
bool wsSend(const char * payload) {
if (ws.count() > 0) {
ws.textAll(payload);
@ -540,8 +544,9 @@ void _wsStart(uint32_t client_id) {
#if ENABLE_EMON
root["emonVisible"] = 1;
root["emonPower"] = getPower();
root["emonMains"] = getSetting("emonMains", EMON_MAINS_VOLTAGE);
root["emonApparentPower"] = getApparentPower();
root["emonCurrent"] = getCurrent();
root["emonVoltage"] = getVoltage();
root["emonRatio"] = getSetting("emonRatio", EMON_CURRENT_RATIO);
#endif
@ -694,13 +699,13 @@ bool _asJson(AsyncWebServerRequest *request) {
ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
return [apiID](AsyncWebServerRequest *request) {
webLogRequest(request);
webLogRequest(request);
if (!_authAPI(request)) return;
bool asJson = _asJson(request);
web_api_t api = _apis[apiID];
// Check if its a PUT
if (api.putFn != NULL) {
if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
@ -708,14 +713,21 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
}
}
// Get response from callback
char value[10];
(api.getFn)(value, 10);
char *p = ltrim(value);
// jump over leading spaces
char *p = value;
while ((unsigned char) *p == ' ') ++p;
// The response will be a 404 NOT FOUND if the resource is not available
if (*value == NULL) {
DEBUG_MSG_P(PSTR("[API] Sending 404 response\n"));
request->send(404);
return;
}
DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), p);
if (asJson) {
// Format response according to the Accept header
if (_asJson(request)) {
char buffer[64];
sprintf_P(buffer, PSTR("{ \"%s\": %s }"), api.key, p);
request->send(200, "application/json", buffer);


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

@ -444,7 +444,9 @@ function processData(data) {
} else if (element.attr('type') == 'radio') {
element.val([data[key]]);
} else {
element.val(data[key]);
var pre = element.attr("pre") || "";
var post = element.attr("post") || "";
element.val(pre + data[key] + post);
}
return;
}
@ -452,7 +454,9 @@ function processData(data) {
// Look for SPANs
var element = $("span[name=" + key + "]");
if (element.length > 0) {
element.html(data[key]);
var pre = element.attr("pre") || "";
var post = element.attr("post") || "";
element.html(pre + data[key] + post);
return;
}


+ 11
- 1
code/html/index.html View File

@ -179,12 +179,22 @@
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="dhtHum" readonly />
</div>
<div class="pure-g module module-emon">
<label class="pure-u-1 pure-u-sm-1-4" for="emonApparentPower">Apparent Power (VA)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="emonApparentPower" readonly />
</div>
<div class="pure-g module module-emon">
<label class="pure-u-1 pure-u-sm-1-4" for="emonCurrent">Current (A)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="emonCurrent" readonly />
</div>
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powActivePower">Active Power (W)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powActivePower" readonly />
</div>
<div class="pure-g module module-pow module-emon">
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powApparentPower">Apparent Power (VA)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powApparentPower" readonly />
</div>


Loading…
Cancel
Save