From 33070af111c87b4828d7f98960446527eb602bf4 Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Sun, 17 Jan 2021 20:11:04 +0100 Subject: [PATCH] Refactored MQTT payload handling into separate classes --- platformio.ini | 2 +- src/AmsConfiguration.cpp | 2 + src/AmsToMqttBridge.ino | 449 +++---------------------------- src/HanToJson.cpp | 47 ---- src/HanToJson.h | 16 -- src/hexutils.cpp | 23 ++ src/hexutils.h | 11 + src/mqtt/AmsMqttHandler.h | 26 ++ src/mqtt/DomoticzMqttHandler.cpp | 89 ++++++ src/mqtt/DomoticzMqttHandler.h | 21 ++ src/mqtt/JsonMqttHandler.cpp | 227 ++++++++++++++++ src/mqtt/JsonMqttHandler.h | 24 ++ src/mqtt/RawMqttHandler.cpp | 200 ++++++++++++++ src/mqtt/RawMqttHandler.h | 21 ++ src/web/AmsWebServer.cpp | 27 +- src/web/AmsWebServer.h | 4 +- web/application.js | 14 +- web/domoticz.json | 6 + web/json1.json | 12 + web/json2.json | 24 ++ web/json3.json | 29 ++ web/jsonprices.json | 23 ++ web/jsonsys.json | 8 + 23 files changed, 803 insertions(+), 502 deletions(-) delete mode 100644 src/HanToJson.cpp delete mode 100644 src/HanToJson.h create mode 100644 src/hexutils.cpp create mode 100644 src/hexutils.h create mode 100644 src/mqtt/AmsMqttHandler.h create mode 100644 src/mqtt/DomoticzMqttHandler.cpp create mode 100644 src/mqtt/DomoticzMqttHandler.h create mode 100644 src/mqtt/JsonMqttHandler.cpp create mode 100644 src/mqtt/JsonMqttHandler.h create mode 100644 src/mqtt/RawMqttHandler.cpp create mode 100644 src/mqtt/RawMqttHandler.h create mode 100644 web/domoticz.json create mode 100644 web/json1.json create mode 100644 web/json2.json create mode 100644 web/json3.json create mode 100644 web/jsonprices.json create mode 100644 web/jsonsys.json diff --git a/platformio.ini b/platformio.ini index fc4eccb5..b727c812 100755 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ extra_configs = platformio-user.ini [common] framework = arduino -lib_deps = file://lib/HanReader, file://lib/Timezone, ArduinoJson@6.14.1, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, RemoteDebug@3.0.5, Time@1.6 +lib_deps = file://lib/HanReader, file://lib/Timezone, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, RemoteDebug@3.0.5, Time@1.6 [env:esp8266] platform = espressif8266@2.5.1 diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index 423987d7..78055591 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -105,6 +105,7 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) { mqttChanged |= strcmp(config.subscribeTopic, existing.subscribeTopic) != 0; mqttChanged |= strcmp(config.username, existing.username) != 0; mqttChanged |= strcmp(config.password, existing.password) != 0; + mqttChanged |= config.payloadFormat != existing.payloadFormat; mqttChanged |= config.ssl != existing.ssl; } else { mqttChanged = true; @@ -254,6 +255,7 @@ bool AmsConfiguration::setDomoticzConfig(DomoticzConfig& config) { } else { domoChanged = true; } + mqttChanged = domoChanged; EEPROM.begin(EEPROM_SIZE); EEPROM.put(CONFIG_DOMOTICZ_START, config); bool ret = EEPROM.commit(); diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 7e02ab83..488f4ca3 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -19,7 +19,6 @@ #include "AmsToMqttBridge.h" #include "AmsStorage.h" #define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e9 -#include #include #include #include @@ -34,7 +33,11 @@ ADC_MODE(ADC_VCC); #include "web/AmsWebServer.h" #include "AmsConfiguration.h" #include "HanReader.h" -#include "HanToJson.h" + +#include "mqtt/AmsMqttHandler.h" +#include "mqtt/JsonMqttHandler.h" +#include "mqtt/RawMqttHandler.h" +#include "mqtt/DomoticzMqttHandler.h" #include "Aidon.h" #include "Kaifa.h" @@ -61,6 +64,7 @@ Timezone* tz; AmsWebServer ws(&Debug, &hw, &eapi); MQTTClient mqtt(512); +AmsMqttHandler* mqttHandler = NULL; HanReader hanReader; @@ -68,10 +72,7 @@ Stream *hanSerial; GpioConfig gpioConfig; MeterConfig meterConfig; -DomoticzConfig* domoConfig = NULL; -float energy = 0.0; bool mqttEnabled = false; -String clientId = "ams-reader"; uint8_t payloadFormat = 0; String topic = "ams"; AmsData meterState; @@ -290,18 +291,6 @@ unsigned long lastSuccessfulRead = 0; unsigned long lastErrorBlink = 0; int lastError = 0; -String toHex(uint8_t* in) { - String hex; - for(int i = 0; i < sizeof(in)*2; i++) { - if(in[i] < 0x10) { - hex += '0'; - } - hex += String(in[i], HEX); - } - hex.toUpperCase(); - return hex; -} - void loop() { Debug.handle(); unsigned long now = millis(); @@ -333,38 +322,8 @@ void loop() { hw.updateTemperatures(); lastTemperatureRead = now; - uint8_t c = hw.getTempSensorCount(); - - if(WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt.connected() && !topic.isEmpty()) { - if(payloadFormat == 1 || payloadFormat == 2) { - for(int i = 0; i < c; i++) { - TempSensorData* data = hw.getTempSensorData(i); - if(data->lastValidRead > -85) { - if(data->changed || payloadFormat == 2) { - mqtt.publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2)); - data->changed = false; - } - } - } - } else if(payloadFormat == 0) { - StaticJsonDocument<512> json; - JsonObject temps = json.createNestedObject("temperatures"); - for(int i = 0; i < c; i++) { - TempSensorData* data = hw.getTempSensorData(i); - if(data->lastValidRead > -85) { - TempSensorConfig* conf = config.getTempSensorConfig(data->address); - JsonObject obj = temps.createNestedObject(toHex(data->address)); - if(conf != NULL) { - obj["name"] = conf->name; - } - obj["value"] = serialized(String(data->lastValidRead, 2)); - } - data->changed = false; - } - String msg; - serializeJson(json, msg); - mqtt.publish(topic, msg.c_str()); - } + if(mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt.connected() && !topic.isEmpty()) { + mqttHandler->publishTemperatures(&config, &hw); } debugD("Used %d ms to update temperature", millis()-start); } @@ -412,6 +371,7 @@ void loop() { MqttConfig mqttConfig; if(config.getMqttConfig(mqttConfig)) { mqttEnabled = strlen(mqttConfig.host) > 0; + ws.setMqttEnabled(mqttEnabled); } } if(config.isNtpChanged()) { @@ -443,9 +403,6 @@ void loop() { if(!mqtt.connected() || config.isMqttChanged()) { MQTT_connect(); } - if(payloadFormat == 1) { - sendSystemStatusToMqtt(); - } } else if(mqtt.connected()) { mqtt.disconnect(); } @@ -470,8 +427,8 @@ void loop() { delay(1); readHanPort(); if(WiFi.status() == WL_CONNECTED) { - if(eapi.loop()) { - sendPricesToMqtt(); + if(eapi.loop() && mqttHandler != NULL) { + mqttHandler->publishPrices(&eapi); } } ws.loop(); @@ -620,150 +577,6 @@ void mqttMessageReceived(String &topic, String &payload) // Ideas could be to query for values or to initiate OTA firmware update } -void sendPricesToMqtt() { - if(!mqttEnabled || topic.isEmpty() || !mqtt.connected()) - return; - if(strcmp(eapi.getToken(), "") != 0) - return; - - time_t now = time(nullptr); - - float min1hr, min3hr, min6hr; - uint8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1; - float min = INT16_MAX, max = INT16_MIN; - float values[24] = {0}; - for(uint8_t i = 0; i < 24; i++) { - float val = eapi.getValueForHour(now, i); - values[i] = val; - - if(val == ENTSOE_NO_VALUE) break; - - if(val < min) min = val; - if(val > max) max = val; - - if(min1hrIdx == -1 || min1hr > val) { - min1hr = val; - min1hrIdx = i; - } - - if(i >= 2) { - i -= 2; - float val1 = values[i++]; - float val2 = values[i++]; - float val3 = val; - if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue; - float val3hr = val1+val2+val3; - if(min3hrIdx == -1 || min3hr > val3hr) { - min3hr = val3hr; - min3hrIdx = i-2; - } - } - - if(i >= 5) { - i -= 5; - float val1 = values[i++]; - float val2 = values[i++]; - float val3 = values[i++]; - float val4 = values[i++]; - float val5 = values[i++]; - float val6 = val; - if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue; - float val6hr = val1+val2+val3+val4+val5+val6; - if(min6hrIdx == -1 || min6hr > val6hr) { - min6hr = val6hr; - min6hrIdx = i-5; - } - } - - } - - char ts1hr[21]; - if(min1hrIdx != -1) { - tmElements_t tm; - breakTime(now + (SECS_PER_HOUR * min1hrIdx), tm); - sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); - } - char ts3hr[21]; - if(min3hrIdx != -1) { - tmElements_t tm; - breakTime(now + (SECS_PER_HOUR * min3hrIdx), tm); - sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); - } - char ts6hr[21]; - if(min6hrIdx != -1) { - tmElements_t tm; - breakTime(now + (SECS_PER_HOUR * min6hrIdx), tm); - sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); - } - - switch(payloadFormat) { - case 0: // JSON - { - StaticJsonDocument<384> json; - json["id"] = WiFi.macAddress(); - JsonObject jp = json.createNestedObject("prices"); - for(int i = 0; i < 24; i++) { - float val = values[i]; - if(val == ENTSOE_NO_VALUE) break; - jp[String(i)] = serialized(String(val, 4)); - } - if(min != INT16_MAX) { - jp["min"] = serialized(String(min, 4)); - } - if(max != INT16_MIN) { - jp["max"] = serialized(String(max, 4)); - } - - if(min1hrIdx != -1) { - jp["cheapest1hr"] = String(ts1hr); - } - if(min3hrIdx != -1) { - jp["cheapest3hr"] = String(ts3hr); - } - if(min6hrIdx != -1) { - jp["cheapest6hr"] = String(ts6hr); - } - - String msg; - serializeJson(json, msg); - mqtt.publish(topic, msg.c_str()); - break; - } - case 1: // RAW - case 2: - { - for(int i = 0; i < 24; i++) { - float val = values[i]; - if(val == ENTSOE_NO_VALUE) { - mqtt.publish(topic + "/price/" + String(i), ""); - break; - } else { - mqtt.publish(topic + "/price/" + String(i), String(val, 4)); - } - mqtt.loop(); - delay(10); - } - if(min != INT16_MAX) { - mqtt.publish(topic + "/price/min", String(min, 4)); - } - if(max != INT16_MIN) { - mqtt.publish(topic + "/price/max", String(max, 4)); - } - - if(min1hrIdx != -1) { - mqtt.publish(topic + "/price/cheapest/1hr", String(ts1hr)); - } - if(min3hrIdx != -1) { - mqtt.publish(topic + "/price/cheapest/3hr", String(ts3hr)); - } - if(min6hrIdx != -1) { - mqtt.publish(topic + "/price/cheapest/6hr", String(ts6hr)); - } - } - break; - } -} - int currentMeterType = 0; void readHanPort() { if (hanReader.read()) { @@ -775,163 +588,13 @@ void readHanPort() { AmsData data(meterConfig.type, meterConfig.substituteMissing, hanReader); if(data.getListType() > 0) { - if(mqttEnabled && !topic.isEmpty()) { - if(payloadFormat == 0) { - StaticJsonDocument<512> json; - hanToJson(json, data, hw, hw.getTemperature(), clientId); - if (Debug.isActive(RemoteDebug::INFO)) { - debugI("Sending data to MQTT"); - if (Debug.isActive(RemoteDebug::DEBUG)) { - serializeJsonPretty(json, Debug); - } + if(mqttEnabled && mqttHandler != NULL) { + if(mqttHandler->publish(&data, &meterState)) { + if(data.getListType() == 3) { + mqttHandler->publishPrices(&eapi); } - - String msg; - serializeJson(json, msg); - mqtt.publish(topic, msg.c_str()); - // - // Start DOMOTICZ - // - } else if(payloadFormat == 3) { - debugI("Sending data to MQTT"); - if(config.isDomoChanged()) { - if(domoConfig == NULL) - domoConfig = new DomoticzConfig(); - if(config.getDomoticzConfig(*domoConfig)) { - config.ackDomoChange(); - } - } - - if (domoConfig->elidx > 0) { - if(data.getActiveImportCounter() > 1.0) { - energy = data.getActiveImportCounter(); - } - if(energy > 0.0) { - String PowerEnergy; - int p; - StaticJsonDocument<200> json_PE; - p = data.getActiveImportPower(); - PowerEnergy = String((double) p/1.0) + ";" + String(energy*1000.0, 0); - json_PE["command"] = "udevice"; - json_PE["idx"] = domoConfig->elidx; - json_PE["nvalue"] = 0; - json_PE["svalue"] = PowerEnergy; - String msg_PE; - serializeJson(json_PE, msg_PE); - mqtt.publish("domoticz/in", msg_PE.c_str()); - } - } - - if (domoConfig->vl1idx > 0){ - if (data.getL1Voltage() > 0.1){ - StaticJsonDocument<200> json_u1; - json_u1["command"] = "udevice"; - json_u1["idx"] = domoConfig->vl1idx; - json_u1["nvalue"] = 0; - json_u1["svalue"] = String(data.getL1Voltage(), 2); - String msg_u1; - serializeJson(json_u1, msg_u1); - mqtt.publish("domoticz/in", msg_u1.c_str()); - } - } - - if (domoConfig->vl2idx > 0){ - if (data.getL2Voltage() > 0.1){ - StaticJsonDocument<200> json_u2; - json_u2["command"] = "udevice"; - json_u2["idx"] = domoConfig->vl2idx; - json_u2["nvalue"] = 0; - json_u2["svalue"] = String(data.getL2Voltage(), 2); - String msg_u2; - serializeJson(json_u2, msg_u2); - mqtt.publish("domoticz/in", msg_u2.c_str()); - } - } - - if (domoConfig->vl3idx > 0){ - if (data.getL3Voltage() > 0.1){ - StaticJsonDocument<200> json_u3; - json_u3["command"] = "udevice"; - json_u3["idx"] = domoConfig->vl3idx; - json_u3["nvalue"] = 0; - json_u3["svalue"] = String(data.getL3Voltage()); - String msg_u3; - serializeJson(json_u3, msg_u3); - mqtt.publish("domoticz/in", msg_u3.c_str()); - } - } - - if (domoConfig->cl1idx > 0){ - if(data.getL1Current() > 0.0) { - StaticJsonDocument<200> json_i1; - String Ampere3; - Ampere3 = String(data.getL1Current(), 1) + ";" + String(data.getL2Current(), 1) + ";" + String(data.getL3Current(), 1) ; - json_i1["command"] = "udevice"; - json_i1["idx"] = domoConfig->cl1idx; - json_i1["nvalue"] = 0; - json_i1["svalue"] = Ampere3; - String msg_i1; - serializeJson(json_i1, msg_i1); - mqtt.publish("domoticz/in", msg_i1.c_str()); - } - } - // - // End DOMOTICZ - // - } else if(payloadFormat == 1 || payloadFormat == 2) { - if(data.getPackageTimestamp() > 0) { - mqtt.publish(topic + "/meter/dlms/timestamp", String(data.getPackageTimestamp())); - } - switch(data.getListType()) { - case 3: - // ID and type belongs to List 2, but I see no need to send that every 10s - mqtt.publish(topic + "/meter/id", data.getMeterId()); - mqtt.publish(topic + "/meter/type", data.getMeterType()); - mqtt.publish(topic + "/meter/clock", String(data.getMeterTimestamp())); - mqtt.publish(topic + "/meter/import/reactive/accumulated", String(data.getReactiveImportCounter(), 2)); - mqtt.publish(topic + "/meter/import/active/accumulated", String(data.getActiveImportCounter(), 2)); - mqtt.publish(topic + "/meter/export/reactive/accumulated", String(data.getReactiveExportCounter(), 2)); - mqtt.publish(topic + "/meter/export/active/accumulated", String(data.getActiveExportCounter(), 2)); - sendPricesToMqtt(); - case 2: - // Only send data if changed. ID and Type is sent on the 10s interval only if changed - if(meterState.getMeterId() != data.getMeterId() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/id", data.getMeterId()); - } - if(meterState.getMeterType() != data.getMeterType() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/type", data.getMeterType()); - } - if(meterState.getL1Current() != data.getL1Current() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/l1/current", String(data.getL1Current(), 2)); - } - if(meterState.getL1Voltage() != data.getL1Voltage() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/l1/voltage", String(data.getL1Voltage(), 2)); - } - if(meterState.getL2Current() != data.getL2Current() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/l2/current", String(data.getL2Current(), 2)); - } - if(meterState.getL2Voltage() != data.getL2Voltage() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/l2/voltage", String(data.getL2Voltage(), 2)); - } - if(meterState.getL3Current() != data.getL3Current() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/l3/current", String(data.getL3Current(), 2)); - } - if(meterState.getL3Voltage() != data.getL3Voltage() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/l3/voltage", String(data.getL3Voltage(), 2)); - } - if(meterState.getReactiveExportPower() != data.getReactiveExportPower() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/export/reactive", String(data.getReactiveExportPower())); - } - if(meterState.getActiveExportPower() != data.getActiveExportPower() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/export/active", String(data.getActiveExportPower())); - } - if(meterState.getReactiveImportPower() != data.getReactiveImportPower() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/import/reactive", String(data.getReactiveImportPower())); - } - case 1: - if(meterState.getActiveImportPower() != data.getActiveImportPower() || payloadFormat == 2) { - mqtt.publish(topic + "/meter/import/active", String(data.getActiveImportPower())); - } + if(data.getListType() >= 2) { + mqttHandler->publishSystem(&hw); } } mqtt.loop(); @@ -1044,6 +707,7 @@ void MQTT_connect() { if(!config.getMqttConfig(mqttConfig) || strlen(mqttConfig.host) == 0) { if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config"); mqttEnabled = false; + ws.setMqttEnabled(false); config.ackMqttChange(); return; } @@ -1053,8 +717,8 @@ void MQTT_connect() { } lastMqttRetry = millis(); - clientId = String(mqttConfig.clientId); mqttEnabled = true; + ws.setMqttEnabled(true); payloadFormat = mqttConfig.payloadFormat; topic = String(mqttConfig.publishTopic); @@ -1065,6 +729,26 @@ void MQTT_connect() { mqtt.disconnect(); yield(); + if(mqttHandler != NULL) { + delete mqttHandler; + mqttHandler = NULL; + } + + switch(mqttConfig.payloadFormat) { + case 0: + mqttHandler = new JsonMqttHandler(&mqtt, mqttConfig.clientId, mqttConfig.publishTopic, &hw); + break; + case 1: + case 2: + mqttHandler = new RawMqttHandler(&mqtt, mqttConfig.publishTopic, mqttConfig.payloadFormat == 2); + break; + case 3: + DomoticzConfig domo; + config.getDomoticzConfig(domo); + mqttHandler = new DomoticzMqttHandler(&mqtt, domo); + break; + } + WiFiClientSecure *secureClient = NULL; Client *client = NULL; if(mqttConfig.ssl) { @@ -1127,10 +811,8 @@ void MQTT_connect() { if (Debug.isActive(RemoteDebug::INFO)) debugI(" Subscribing to [%s]\r\n", mqttConfig.subscribeTopic); } - if(payloadFormat == 0) { - sendMqttData("Connected!"); - } else if(payloadFormat == 1 || payloadFormat == 2) { - sendSystemStatusToMqtt(); + if(mqttHandler != NULL) { + mqttHandler->publishSystem(&hw); } } else { if (Debug.isActive(RemoteDebug::ERROR)) { @@ -1146,52 +828,3 @@ void MQTT_connect() { } yield(); } - -// Send a simple string embedded in json over MQTT -void sendMqttData(String data) -{ - // Make sure we have configured a publish topic - if (topic.isEmpty()) - return; - - // Build a json with the message in a "data" attribute - StaticJsonDocument<128> json; - json["id"] = WiFi.macAddress(); - json["up"] = millis64()/1000; - json["data"] = data; - double vcc = hw.getVcc(); - if(vcc > 0) { - json["vcc"] = vcc; - } - json["rssi"] = hw.getWifiRssi(); - - // Stringify the json - String msg; - serializeJson(json, msg); - - // Send the json over MQTT - mqtt.publish(topic, msg.c_str()); - - if (Debug.isActive(RemoteDebug::INFO)) debugI("Sending MQTT data"); - if (Debug.isActive(RemoteDebug::DEBUG)) debugD("[%s]", data.c_str()); -} - -unsigned long lastSystemDataSent = -10000; -void sendSystemStatusToMqtt() { - if (topic.isEmpty()) - return; - if(millis() - lastSystemDataSent < 10000) - return; - lastSystemDataSent = millis(); - - mqtt.publish(topic + "/id", WiFi.macAddress()); - mqtt.publish(topic + "/uptime", String((unsigned long) millis64()/1000)); - double vcc = hw.getVcc(); - if(vcc > 0) { - mqtt.publish(topic + "/vcc", String(vcc, 2)); - } - mqtt.publish(topic + "/rssi", String(hw.getWifiRssi())); - if(hw.getTemperature() > -85) { - mqtt.publish(topic + "/temperature", String(hw.getTemperature(), 2)); - } -} diff --git a/src/HanToJson.cpp b/src/HanToJson.cpp deleted file mode 100644 index f455693f..00000000 --- a/src/HanToJson.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "HanToJson.h" - -void hanToJson(JsonDocument& json, AmsData& data, HwTools& hw, double temperature, String name) { - json["id"] = WiFi.macAddress(); - json["name"] = name; - json["up"] = millis(); - json["t"] = data.getPackageTimestamp(); - - double vcc = hw.getVcc(); - if(vcc > 0) { - json["vcc"] = serialized(String(vcc, 3)); - } - - json["rssi"] = hw.getWifiRssi(); - - if(temperature != DEVICE_DISCONNECTED_C) { - json["temp"] = serialized(String(temperature, 2)); - } - - // Add a sub-structure to the json object, - // to keep the data from the meter itself - JsonObject jd = json.createNestedObject("data"); - - switch(data.getListType()) { - case 3: - jd["rtc"] = data.getMeterTimestamp(); - jd["tPI"] = data.getActiveImportCounter(); - jd["tPO"] = data.getActiveExportCounter(); - jd["tQI"] = data.getReactiveImportCounter(); - jd["tQO"] = data.getReactiveExportCounter(); - case 2: - jd["lv"] = data.getListId(); - jd["id"] = data.getMeterId(); - jd["type"] = data.getMeterType(); - jd["Q"] = data.getReactiveImportPower(); - jd["PO"] = data.getActiveExportPower(); - jd["QO"] = data.getReactiveExportPower(); - jd["I1"] = data.getL1Current(); - jd["I2"] = data.getL2Current(); - jd["I3"] = data.getL3Current(); - jd["U1"] = data.getL1Voltage(); - jd["U2"] = data.getL2Voltage(); - jd["U3"] = data.getL3Voltage(); - case 1: - jd["P"] = data.getActiveImportPower(); - } -} diff --git a/src/HanToJson.h b/src/HanToJson.h deleted file mode 100644 index c64ea5cf..00000000 --- a/src/HanToJson.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _HANTOJSON_h -#define _HANTOJSON_h - -#if defined(ARDUINO) && ARDUINO >= 100 - #include "Arduino.h" -#else - #include "WProgram.h" -#endif - -#include -#include "AmsData.h" -#include "HwTools.h" - -void hanToJson(JsonDocument& json, AmsData& data, HwTools& hw, double temperature, String name); - -#endif diff --git a/src/hexutils.cpp b/src/hexutils.cpp new file mode 100644 index 00000000..2f36af4a --- /dev/null +++ b/src/hexutils.cpp @@ -0,0 +1,23 @@ +#include "hexutils.h"; + +String toHex(uint8_t* in) { + return toHex(in, sizeof(in)*2); +} + +String toHex(uint8_t* in, uint8_t size) { + String hex; + for(int i = 0; i < size; i++) { + if(in[i] < 0x10) { + hex += '0'; + } + hex += String(in[i], HEX); + } + hex.toUpperCase(); + return hex; +} + +void fromHex(uint8_t *out, String in, uint8_t size) { + for(int i = 0; i < size*2; i += 2) { + out[i/2] = strtol(in.substring(i, i+2).c_str(), 0, 16); + } +} \ No newline at end of file diff --git a/src/hexutils.h b/src/hexutils.h new file mode 100644 index 00000000..3f25773d --- /dev/null +++ b/src/hexutils.h @@ -0,0 +1,11 @@ +#ifndef _HEXUTILS_H +#define _HEXUTILS_H + +#include +#include "Arduino.h" + +String toHex(uint8_t* in); +String toHex(uint8_t* in, uint8_t size); +void fromHex(uint8_t *out, String in, uint8_t size); + +#endif \ No newline at end of file diff --git a/src/mqtt/AmsMqttHandler.h b/src/mqtt/AmsMqttHandler.h new file mode 100644 index 00000000..f627ba36 --- /dev/null +++ b/src/mqtt/AmsMqttHandler.h @@ -0,0 +1,26 @@ +#ifndef _AMSMQTTHANDLER_H +#define _AMSMQTTHANDLER_H + +#include "Arduino.h" +#include +#include "AmsData.h" +#include "AmsConfiguration.h" +#include "HwTools.h" +#include "entsoe/EntsoeApi.h" + +class AmsMqttHandler { +public: + AmsMqttHandler(MQTTClient* mqtt) { + this->mqtt = mqtt; + }; + + virtual bool publish(AmsData* data, AmsData* previousState); + virtual bool publishTemperatures(AmsConfiguration*, HwTools*); + virtual bool publishPrices(EntsoeApi* eapi); + virtual bool publishSystem(HwTools*); + +protected: + MQTTClient* mqtt; +}; + +#endif diff --git a/src/mqtt/DomoticzMqttHandler.cpp b/src/mqtt/DomoticzMqttHandler.cpp new file mode 100644 index 00000000..288a105b --- /dev/null +++ b/src/mqtt/DomoticzMqttHandler.cpp @@ -0,0 +1,89 @@ +#include "DomoticzMqttHandler.h" +#include "web/root/domoticz_json.h" + +bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) { + bool ret = false; + if (config.elidx > 0) { + if(data->getActiveImportCounter() > 1.0) { + energy = data->getActiveImportCounter(); + } + if(energy > 0.0) { + char val[16]; + snprintf(val, 16, "%.1f;%.1f", (data->getActiveImportPower()/1.0), energy*1000.0); + char json[192]; + snprintf_P(json, sizeof(json), DOMOTICZ_JSON, + config.elidx, + val + ); + ret = mqtt->publish("domoticz/in", json); + } + } + + if(data->getListType() == 1) + return ret; + + if (config.vl1idx > 0){ + if (data->getL1Voltage() > 0.1){ + char val[16]; + snprintf(val, 16, "%.2f", data->getL1Voltage()); + char json[192]; + snprintf_P(json, sizeof(json), DOMOTICZ_JSON, + config.vl1idx, + val + ); + ret |= mqtt->publish("domoticz/in", json); + } + } + + if (config.vl2idx > 0){ + if (data->getL2Voltage() > 0.1){ + char val[16]; + snprintf(val, 16, "%.2f", data->getL2Voltage()); + char json[192]; + snprintf_P(json, sizeof(json), DOMOTICZ_JSON, + config.vl2idx, + val + ); + ret |= mqtt->publish("domoticz/in", json); + } + } + + if (config.vl3idx > 0){ + if (data->getL3Voltage() > 0.1){ + char val[16]; + snprintf(val, 16, "%.2f", data->getL3Voltage()); + char json[192]; + snprintf_P(json, sizeof(json), DOMOTICZ_JSON, + config.vl3idx, + val + ); + ret |= mqtt->publish("domoticz/in", json); + } + } + + if (config.cl1idx > 0){ + if(data->getL1Current() > 0.0) { + char val[16]; + snprintf(val, 16, "%.1f;%.1f;%.1f", data->getL1Current(), data->getL2Current(), data->getL3Current()); + char json[192]; + snprintf_P(json, sizeof(json), DOMOTICZ_JSON, + config.cl1idx, + val + ); + ret |= mqtt->publish("domoticz/in", json); + } + } + return ret; +} + +bool DomoticzMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) { + return false; +} + +bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) { + return false; +} + +bool DomoticzMqttHandler::publishSystem(HwTools* hw) { + return false; +} diff --git a/src/mqtt/DomoticzMqttHandler.h b/src/mqtt/DomoticzMqttHandler.h new file mode 100644 index 00000000..408f0d75 --- /dev/null +++ b/src/mqtt/DomoticzMqttHandler.h @@ -0,0 +1,21 @@ +#ifndef _DOMOTICZMQTTHANDLER_H +#define _DOMOTICZMQTTHANDLER_H + +#include "AmsMqttHandler.h" +#include "AmsConfiguration.h" + +class DomoticzMqttHandler : public AmsMqttHandler { +public: + DomoticzMqttHandler(MQTTClient* mqtt, DomoticzConfig config) : AmsMqttHandler(mqtt) { + this->config = config; + }; + bool publish(AmsData* data, AmsData* previousState); + bool publishTemperatures(AmsConfiguration*, HwTools*); + bool publishPrices(EntsoeApi*); + bool publishSystem(HwTools*); + +private: + DomoticzConfig config; + int energy = 0.0; +}; +#endif diff --git a/src/mqtt/JsonMqttHandler.cpp b/src/mqtt/JsonMqttHandler.cpp new file mode 100644 index 00000000..5ab5f037 --- /dev/null +++ b/src/mqtt/JsonMqttHandler.cpp @@ -0,0 +1,227 @@ +#include "JsonMqttHandler.h" +#include "hexutils.h" +#include "Uptime.h" +#include "web/root/json1_json.h" +#include "web/root/json2_json.h" +#include "web/root/json3_json.h" +#include "web/root/jsonsys_json.h" +#include "web/root/jsonprices_json.h" + +bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) { + if(topic.isEmpty() || !mqtt->connected()) + return false; + + double vcc = hw->getVcc(); + if(data->getListType() == 1) { + char json[192]; + snprintf_P(json, sizeof(json), JSON1_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + data->getPackageTimestamp(), + vcc, + hw->getWifiRssi(), + hw->getTemperature(), + data->getActiveImportPower() + ); + return mqtt->publish(topic, json); + } else if(data->getListType() == 2) { + char json[256]; + snprintf_P(json, sizeof(json), JSON2_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + data->getPackageTimestamp(), + vcc, + hw->getWifiRssi(), + hw->getTemperature(), + data->getListId().c_str(), + data->getMeterId().c_str(), + data->getMeterType().c_str(), + data->getActiveImportPower(), + data->getReactiveImportPower(), + data->getActiveExportPower(), + data->getReactiveExportPower(), + data->getL1Current(), + data->getL2Current(), + data->getL3Current(), + data->getL1Voltage(), + data->getL2Voltage(), + data->getL3Voltage() + ); + return mqtt->publish(topic, json); + } else if(data->getListType() == 3) { + char json[384]; + snprintf_P(json, sizeof(json), JSON3_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + data->getPackageTimestamp(), + vcc, + hw->getWifiRssi(), + hw->getTemperature(), + data->getListId().c_str(), + data->getMeterId().c_str(), + data->getMeterType().c_str(), + data->getActiveImportPower(), + data->getReactiveImportPower(), + data->getActiveExportPower(), + data->getReactiveExportPower(), + data->getL1Current(), + data->getL2Current(), + data->getL3Current(), + data->getL1Voltage(), + data->getL2Voltage(), + data->getL3Voltage(), + data->getActiveImportCounter(), + data->getActiveExportCounter(), + data->getReactiveImportCounter(), + data->getReactiveExportCounter(), + data->getMeterTimestamp() + ); + return mqtt->publish(topic, json); + } +} + +bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) { + int count = hw->getTempSensorCount(); + if(count == 0) + return false; + + int size = 32 + (count * 26); + + char buf[size]; + snprintf(buf, 24, "{\"temperatures\":{"); + + for(int i = 0; i < count; i++) { + TempSensorData* data = hw->getTempSensorData(i); + TempSensorConfig* conf = config->getTempSensorConfig(data->address); + char* pos = buf+strlen(buf); + snprintf(pos, 26, "\"%s\":%.2f,", + toHex(data->address, 8).c_str(), + data->lastRead + ); + data->changed = false; + delay(1); + } + char* pos = buf+strlen(buf); + snprintf(count == 0 ? pos : pos-1, 8, "}}"); + return mqtt->publish(topic, buf); +} + +bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) { + if(topic.isEmpty() || !mqtt->connected()) + return false; + if(strcmp(eapi->getToken(), "") != 0) + return false; + + time_t now = time(nullptr); + + float min1hr, min3hr, min6hr; + uint8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1; + float min = INT16_MAX, max = INT16_MIN; + float values[24] = {0}; + for(uint8_t i = 0; i < 24; i++) { + float val = eapi->getValueForHour(now, i); + values[i] = val; + + if(val == ENTSOE_NO_VALUE) break; + + if(val < min) min = val; + if(val > max) max = val; + + if(min1hrIdx == -1 || min1hr > val) { + min1hr = val; + min1hrIdx = i; + } + + if(i >= 2) { + i -= 2; + float val1 = values[i++]; + float val2 = values[i++]; + float val3 = val; + if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue; + float val3hr = val1+val2+val3; + if(min3hrIdx == -1 || min3hr > val3hr) { + min3hr = val3hr; + min3hrIdx = i-2; + } + } + + if(i >= 5) { + i -= 5; + float val1 = values[i++]; + float val2 = values[i++]; + float val3 = values[i++]; + float val4 = values[i++]; + float val5 = values[i++]; + float val6 = val; + if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue; + float val6hr = val1+val2+val3+val4+val5+val6; + if(min6hrIdx == -1 || min6hr > val6hr) { + min6hr = val6hr; + min6hrIdx = i-5; + } + } + + } + + char ts1hr[21]; + if(min1hrIdx != -1) { + tmElements_t tm; + breakTime(now + (SECS_PER_HOUR * min1hrIdx), tm); + sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + char ts3hr[21]; + if(min3hrIdx != -1) { + tmElements_t tm; + breakTime(now + (SECS_PER_HOUR * min3hrIdx), tm); + sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + char ts6hr[21]; + if(min6hrIdx != -1) { + tmElements_t tm; + breakTime(now + (SECS_PER_HOUR * min6hrIdx), tm); + sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + + char json[384]; + snprintf_P(json, sizeof(json), JSONPRICES_JSON, + WiFi.macAddress().c_str(), + values[0], + values[1], + values[2], + values[3], + values[4], + values[5], + values[6], + values[7], + values[8], + values[9], + values[10], + values[11], + min == INT16_MAX ? 0.0 : min, + max == INT16_MIN ? 0.0 : max, + ts1hr, + ts3hr, + ts6hr + ); + return mqtt->publish(topic, json); +} + +bool JsonMqttHandler::publishSystem(HwTools* hw) { + if(init || topic.isEmpty() || !mqtt->connected()) + return false; + + char json[192]; + snprintf_P(json, sizeof(json), JSONSYS_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + hw->getVcc(), + hw->getWifiRssi(), + hw->getTemperature() + ); + init = mqtt->publish(topic, json); + return init; +} diff --git a/src/mqtt/JsonMqttHandler.h b/src/mqtt/JsonMqttHandler.h new file mode 100644 index 00000000..c8f45d03 --- /dev/null +++ b/src/mqtt/JsonMqttHandler.h @@ -0,0 +1,24 @@ +#ifndef _JSONMQTTHANDLER_H +#define _JSONMQTTHANDLER_H + +#include "AmsMqttHandler.h" + +class JsonMqttHandler : public AmsMqttHandler { +public: + JsonMqttHandler(MQTTClient* mqtt, const char* clientId, const char* topic, HwTools* hw) : AmsMqttHandler(mqtt) { + this->clientId = clientId; + this->topic = String(topic); + this->hw = hw; + }; + bool publish(AmsData* data, AmsData* previousState); + bool publishTemperatures(AmsConfiguration*, HwTools*); + bool publishPrices(EntsoeApi*); + bool publishSystem(HwTools*); + +private: + String clientId; + String topic; + HwTools* hw; + bool init = false; +}; +#endif diff --git a/src/mqtt/RawMqttHandler.cpp b/src/mqtt/RawMqttHandler.cpp new file mode 100644 index 00000000..36aa5378 --- /dev/null +++ b/src/mqtt/RawMqttHandler.cpp @@ -0,0 +1,200 @@ +#include "RawMqttHandler.h" +#include "hexutils.h" +#include "Uptime.h" + +bool RawMqttHandler::publish(AmsData* data, AmsData* meterState) { + if(topic.isEmpty() || !mqtt->connected()) + return false; + + if(data->getPackageTimestamp() > 0) { + mqtt->publish(topic + "/meter/dlms/timestamp", String(data->getPackageTimestamp())); + } + switch(data->getListType()) { + case 3: + // ID and type belongs to List 2, but I see no need to send that every 10s + mqtt->publish(topic + "/meter/id", data->getMeterId()); + mqtt->publish(topic + "/meter/type", data->getMeterType()); + mqtt->publish(topic + "/meter/clock", String(data->getMeterTimestamp())); + mqtt->publish(topic + "/meter/import/reactive/accumulated", String(data->getReactiveImportCounter(), 2)); + mqtt->publish(topic + "/meter/import/active/accumulated", String(data->getActiveImportCounter(), 2)); + mqtt->publish(topic + "/meter/export/reactive/accumulated", String(data->getReactiveExportCounter(), 2)); + mqtt->publish(topic + "/meter/export/active/accumulated", String(data->getActiveExportCounter(), 2)); + case 2: + // Only send data if changed. ID and Type is sent on the 10s interval only if changed + if(full || meterState->getMeterId() != data->getMeterId()) { + mqtt->publish(topic + "/meter/id", data->getMeterId()); + } + if(full || meterState->getMeterType() != data->getMeterType()) { + mqtt->publish(topic + "/meter/type", data->getMeterType()); + } + if(full || meterState->getL1Current() != data->getL1Current()) { + mqtt->publish(topic + "/meter/l1/current", String(data->getL1Current(), 2)); + } + if(full || meterState->getL1Voltage() != data->getL1Voltage()) { + mqtt->publish(topic + "/meter/l1/voltage", String(data->getL1Voltage(), 2)); + } + if(full || meterState->getL2Current() != data->getL2Current()) { + mqtt->publish(topic + "/meter/l2/current", String(data->getL2Current(), 2)); + } + if(full || meterState->getL2Voltage() != data->getL2Voltage()) { + mqtt->publish(topic + "/meter/l2/voltage", String(data->getL2Voltage(), 2)); + } + if(full || meterState->getL3Current() != data->getL3Current()) { + mqtt->publish(topic + "/meter/l3/current", String(data->getL3Current(), 2)); + } + if(full || meterState->getL3Voltage() != data->getL3Voltage()) { + mqtt->publish(topic + "/meter/l3/voltage", String(data->getL3Voltage(), 2)); + } + if(full || meterState->getReactiveExportPower() != data->getReactiveExportPower()) { + mqtt->publish(topic + "/meter/export/reactive", String(data->getReactiveExportPower())); + } + if(full || meterState->getActiveExportPower() != data->getActiveExportPower()) { + mqtt->publish(topic + "/meter/export/active", String(data->getActiveExportPower())); + } + if(full || meterState->getReactiveImportPower() != data->getReactiveImportPower()) { + mqtt->publish(topic + "/meter/import/reactive", String(data->getReactiveImportPower())); + } + case 1: + if(full || meterState->getActiveImportPower() != data->getActiveImportPower()) { + mqtt->publish(topic + "/meter/import/active", String(data->getActiveImportPower())); + } + } + return true; +} + +bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) { + uint8_t c = hw->getTempSensorCount(); + for(int i = 0; i < c; i++) { + TempSensorData* data = hw->getTempSensorData(i); + if(data->lastValidRead > -85) { + if(data->changed || full) { + mqtt->publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2)); + data->changed = false; + } + } + } + return c > 0; +} + +bool RawMqttHandler::publishPrices(EntsoeApi* eapi) { + if(topic.isEmpty() || !mqtt->connected()) + return false; + if(strcmp(eapi->getToken(), "") != 0) + return false; + + time_t now = time(nullptr); + + float min1hr, min3hr, min6hr; + uint8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1; + float min = INT16_MAX, max = INT16_MIN; + float values[24] = {0}; + for(uint8_t i = 0; i < 24; i++) { + float val = eapi->getValueForHour(now, i); + values[i] = val; + + if(val == ENTSOE_NO_VALUE) break; + + if(val < min) min = val; + if(val > max) max = val; + + if(min1hrIdx == -1 || min1hr > val) { + min1hr = val; + min1hrIdx = i; + } + + if(i >= 2) { + i -= 2; + float val1 = values[i++]; + float val2 = values[i++]; + float val3 = val; + if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue; + float val3hr = val1+val2+val3; + if(min3hrIdx == -1 || min3hr > val3hr) { + min3hr = val3hr; + min3hrIdx = i-2; + } + } + + if(i >= 5) { + i -= 5; + float val1 = values[i++]; + float val2 = values[i++]; + float val3 = values[i++]; + float val4 = values[i++]; + float val5 = values[i++]; + float val6 = val; + if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue; + float val6hr = val1+val2+val3+val4+val5+val6; + if(min6hrIdx == -1 || min6hr > val6hr) { + min6hr = val6hr; + min6hrIdx = i-5; + } + } + + } + + char ts1hr[21]; + if(min1hrIdx != -1) { + tmElements_t tm; + breakTime(now + (SECS_PER_HOUR * min1hrIdx), tm); + sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + char ts3hr[21]; + if(min3hrIdx != -1) { + tmElements_t tm; + breakTime(now + (SECS_PER_HOUR * min3hrIdx), tm); + sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + char ts6hr[21]; + if(min6hrIdx != -1) { + tmElements_t tm; + breakTime(now + (SECS_PER_HOUR * min6hrIdx), tm); + sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + + for(int i = 0; i < 24; i++) { + float val = values[i]; + if(val == ENTSOE_NO_VALUE) { + mqtt->publish(topic + "/price/" + String(i), ""); + break; + } else { + mqtt->publish(topic + "/price/" + String(i), String(val, 4)); + } + mqtt->loop(); + delay(10); + } + if(min != INT16_MAX) { + mqtt->publish(topic + "/price/min", String(min, 4)); + } + if(max != INT16_MIN) { + mqtt->publish(topic + "/price/max", String(max, 4)); + } + + if(min1hrIdx != -1) { + mqtt->publish(topic + "/price/cheapest/1hr", String(ts1hr)); + } + if(min3hrIdx != -1) { + mqtt->publish(topic + "/price/cheapest/3hr", String(ts3hr)); + } + if(min6hrIdx != -1) { + mqtt->publish(topic + "/price/cheapest/6hr", String(ts6hr)); + } + return true; +} + +bool RawMqttHandler::publishSystem(HwTools* hw) { + if(topic.isEmpty() || !mqtt->connected()) + return false; + + mqtt->publish(topic + "/id", WiFi.macAddress()); + mqtt->publish(topic + "/uptime", String((unsigned long) millis64()/1000)); + float vcc = hw->getVcc(); + if(vcc > 0) { + mqtt->publish(topic + "/vcc", String(vcc, 2)); + } + mqtt->publish(topic + "/rssi", String(hw->getWifiRssi())); + if(hw->getTemperature() > -85) { + mqtt->publish(topic + "/temperature", String(hw->getTemperature(), 2)); + } + return true; +} \ No newline at end of file diff --git a/src/mqtt/RawMqttHandler.h b/src/mqtt/RawMqttHandler.h new file mode 100644 index 00000000..f312875c --- /dev/null +++ b/src/mqtt/RawMqttHandler.h @@ -0,0 +1,21 @@ +#ifndef _RAWMQTTHANDLER_H +#define _RAWMQTTHANDLER_H + +#include "AmsMqttHandler.h" + +class RawMqttHandler : public AmsMqttHandler { +public: + RawMqttHandler(MQTTClient* mqtt, const char* topic, bool full) : AmsMqttHandler(mqtt) { + this->topic = String(topic); + this->full = full; + }; + bool publish(AmsData* data, AmsData* previousState); + bool publishTemperatures(AmsConfiguration*, HwTools*); + bool publishPrices(EntsoeApi*); + bool publishSystem(HwTools*); + +private: + String topic; + bool full; +}; +#endif diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index b21fee14..d47a5fea 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -1,6 +1,7 @@ #include "AmsWebServer.h" #include "version.h" #include "AmsStorage.h" +#include "hexutils.h" #include "root/head_html.h" #include "root/foot_html.h" @@ -101,6 +102,10 @@ void AmsWebServer::setTimezone(Timezone* tz) { this->tz = tz; } +void AmsWebServer::setMqttEnabled(bool enabled) { + mqttEnabled = enabled; +} + void AmsWebServer::loop() { server.handleClient(); @@ -192,7 +197,7 @@ void AmsWebServer::temperatureJson() { int size = 32 + (count * 72); char buf[size]; - snprintf_P(buf, 16, "{\"c\":%d,\"s\":[", count); + snprintf(buf, 16, "{\"c\":%d,\"s\":[", count); for(int i = 0; i < count; i++) { TempSensorData* data = hw->getTempSensorData(i); @@ -208,7 +213,7 @@ void AmsWebServer::temperatureJson() { delay(1); } char* pos = buf+strlen(buf); - snprintf_P(count == 0 ? pos : pos-1, 8, "]}"); + snprintf(count == 0 ? pos : pos-1, 8, "]}"); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); @@ -391,24 +396,6 @@ void AmsWebServer::configMeterHtml() { server.sendContent_P(FOOT_HTML); } -String AmsWebServer::toHex(uint8_t* in, uint8_t size) { - String hex; - for(int i = 0; i < size; i++) { - if(in[i] < 0x10) { - hex += '0'; - } - hex += String(in[i], HEX); - } - hex.toUpperCase(); - return hex; -} - -void AmsWebServer::fromHex(uint8_t *out, String in, uint8_t size) { - for(int i = 0; i < size*2; i += 2) { - out[i/2] = strtol(in.substring(i, i+2).c_str(), 0, 16); - } -} - void AmsWebServer::configWifiHtml() { printD("Serving /wifi.html over http..."); diff --git a/src/web/AmsWebServer.h b/src/web/AmsWebServer.h index 274dd3d9..f946a25d 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -31,6 +31,7 @@ public: void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, MQTTClient*); void loop(); void setTimezone(Timezone* tz); + void setMqttEnabled(bool); private: RemoteDebug* debugger; @@ -109,9 +110,6 @@ private: void notFound(); - String toHex(uint8_t* in, uint8_t size); - void fromHex(uint8_t *out, String in, uint8_t size); - void printD(String fmt, ...); void printI(String fmt, ...); void printW(String fmt, ...); diff --git a/web/application.js b/web/application.js index 5a24be53..1e1af3c2 100644 --- a/web/application.js +++ b/web/application.js @@ -272,9 +272,9 @@ var fetch = function() { } setStatus("esp", json.em); - setStatus("han", json.em == 3 ? 0 : json.hm); - setStatus("wifi", json.em == 3 ? 0 : json.wm); - setStatus("mqtt", json.em == 3 ? 0 : json.mm); + setStatus("han", json.hm); + setStatus("wifi", json.wm); + setStatus("mqtt", json.mm); if(im && im.gaugeMeter) { @@ -348,10 +348,10 @@ var fetch = function() { }).fail(function() { setTimeout(fetch, interval*4); - setStatus("mqtt", "secondary"); - setStatus("wifi", "secondary"); - setStatus("han", "secondary"); - setStatus("esp", "danger"); + setStatus("mqtt", 0); + setStatus("wifi", 0); + setStatus("han", 0); + setStatus("esp", 3); }); } diff --git a/web/domoticz.json b/web/domoticz.json new file mode 100644 index 00000000..c151e016 --- /dev/null +++ b/web/domoticz.json @@ -0,0 +1,6 @@ +{ + "command" : "udevice", + "idx" : %d, + "nvalue" : 0, + "svalue" : "%s" +} \ No newline at end of file diff --git a/web/json1.json b/web/json1.json new file mode 100644 index 00000000..aea46d51 --- /dev/null +++ b/web/json1.json @@ -0,0 +1,12 @@ +{ + "id" : "%s", + "name" : "%s", + "up" : %d, + "t" : %d, + "vcc" : %.3f, + "rssi": %d, + "temp": %.2f, + "data" : { + "P" : %d + } +} diff --git a/web/json2.json b/web/json2.json new file mode 100644 index 00000000..c93cf6e8 --- /dev/null +++ b/web/json2.json @@ -0,0 +1,24 @@ +{ + "id" : "%s", + "name" : "%s", + "up" : %d, + "t" : %d, + "vcc" : %.3f, + "rssi": %d, + "temp": %.2f, + "data" : { + "lv" : "%s", + "id" : "%s", + "type" : "%s", + "P" : %d, + "Q" : %d, + "PO" : %d, + "QO" : %d, + "I1" : %.2f, + "I2" : %.2f, + "I3" : %.2f, + "U1" : %.2f, + "U2" : %.2f, + "U3" : %.2f + } +} diff --git a/web/json3.json b/web/json3.json new file mode 100644 index 00000000..7c62da20 --- /dev/null +++ b/web/json3.json @@ -0,0 +1,29 @@ +{ + "id" : "%s", + "name" : "%s", + "up" : %d, + "t" : %d, + "vcc" : %.3f, + "rssi": %d, + "temp": %.2f, + "data" : { + "lv" : "%s", + "id" : "%s", + "type" : "%s", + "P" : %d, + "Q" : %d, + "PO" : %d, + "QO" : %d, + "I1" : %.2f, + "I2" : %.2f, + "I3" : %.2f, + "U1" : %.2f, + "U2" : %.2f, + "U3" : %.2f, + "tPI" : %.1f, + "tPO" : %.1f, + "tQI" : %.1f, + "tQO" : %.1f, + "rtc" : %d + } +} diff --git a/web/jsonprices.json b/web/jsonprices.json new file mode 100644 index 00000000..f9823c3f --- /dev/null +++ b/web/jsonprices.json @@ -0,0 +1,23 @@ +{ + "id" : "%s", + "prices" : { + "0" : %.4f, + "1" : %.4f, + "2" : %.4f, + "3" : %.4f, + "4" : %.4f, + "5" : %.4f, + "6" : %.4f, + "7" : %.4f, + "8" : %.4f, + "9" : %.4f, + "10" : %.4f, + "11" : %.4f, + "12" : %.4f, + "min" : %.4f, + "max" : %.4f, + "cheapest1hr" : "%s", + "cheapest3hr" : "%s", + "cheapest6hr" : "%s" + } +} diff --git a/web/jsonsys.json b/web/jsonsys.json new file mode 100644 index 00000000..9c1bdccb --- /dev/null +++ b/web/jsonsys.json @@ -0,0 +1,8 @@ +{ + "id" : "%s", + "name" : "%s", + "up" : %d, + "vcc" : %.3f, + "rssi": %d, + "temp": %.2f +}