diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 5bb1d126..68fea5dc 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -44,3 +44,5 @@ //#define ENABLE_NOFUSS 1 //#define ENABLE_DOMOTICZ 0 //#define ENABLE_ANALOG 1 +//#define ENABLE_INFLUXDB 0 +//#define ENABLE_I2C 1 diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index c38fcc6d..d1131fad 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -17,3 +17,4 @@ template String getSetting(const String& key, unsigned int index, T template void domoticzSend(const char * key, T value); template void domoticzSend(const char * key, T nvalue, const char * svalue); template bool influxDBSend(const char * topic, T payload); +char * ltrim(char * s); diff --git a/code/espurna/emon.ino b/code/espurna/emon.ino index e5ca8b84..94f042d1 100644 --- a/code/espurna/emon.ino +++ b/code/espurna/emon.ino @@ -25,8 +25,10 @@ Copyright (C) 2016-2017 by Xose Pérez #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 diff --git a/code/espurna/hlw8012.ino b/code/espurna/hlw8012.ino index 2cf3be07..0a23926f 100644 --- a/code/espurna/hlw8012.ino +++ b/code/espurna/hlw8012.ino @@ -12,10 +12,13 @@ Copyright (C) 2016-2017 by Xose Pérez #include #include #include -#include 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 diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino new file mode 100644 index 00000000..6e41bc12 --- /dev/null +++ b/code/espurna/utils.ino @@ -0,0 +1,13 @@ +/* + +UTILS MODULE + +Copyright (C) 2017 by Xose Pérez + +*/ + +char * ltrim(char * s) { + char *p = s; + while ((unsigned char) *p == ' ') ++p; + return p; +} diff --git a/code/espurna/web.ino b/code/espurna/web.ino index eb538741..80d49506 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -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); diff --git a/code/html/custom.js b/code/html/custom.js index 6a44350d..a28228eb 100644 --- a/code/html/custom.js +++ b/code/html/custom.js @@ -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; } diff --git a/code/html/index.html b/code/html/index.html index eaf2a555..7502923d 100644 --- a/code/html/index.html +++ b/code/html/index.html @@ -179,12 +179,22 @@ +
+ + +
+ +
+ + +
+
-
+