AMS reader ${version}
+ +-
+
- + + + + +
diff --git a/platformio.ini b/platformio.ini index b357a037..a4987a50 100755 --- a/platformio.ini +++ b/platformio.ini @@ -61,3 +61,21 @@ lib_deps = ${common.lib_deps} extra_scripts = pre:scripts/addversion.py scripts/makeweb.py + +[env:az-delivery-devkit-v4] +platform = espressif32@1.11.2 +board = az-delivery-devkit-v4 +framework = ${common.framework} +lib_deps = ${common.lib_deps} +extra_scripts = + pre:scripts/addversion.py + scripts/makeweb.py + +[env:esp32doit-devkit-v1] +platform = espressif32@1.11.2 +board = esp32doit-devkit-v1 +framework = ${common.framework} +lib_deps = ${common.lib_deps} +extra_scripts = + pre:scripts/addversion.py + scripts/makeweb.py diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index 20bd5755..0d7ae63d 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -177,6 +177,7 @@ void AmsConfiguration::clearMqtt() { setMqttSubscribeTopic(""); setMqttUser(""); setMqttPassword(""); + setMqttSsl(false); } void AmsConfiguration::setMqttChanged() { @@ -191,7 +192,6 @@ void AmsConfiguration::ackMqttChange() { mqttChanged = false; } - byte AmsConfiguration::getAuthSecurity() { return config.authSecurity; } @@ -278,6 +278,55 @@ void AmsConfiguration::setDebugLevel(uint8_t debugLevel) { config.debugLevel = debugLevel; } +int AmsConfiguration::getDomoELIDX() { + return config.domoELIDX; +} +int AmsConfiguration::getDomoVL1IDX() { + return config.domoVL1IDX; +} +int AmsConfiguration::getDomoVL2IDX() { + return config.domoVL2IDX; +} +int AmsConfiguration::getDomoVL3IDX() { + return config.domoVL3IDX; +} +int AmsConfiguration::getDomoCL1IDX() { + return config.domoCL1IDX; +} + +void AmsConfiguration::setDomoELIDX(int domoELIDX) { + domoChanged |= config.domoELIDX != domoELIDX; + config.domoELIDX = domoELIDX; +} + +void AmsConfiguration::setDomoVL1IDX(int domoVL1IDX) { + domoChanged |= config.domoVL1IDX != domoVL1IDX; + config.domoVL1IDX = domoVL1IDX; +} + +void AmsConfiguration::setDomoVL2IDX(int domoVL2IDX) { + domoChanged |= config.domoVL2IDX != domoVL2IDX; + config.domoVL2IDX = domoVL2IDX; +} + +void AmsConfiguration::setDomoVL3IDX(int domoVL3IDX) { + domoChanged |= config.domoVL3IDX != domoVL3IDX; + config.domoVL3IDX = domoVL3IDX; +} + +void AmsConfiguration::setDomoCL1IDX(int domoCL1IDX) { + domoChanged |= config.domoCL1IDX != domoCL1IDX; + config.domoCL1IDX = domoCL1IDX; +} + +void AmsConfiguration::clearDomo() { + setDomoELIDX(0); + setDomoVL1IDX(0); + setDomoVL2IDX(0); + setDomoVL3IDX(0); + setDomoCL1IDX(0); +} + bool AmsConfiguration::pinUsed(uint8_t pin) { if(pin == 0xFF) return false; @@ -410,6 +459,13 @@ void AmsConfiguration::setVccBootLimit(double vccBootLimit) { else config.vccBootLimit = max(2.5, min(vccBootLimit, 3.5)) * 10; } +bool AmsConfiguration::isDomoChanged() { + return domoChanged; +} + +void AmsConfiguration::ackDomoChange() { + domoChanged = false; +} bool AmsConfiguration::hasConfig() { if(configVersion == 0) { @@ -628,6 +684,29 @@ bool AmsConfiguration::loadConfig81(int address) { address += readInt(address, &i); setDebugLevel(i); + bool domo = false; + address += readBool(address, &domo); + if(domo) { + int domoELIDX; + address += readInt(address, &domoELIDX); + setDomoELIDX(domoELIDX); + int domoVL1IDX; + address += readInt(address, &domoVL1IDX); + setDomoVL1IDX(domoVL1IDX); + int domoVL2IDX; + address += readInt(address, &domoVL2IDX); + setDomoVL2IDX(domoVL2IDX); + int domoVL3IDX; + address += readInt(address, &domoVL3IDX); + setDomoVL3IDX(domoVL3IDX); + int domoCL1IDX; + address += readInt(address, &domoCL1IDX); + setDomoCL1IDX(domoCL1IDX); + + } else { + clearDomo(); + } + ackWifiChange(); return true; @@ -730,5 +809,13 @@ void AmsConfiguration::print(Print* debugger) debugger->printf("AP pin: %i\r\n", this->getApPin()); debugger->printf("Temperature pin: %i\r\n", this->getTempSensorPin()); + if(this->getDomoELIDX() > 0) { + debugger->printf("Domoticz ELIDX: %i\r\n", this->getDomoELIDX()); + debugger->printf("Domoticz VL1IDX: %i\r\n", this->getDomoVL1IDX()); + debugger->printf("Domoticz VL2IDX: %i\r\n", this->getDomoVL2IDX()); + debugger->printf("Domoticz VL3IDX: %i\r\n", this->getDomoVL3IDX()); + debugger->printf("Domoticz CL1IDX: %i\r\n", this->getDomoCL1IDX()); + } + debugger->println("-----------------------------------------------"); } diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index a672c8dc..65d83aa3 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -45,6 +45,12 @@ struct ConfigObject { uint8_t vccPin; uint16_t vccMultiplier; uint8_t vccBootLimit; + + int domoELIDX; + int domoVL1IDX; + int domoVL2IDX; + int domoVL3IDX; + int domoCL1IDX; }; class AmsConfiguration { @@ -154,7 +160,23 @@ public: void setVccBootLimit(double vccBootLimit); void print(Print* debugger); - + + int getDomoELIDX(); + int getDomoVL1IDX(); + int getDomoVL2IDX(); + int getDomoVL3IDX(); + int getDomoCL1IDX(); + double getDomoEnergy(); + void setDomoELIDX(int domoELIDX); + void setDomoVL1IDX(int domoVL1IDX); + void setDomoVL2IDX(int domoVL2IDX); + void setDomoVL3IDX(int domoVL3IDX); + void setDomoCL1IDX(int domoCL1IDX); + void clearDomo(); + + bool isDomoChanged(); + void ackDomoChange(); + protected: private: @@ -198,9 +220,15 @@ private: 0xFF, // Temp sensor 0xFF, // Vcc 100, // Multiplier - 0 // Boot limit + 0, // Boot limit + //Domoticz + 0, // ELIDX + 0, // VL1IDX + 0, // VL2IDX + 0, // VL3IDX + 0 // CL1IDX }; - bool wifiChanged, mqttChanged; + bool wifiChanged, mqttChanged, domoChanged; const int EEPROM_SIZE = 2048; const int EEPROM_CHECK_SUM = 82; // Used to check if config is stored. Change if structure changes diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 98a67840..8921ea64 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -258,6 +258,9 @@ unsigned long lastSuccessfulRead = 0; unsigned long lastErrorBlink = 0; int lastError = 0; +// domoticz energy init +double energy = -1.0; + void loop() { Debug.handle(); unsigned long now = millis(); @@ -514,6 +517,156 @@ void readHanPort() { String msg; serializeJson(json, msg); mqtt.publish(config.getMqttPublishTopic(), msg.c_str()); + // + // Start DOMOTICZ + // + } else if(config.getMqttPayloadFormat() == 3) { + // + // This part is also publishing standard json message for now. May be removed. + // + StaticJsonDocument<512> json; + hanToJson(json, data, hw, temperature); + if (Debug.isActive(RemoteDebug::INFO)) { + debugI("Sending data to MQTT"); + if (Debug.isActive(RemoteDebug::DEBUG)) { + serializeJsonPretty(json, Debug); + } + } + String msg; + serializeJson(json, msg); + mqtt.publish(config.getMqttPublishTopic(), msg.c_str()); // keep for now, this is identical to option 0. + // + // Special MQTT messages for DOMOTIZ (https://www.domoticz.com/wiki/MQTT) + // -All messages should be published to topic "domoticz/in" + // + // message msg_PE : send active power and and cumulative energy consuption to virtual meter "Electricity (instant and counter)" + // + // /json.htm?type=command¶m=udevice&idx=IDX&nvalue=0&svalue=POWER;ENERGY + // + // MQTT sample message: {"command": "udevice", "idx" : IDX , "nvalue" : 0, "svalue" : "POWER;ENERGY"} + // IDX = id of your device (This number can be found in the devices tab in the column "IDX") + // POWER = current power (Watt) + // ENERGY = cumulative energy in Watt-hours (Wh) This is an incrementing counter. + // (if you choose as type "Energy read : Computed", this is just a "dummy" counter, not updatable because it's the result of DomoticZ calculs from POWER) + // + // message msg_V1 : send Voltage of L1 to virtual Voltage meter + // + // /json.htm?type=command¶m=udevice&idx=IDX&nvalue=0&svalue=VOLTAGE + // + // MQTT sample message: {"command": "udevice", "idx" : IDX , "nvalue" : 0, "svalue" : "VOLTAGE"} + // IDX = id of your device (This number can be found in the devices tab in the column "IDX") + // VOLTAGE = Voltage (V) + // + + int idx1 = config.getDomoELIDX(); + if (idx1 > 0) { + String PowerEnergy; + int p; + // double energy = config.getDomoEnergy(); + double tmp_energy; + StaticJsonDocument<200> json_PE; + p = data.getActiveImportPower(); + // cumulative energy is given only once pr hour. check if value is different from 0 and store last valid value on global variable. + tmp_energy = data.getActiveImportCounter(); + if (tmp_energy > 1.0) energy = tmp_energy; + // power_unit: watt, energy_unit: watt*h. Stored as kwh, need watth + PowerEnergy = String((double) p/1.0) + ";" + String((double) energy*1000.0) ; + json_PE["command"] = "udevice"; + json_PE["idx"] = idx1; + json_PE["nvalue"] = 0; + json_PE["svalue"] = PowerEnergy; + // Stringify the json + String msg_PE; + serializeJson(json_PE, msg_PE); + // publish power data directly to domoticz/in, but only after first reading of total power, once an hour... . (otherwise total consumtion will be wrong.) + if (energy > 0.0 ) mqtt.publish("domoticz/in", msg_PE.c_str()); + } + int idxu1 =config.getDomoVL1IDX(); + if (idxu1 > 0){ + StaticJsonDocument<200> json_u1; + double u1; + // + // prepare message msg_u1 for virtual Voltage meter" + // + u1 = data.getL1Voltage(); + if (u1 > 0.1){ + json_u1["command"] = "udevice"; + json_u1["idx"] = idxu1; + json_u1["nvalue"] = 0; + json_u1["svalue"] = String(u1); + // Stringify the json + String msg_u1; + serializeJson(json_u1, msg_u1); + // publish power data directly to domoticz/in + mqtt.publish("domoticz/in", msg_u1.c_str()); + } + } + int idxu2 =config.getDomoVL2IDX(); + if (idxu2 > 0){ + StaticJsonDocument<200> json_u2; + double u2; + // + // prepare message msg_u2 for virtual Voltage meter" + // + u2 = data.getL2Voltage(); + if (u2 > 0.1){ + json_u2["command"] = "udevice"; + json_u2["idx"] = idxu2; + json_u2["nvalue"] = 0; + json_u2["svalue"] = String(u2); + // Stringify the json + String msg_u2; + serializeJson(json_u2, msg_u2); + // publish power data directly to domoticz/in + mqtt.publish("domoticz/in", msg_u2.c_str()); + } + } + int idxu3 =config.getDomoVL3IDX(); + if (idxu3 > 0){ + StaticJsonDocument<200> json_u3; + double u3; + // + // prepare message msg_u3 for virtual Voltage meter" + // + u3 = data.getL3Voltage(); + if (u3 > 0.1){ + json_u3["command"] = "udevice"; + json_u3["idx"] = idxu3; + json_u3["nvalue"] = 0; + json_u3["svalue"] = String(u3); + // Stringify the json + String msg_u3; + serializeJson(json_u3, msg_u3); + // publish power data directly to domoticz/in + mqtt.publish("domoticz/in", msg_u3.c_str()); + } + } + + int idxi1 =config.getDomoCL1IDX(); + if (idxi1 > 0){ + StaticJsonDocument<200> json_i1; + double i1, i2, i3; + String Ampere3; + // + // prepare message msg_i1 for virtual Current/Ampere 3phase mater" + // + i1 = data.getL1Current(); + i2 = data.getL2Current(); + i3 = data.getL3Current(); + Ampere3 = String(i1) + ";" + String(i2) + ";" + String(i3) ; + json_i1["command"] = "udevice"; + json_i1["idx"] = idxi1; + json_i1["nvalue"] = 0; + json_i1["svalue"] = Ampere3; + // Stringify the json + String msg_i1; + serializeJson(json_i1, msg_i1); + // publish power data directly to domoticz/in + if (i1 > 0.0) mqtt.publish("domoticz/in", msg_i1.c_str()); + } + // + // End DOMOTICZ + // } else if(config.getMqttPayloadFormat() == 1 || config.getMqttPayloadFormat() == 2) { mqtt.publish(String(config.getMqttPublishTopic()) + "/meter/dlms/timestamp", String(data.getPackageTimestamp())); switch(data.getListType()) { diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index 5e106905..89a38af7 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -9,6 +9,7 @@ #include "root/configwifi_html.h" #include "root/configmqtt_html.h" #include "root/configweb_html.h" +#include "root/configdomoticz_html.h" #include "root/configsystem_html.h" #include "root/restartwait_html.h" #include "root/boot_css.h" @@ -34,6 +35,7 @@ void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) { server.on("/config-wifi", HTTP_GET, std::bind(&AmsWebServer::configWifiHtml, this)); server.on("/config-mqtt", HTTP_GET, std::bind(&AmsWebServer::configMqttHtml, this)); server.on("/config-web", HTTP_GET, std::bind(&AmsWebServer::configWebHtml, this)); + server.on("/config-domoticz",HTTP_GET, std::bind(&AmsWebServer::configDomoticzHtml, this)); server.on("/boot.css", HTTP_GET, std::bind(&AmsWebServer::bootCss, this)); server.on("/gaugemeter.js", HTTP_GET, std::bind(&AmsWebServer::gaugemeterJs, this)); server.on("/data.json", HTTP_GET, std::bind(&AmsWebServer::dataJson, this)); @@ -268,7 +270,7 @@ void AmsWebServer::configMqttHtml() { html.replace("${config.mqttUser}", config->getMqttUser()); html.replace("${config.mqttPassword}", config->getMqttPassword()); html.replace("${config.mqttPayloadFormat}", String(config->getMqttPayloadFormat())); - for(int i = 0; i<3; i++) { + for(int i = 0; i<4; i++) { html.replace("${config.mqttPayloadFormat" + String(i) + "}", config->getMqttPayloadFormat() == i ? "selected" : ""); } @@ -296,6 +298,38 @@ void AmsWebServer::configMqttHtml() { server.send(200, "text/html", html); } +void AmsWebServer::configDomoticzHtml() { + printD("Serving /config/domoticz.html over http..."); + + if(!checkSecurity(1)) + return; + + String html = String((const __FlashStringHelper*) CONFIGDOMOTICZ_HTML); + html.replace("${version}", VERSION); + + if(WiFi.getMode() != WIFI_AP) { + html.replace("boot.css", BOOTSTRAP_URL); + } + + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server.sendHeader("Pragma", "no-cache"); + + html.replace("${config.domo}", config->getDomoELIDX() <= 0 ? "" : "checked"); + if(config->getDomoELIDX() > 0){ html.replace("${config.domoELIDX}", String(config->getDomoELIDX())); + } else { html.replace("${config.domoELIDX}", ""); } + if(config->getDomoVL1IDX() > 0){ html.replace("${config.domoVL1IDX}", String(config->getDomoVL1IDX())); + } else { html.replace("${config.domoVL1IDX}", ""); } + if(config->getDomoVL2IDX() > 0){ html.replace("${config.domoVL2IDX}", String(config->getDomoVL2IDX())); + } else { html.replace("${config.domoVL2IDX}", ""); } + if(config->getDomoVL3IDX() > 0){ html.replace("${config.domoVL3IDX}", String(config->getDomoVL3IDX())); + } else { html.replace("${config.domoVL3IDX}", ""); } + if(config->getDomoCL1IDX() > 0){ html.replace("${config.domoCL1IDX}", String(config->getDomoCL1IDX())); + } else { html.replace("${config.domoCL1IDX}", ""); } + server.setContentLength(html.length()); + server.send(200, "text/html", html); +} + + void AmsWebServer::configWebHtml() { printD("Serving /config-web.html over http..."); @@ -471,6 +505,18 @@ void AmsWebServer::dataJson() { json.createNestedObject("mqtt"); json["mqtt"]["lastError"] = (int) mqtt->lastError(); + String domoStatus; + if(String(config->getDomoELIDX()).isEmpty()) { + domoStatus = "secondary"; + } else if(mqtt->connected() && config->getMqttPayloadFormat() == 3 && config->getDomoELIDX() > 0) { + domoStatus = "success"; + } else if(mqtt->lastError() == 0) { + domoStatus = "warning"; + } else { + domoStatus = "danger"; + } + json["status"]["domo"] = domoStatus; + serializeJson(json, jsonStr); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); @@ -617,6 +663,20 @@ void AmsWebServer::handleSave() { } } + if(server.hasArg("domoConfig") && server.arg("domoConfig") == "true") { + if(server.hasArg("domo") && server.arg("domo") == "true") { + config->setDomoELIDX(server.arg("domoELIDX").toInt()); + config->setDomoVL1IDX(server.arg("domoVL1IDX").toInt()); + config->setDomoVL2IDX(server.arg("domoVL2IDX").toInt()); + config->setDomoVL3IDX(server.arg("domoVL3IDX").toInt()); + config->setDomoCL1IDX(server.arg("domoCL1IDX").toInt()); + } else { + config->clearDomo(); + } + } + + + if(server.hasArg("authConfig") && server.arg("authConfig") == "true") { config->setAuthSecurity((byte)server.arg("authSecurity").toInt()); if(config->getAuthSecurity() > 0) { diff --git a/src/web/AmsWebServer.h b/src/web/AmsWebServer.h index 57ad63c6..8f40151f 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -61,6 +61,7 @@ private: void configWifiHtml(); void configMqttHtml(); void configWebHtml(); + void configDomoticzHtml(); void bootCss(); void gaugemeterJs(); void dataJson(); diff --git a/web/configdomoticz.html b/web/configdomoticz.html new file mode 100644 index 00000000..07c3ef9e --- /dev/null +++ b/web/configdomoticz.html @@ -0,0 +1,120 @@ + +
+ + +