From f67f12a1888d290c287ee19acdd658a407ecf1c9 Mon Sep 17 00:00:00 2001 From: Sten Otto Johnsen Date: Fri, 24 Apr 2020 00:05:12 +0200 Subject: [PATCH 1/4] Adding full report on raw data --- src/AmsConfiguration.h | 2 +- src/AmsToMqttBridge.ino | 26 +++++++++++++------------- src/web/AmsWebServer.cpp | 2 +- web/configmqtt.html | 3 ++- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index c2991cb6..afe60992 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -114,7 +114,7 @@ private: int debugLevel = 3; const int EEPROM_SIZE = 512; - const int EEPROM_CHECK_SUM = 81; // Used to check if config is stored. Change if structure changes + const int EEPROM_CHECK_SUM = 82; // Used to check if config is stored. Change if structure changes const int EEPROM_CONFIG_ADDRESS = 0; bool loadConfig72(int address); diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index aee17ecc..85932da7 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -462,7 +462,7 @@ void readHanPort() { String msg; serializeJson(json, msg); mqtt.publish(config.getMqttPublishTopic(), msg.c_str()); - } else if(config.getMqttPayloadFormat() == 1) { + } else if(config.getMqttPayloadFormat() == 1 || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/dlms/timestamp", String(data.getPackageTimestamp())); switch(data.getListType()) { case 3: @@ -476,41 +476,41 @@ void readHanPort() { mqtt.publish(config.getMqttPublishTopic() + "/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(lastMqttData.getMeterId() != data.getMeterId()) { + if(lastMqttData.getMeterId() != data.getMeterId() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/id", data.getMeterId()); } - if(lastMqttData.getMeterType() != data.getMeterType()) { + if(lastMqttData.getMeterType() != data.getMeterType() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/type", data.getMeterType()); } - if(lastMqttData.getL1Current() != data.getL1Current()) { + if(lastMqttData.getL1Current() != data.getL1Current() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/l1/current", String(data.getL1Current(), 2)); } - if(lastMqttData.getL1Voltage() != data.getL1Voltage()) { + if(lastMqttData.getL1Voltage() != data.getL1Voltage() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/l1/voltage", String(data.getL1Voltage(), 2)); } - if(lastMqttData.getL2Current() != data.getL2Current()) { + if(lastMqttData.getL2Current() != data.getL2Current() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/l2/current", String(data.getL2Current(), 2)); } - if(lastMqttData.getL2Voltage() != data.getL2Voltage()) { + if(lastMqttData.getL2Voltage() != data.getL2Voltage() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/l2/voltage", String(data.getL2Voltage(), 2)); } - if(lastMqttData.getL3Current() != data.getL3Current()) { + if(lastMqttData.getL3Current() != data.getL3Current() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/l3/current", String(data.getL3Current(), 2)); } - if(lastMqttData.getL3Voltage() != data.getL3Voltage()) { + if(lastMqttData.getL3Voltage() != data.getL3Voltage() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/l3/voltage", String(data.getL3Voltage(), 2)); } - if(lastMqttData.getReactiveExportPower() != data.getReactiveExportPower()) { + if(lastMqttData.getReactiveExportPower() != data.getReactiveExportPower() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/export/reactive", String(data.getReactiveExportPower())); } - if(lastMqttData.getActiveExportPower() != data.getActiveExportPower()) { + if(lastMqttData.getActiveExportPower() != data.getActiveExportPower() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/export/active", String(data.getActiveExportPower())); } - if(lastMqttData.getReactiveImportPower() != data.getReactiveImportPower()) { + if(lastMqttData.getReactiveImportPower() != data.getReactiveImportPower() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/import/reactive", String(data.getReactiveImportPower())); } case 1: - if(lastMqttData.getActiveImportPower() != data.getActiveImportPower()) { + if(lastMqttData.getActiveImportPower() != data.getActiveImportPower() || config.getMqttPayloadFormat() == 2) { mqtt.publish(config.getMqttPublishTopic() + "/meter/import/active", String(data.getActiveImportPower())); } } diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index b5c6d8c9..9d1cbf45 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -248,7 +248,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<2; i++) { + for(int i = 0; i<3; i++) { html.replace("${config.mqttPayloadFormat" + String(i) + "}", config->getMqttPayloadFormat() == i ? "selected" : ""); } diff --git a/web/configmqtt.html b/web/configmqtt.html index 29fdcc12..e507fb5f 100644 --- a/web/configmqtt.html +++ b/web/configmqtt.html @@ -54,7 +54,8 @@
From f696e0b59bbe72be3c7ca1fd64dc46ddae1f4cc7 Mon Sep 17 00:00:00 2001 From: Sten Otto Johnsen Date: Fri, 24 Apr 2020 12:15:25 +0200 Subject: [PATCH 2/4] Removed checksum change --- src/AmsConfiguration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index afe60992..c2991cb6 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -114,7 +114,7 @@ private: int debugLevel = 3; const int EEPROM_SIZE = 512; - const int EEPROM_CHECK_SUM = 82; // Used to check if config is stored. Change if structure changes + const int EEPROM_CHECK_SUM = 81; // Used to check if config is stored. Change if structure changes const int EEPROM_CONFIG_ADDRESS = 0; bool loadConfig72(int address); From cc032fdf298cf934e35f661558e66587f2b6a7c8 Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Wed, 29 Apr 2020 21:04:42 +0200 Subject: [PATCH 3/4] Implemented upload of certificates for MQTT SSL --- src/AmsConfiguration.cpp | 109 +++++++++++++++++++++++ src/AmsConfiguration.h | 6 +- src/AmsToMqttBridge.h | 1 + src/AmsToMqttBridge.ino | 82 ++++++++++++++++- src/web/AmsWebServer.cpp | 186 ++++++++++++++++++++++++++++++++++++++- src/web/AmsWebServer.h | 15 +++- web/configmqtt.html | 43 +++++++++ web/upload.html | 66 ++++++++++++++ 8 files changed, 501 insertions(+), 7 deletions(-) create mode 100644 web/upload.html diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index 971ccf5f..63b09317 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -160,6 +160,15 @@ void AmsConfiguration::setMqttPayloadFormat(int mqttPayloadFormat) { this->mqttPayloadFormat = mqttPayloadFormat; } +bool AmsConfiguration::isMqttSsl() { + return this->mqttSsl; +} + +void AmsConfiguration::setMqttSsl(bool mqttSsl) { + mqttChanged |= this->mqttSsl != mqttSsl; + this->mqttSsl = mqttSsl; +} + void AmsConfiguration::clearMqtt() { setMqttHost(""); setMqttPort(1883); @@ -278,6 +287,7 @@ bool AmsConfiguration::hasConfig() { case 75: case 80: case 81: + case 82: return true; default: configVersion = 0; @@ -310,6 +320,9 @@ bool AmsConfiguration::load() { case 81: success = loadConfig81(address); break; + case 82: + success = loadConfig82(address); + break; } EEPROM.end(); return success; @@ -608,6 +621,101 @@ bool AmsConfiguration::loadConfig81(int address) { return true; } +bool AmsConfiguration::loadConfig82(int address) { + char* temp; + + address += readString(address, &temp); + setWifiSsid(temp); + address += readString(address, &temp); + setWifiPassword(temp); + + bool staticIp = false; + address += readBool(address, &staticIp); + if(staticIp) { + address += readString(address, &temp); + setWifiIp(temp); + address += readString(address, &temp); + setWifiGw(temp); + address += readString(address, &temp); + setWifiSubnet(temp); + address += readString(address, &temp); + setWifiDns1(temp); + address += readString(address, &temp); + setWifiDns2(temp); + } + address += readString(address, &temp); + setWifiHostname(temp); + bool mqtt = false; + address += readBool(address, &mqtt); + if(mqtt) { + address += readString(address, &temp); + setMqttHost(temp); + int port; + address += readInt(address, &port); + setMqttPort(port); + address += readString(address, &temp); + setMqttClientId(temp); + address += readString(address, &temp); + setMqttPublishTopic(temp); + address += readString(address, &temp); + setMqttSubscribeTopic(temp); + + bool secure = false; + address += readBool(address, &secure); + if (secure) + { + address += readString(address, &temp); + setMqttUser(temp); + address += readString(address, &temp); + setMqttPassword(temp); + } else { + setMqttUser(""); + setMqttPassword(""); + } + int payloadFormat; + address += readInt(address, &payloadFormat); + setMqttPayloadFormat(payloadFormat); + bool ssl = false; + address += readBool(address, &ssl); + setMqttSsl(ssl); + } else { + clearMqtt(); + } + + address += readByte(address, &authSecurity); + if (authSecurity > 0) { + address += readString(address, &temp); + setAuthUser(temp); + address += readString(address, &temp); + setAuthPassword(temp); + } else { + clearAuth(); + } + + int i; + address += readInt(address, &i); + setMeterType(i); + address += readInt(address, &i); + setDistributionSystem(i); + address += readInt(address, &i); + setMainFuse(i); + address += readInt(address, &i); + setProductionCapacity(i); + + bool debugTelnet = false; + address += readBool(address, &debugTelnet); + setDebugTelnet(debugTelnet); + bool debugSerial = false; + address += readBool(address, &debugSerial); + setDebugSerial(debugSerial); + address += readInt(address, &i); + setDebugLevel(i); + + ackWifiChange(); + + return true; +} + bool AmsConfiguration::save() { int address = EEPROM_CONFIG_ADDRESS; @@ -643,6 +751,7 @@ bool AmsConfiguration::save() { address += saveBool(address, false); } address += saveInt(address, mqttPayloadFormat); + address += saveBool(address, mqttSsl); } else { address += saveBool(address, false); } diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index c2991cb6..68591466 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -48,6 +48,8 @@ public: void setMqttPassword(String mqttPassword); int getMqttPayloadFormat(); void setMqttPayloadFormat(int mqttPayloadFormat); + bool isMqttSsl(); + void setMqttSsl(bool mqttSsl); void clearMqtt(); bool isMqttChanged(); @@ -102,6 +104,7 @@ private: String mqttUser; String mqttPassword; int mqttPayloadFormat = 0; + bool mqttSsl; bool mqttChanged = false; byte authSecurity; @@ -114,13 +117,14 @@ private: int debugLevel = 3; const int EEPROM_SIZE = 512; - const int EEPROM_CHECK_SUM = 81; // Used to check if config is stored. Change if structure changes + const int EEPROM_CHECK_SUM = 82; // Used to check if config is stored. Change if structure changes const int EEPROM_CONFIG_ADDRESS = 0; bool loadConfig72(int address); bool loadConfig75(int address); bool loadConfig80(int address); bool loadConfig81(int address); + bool loadConfig82(int address); int saveString(int pAddress, const char* pString); int readString(int pAddress, char* pString[]); diff --git a/src/AmsToMqttBridge.h b/src/AmsToMqttBridge.h index a88fce86..1d60906f 100644 --- a/src/AmsToMqttBridge.h +++ b/src/AmsToMqttBridge.h @@ -11,6 +11,7 @@ #include #elif defined(ESP32) #include +#include #include #include "SPIFFS.h" #include "Update.h" diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index aee17ecc..7cb9d811 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -52,7 +52,6 @@ RemoteDebug Debug; AmsWebServer ws(&Debug); -WiFiClient *client; MQTTClient mqtt(512); HanReader hanReader; @@ -188,7 +187,6 @@ void setup() { if(config.hasConfig()) { if(Debug.isActive(RemoteDebug::INFO)) config.print(&Debug); WiFi_connect(); - client = new WiFiClient(); } else { if(Debug.isActive(RemoteDebug::INFO)) { debugI("No configuration, booting AP"); @@ -635,6 +633,85 @@ void MQTT_connect() { mqtt.disconnect(); yield(); + Client *client; + if(config.isMqttSsl()) { + debugI("MQTT SSL is configured"); + WiFiClientSecure *secureClient = new WiFiClientSecure(); + + bool spiffs = false; +#if defined(ESP32) + debugD("ESP32 SPIFFS"); + spiffs = SPIFFS.begin(true); +#else + debugD("ESP8266 SPIFFS"); + spiffs = SPIFFS.begin(); +#endif + + bool caExists = false; + if(spiffs) { + char *ca = NULL; + char *cert = NULL; + char *key = NULL; + if(SPIFFS.exists("/mqtt-ca.cer")) { + debugI("Found MQTT CA file"); + File file = SPIFFS.open("/mqtt-ca.cer", "r"); + ca = new char[file.size()]; + if (file.size() != file.readBytes(ca, file.size())) { + delete ca; + ca = NULL; + } + } + if(SPIFFS.exists("/mqtt-cert.cer")) { + debugI("Found MQTT certificate file"); + File file = SPIFFS.open("/mqtt-cert.cer", "r"); + cert = new char[file.size()]; + if (file.size() != file.readBytes(cert, file.size())) { + delete cert; + cert = NULL; + } + } + if(SPIFFS.exists("/mqtt-key.cer")) { + debugI("Found MQTT key file"); + File file = SPIFFS.open("/mqtt-key.cer", "r"); + key = new char[file.size()]; + if (file.size() != file.readBytes(key, file.size())) { + delete key; + key = NULL; + } + } + SPIFFS.end(); + + if(ca) { +#ifdef ESP32 + secureClient->setCACert(ca); +#else + secureClient->setTrustAnchors(new X509List(ca)); + secureClient->allowSelfSignedCerts(); +#endif + caExists = true; + } + if(cert && key) { +#ifdef ESP32 + secureClient->setCertificate(cert); + secureClient->setPrivateKey(key); +#else + secureClient->setClientRSACert(new X509List(cert), new PrivateKey(key)); +#endif + } + } + if(!caExists) { + debugW("No CA found, using insecure"); +#ifdef ESP32 + // TODO +#else + secureClient->setInsecure(); +#endif + } + client = secureClient; + } else { + client = new WiFiClient(); + } + mqtt.begin(config.getMqttHost().c_str(), config.getMqttPort(), *client); // Connect to a unsecure or secure MQTT server @@ -655,6 +732,7 @@ void MQTT_connect() { sendSystemStatusToMqtt(); } } else { + lastMqttRetry = millis() + 30000; if (Debug.isActive(RemoteDebug::ERROR)) { debugI("Failed to connect to MQTT"); } diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index b5c6d8c9..9f2056f0 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -10,6 +10,7 @@ #include "root/restartwait_html.h" #include "root/boot_css.h" #include "root/gaugemeter_js.h" +#include "root/upload_html.h" #include "Base64.h" @@ -30,13 +31,20 @@ void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) { server.on("/gaugemeter.js", HTTP_GET, std::bind(&AmsWebServer::gaugemeterJs, this)); server.on("/data.json", HTTP_GET, std::bind(&AmsWebServer::dataJson, this)); - server.on("/save", std::bind(&AmsWebServer::handleSave, this)); + server.on("/save", HTTP_POST, std::bind(&AmsWebServer::handleSave, this)); server.on("/config-system", HTTP_GET, std::bind(&AmsWebServer::configSystemHtml, this)); - server.on("/config-system", HTTP_POST, std::bind(&AmsWebServer::configSystemPost, this), std::bind(&AmsWebServer::configSystemUpload, this)); + server.on("/config-system", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::configSystemUpload, this)); server.on("/restart-wait", HTTP_GET, std::bind(&AmsWebServer::restartWaitHtml, this)); server.on("/is-alive", HTTP_GET, std::bind(&AmsWebServer::isAliveCheck, this)); + server.on("/mqtt-ca", HTTP_GET, std::bind(&AmsWebServer::mqttCa, this)); + server.on("/mqtt-ca", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttCaUpload, this)); + server.on("/mqtt-cert", HTTP_GET, std::bind(&AmsWebServer::mqttCert, this)); + server.on("/mqtt-cert", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttCertUpload, this)); + server.on("/mqtt-key", HTTP_GET, std::bind(&AmsWebServer::mqttKey, this)); + server.on("/mqtt-key", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttKeyUpload, this)); + server.begin(); // Web server start } @@ -252,6 +260,26 @@ void AmsWebServer::configMqttHtml() { html.replace("${config.mqttPayloadFormat" + String(i) + "}", config->getMqttPayloadFormat() == i ? "selected" : ""); } + html.replace("${config.mqttSsl}", config->isMqttSsl() ? "checked" : ""); + html.replace("${display.ssl}", config->isMqttSsl() ? "" : "none"); + + if(SPIFFS.begin()) { + html.replace("${display.ca.upload}", SPIFFS.exists("/mqtt-ca.pem") ? "none" : ""); + html.replace("${display.ca.file}", SPIFFS.exists("/mqtt-ca.pem") ? "" : "none"); + html.replace("${display.cert.upload}", SPIFFS.exists("/mqtt-cert.pem") ? "none" : ""); + html.replace("${display.cert.file}", SPIFFS.exists("/mqtt-cert.pem") ? "" : "none"); + html.replace("${display.key.upload}", SPIFFS.exists("/mqtt-key.pem") ? "none" : ""); + html.replace("${display.key.file}", SPIFFS.exists("/mqtt-key.pem") ? "" : "none"); + SPIFFS.end(); + } else { + html.replace("${display.ca.upload}", ""); + html.replace("${display.ca.file}", "none"); + html.replace("${display.cert.upload}", ""); + html.replace("${display.cert.file}", "none"); + html.replace("${display.key.upload}", ""); + html.replace("${display.key.file}", "none"); + } + server.setContentLength(html.length()); server.send(200, "text/html", html); } @@ -489,6 +517,7 @@ void AmsWebServer::handleSave() { config->setMqttUser(server.arg("mqttUser")); config->setMqttPassword(server.arg("mqttPassword")); config->setMqttPayloadFormat(server.arg("mqttPayloadFormat").toInt()); + config->setMqttSsl(server.arg("mqttSsl") == "true"); } else { config->clearMqtt(); } @@ -571,7 +600,7 @@ void AmsWebServer::configSystemHtml() { server.send(200, "text/html", html); } -void AmsWebServer::configSystemPost() { +void AmsWebServer::uploadPost() { server.send(200); } @@ -651,6 +680,157 @@ void AmsWebServer::isAliveCheck() { server.send(200); } +void AmsWebServer::mqttCa() { + printD("Serving /mqtt-ca.html over http..."); + + String html = String((const __FlashStringHelper*) UPLOAD_HTML); + html.replace("${menu.meter.class}", ""); + html.replace("${menu.wifi.class}", ""); + html.replace("${menu.mqtt.class}", "active"); + html.replace("${menu.web.class}", ""); + html.replace("${menu.system.class}", ""); + html.replace("${file.label}", "CA file"); + + server.sendHeader("Cache-Control", "public, max-age=3600"); + + server.setContentLength(html.length()); + server.send(200, "text/html", html); +} + +void AmsWebServer::mqttCaUpload() { + HTTPUpload& upload = server.upload(); + if(upload.status == UPLOAD_FILE_START){ + String filename = upload.filename; + if (!SPIFFS.begin()) { + printE("An Error has occurred while mounting SPIFFS"); + String html = "

Error uploading!

"; + server.send(500, "text/html", html); + } else { + printD("handleFileUpload Name: %s", filename.c_str()); + mqttCaFile = SPIFFS.open("/mqtt-ca.pem", "w"); + filename = String(); + } + } else if(upload.status == UPLOAD_FILE_WRITE) { + if(mqttCaFile) + mqttCaFile.write(upload.buf, upload.currentSize); + } else if(upload.status == UPLOAD_FILE_END) { + if(mqttCaFile) { + mqttCaFile.close(); + SPIFFS.end(); + printD("handleFileUpload Size: %d", upload.totalSize); + server.sendHeader("Location","/config-mqtt"); + server.send(303); + } else { + server.send(500, "text/plain", "500: couldn't create file"); + } + } +} + +void AmsWebServer::mqttCaDelete() { + +} + +void AmsWebServer::mqttCert() { + printD("Serving /mqtt-cert.html over http..."); + + String html = String((const __FlashStringHelper*) UPLOAD_HTML); + html.replace("${menu.meter.class}", ""); + html.replace("${menu.wifi.class}", ""); + html.replace("${menu.mqtt.class}", "active"); + html.replace("${menu.web.class}", ""); + html.replace("${menu.system.class}", ""); + html.replace("${file.label}", "Certificate"); + + server.sendHeader("Cache-Control", "public, max-age=3600"); + + server.setContentLength(html.length()); + server.send(200, "text/html", html); +} + +void AmsWebServer::mqttCertUpload() { + HTTPUpload& upload = server.upload(); + if(upload.status == UPLOAD_FILE_START){ + String filename = upload.filename; + if (!SPIFFS.begin()) { + printE("An Error has occurred while mounting SPIFFS"); + String html = "

Error uploading!

"; + server.send(500, "text/html", html); + } else { + printD("handleFileUpload Name: %s", filename.c_str()); + mqttCertFile = SPIFFS.open("/mqtt-cert.pem", "w"); + filename = String(); + } + } else if(upload.status == UPLOAD_FILE_WRITE) { + if(mqttCertFile) + mqttCertFile.write(upload.buf, upload.currentSize); + } else if(upload.status == UPLOAD_FILE_END) { + if(mqttCertFile) { + mqttCertFile.close(); + SPIFFS.end(); + printD("handleFileUpload Size: %d", upload.totalSize); + server.sendHeader("Location","/config-mqtt"); + server.send(303); + } else { + server.send(500, "text/plain", "500: couldn't create file"); + } + } +} + +void AmsWebServer::mqttCertDelete() { + +} + +void AmsWebServer::mqttKey() { + printD("Serving /mqtt-key.html over http..."); + + String html = String((const __FlashStringHelper*) UPLOAD_HTML); + html.replace("${menu.meter.class}", ""); + html.replace("${menu.wifi.class}", ""); + html.replace("${menu.mqtt.class}", "active"); + html.replace("${menu.web.class}", ""); + html.replace("${menu.system.class}", ""); + html.replace("${file.label}", "Private key"); + + server.sendHeader("Cache-Control", "public, max-age=3600"); + + server.setContentLength(html.length()); + server.send(200, "text/html", html); +} + +void AmsWebServer::mqttKeyUpload() { + HTTPUpload& upload = server.upload(); + if(upload.status == UPLOAD_FILE_START){ + String filename = upload.filename; + if (!SPIFFS.begin()) { + printE("An Error has occurred while mounting SPIFFS"); + String html = "

Error uploading!

"; + server.send(500, "text/html", html); + } else { + printD("handleFileUpload Name: %s", filename.c_str()); + mqttKeyFile = SPIFFS.open("/mqtt-key.pem", "w"); + filename = String(); + } + } else if(upload.status == UPLOAD_FILE_WRITE) { + if(mqttKeyFile) + mqttKeyFile.write(upload.buf, upload.currentSize); + } else if(upload.status == UPLOAD_FILE_END) { + if(mqttKeyFile) { + mqttKeyFile.close(); + SPIFFS.end(); + printD("handleFileUpload Size: %d", upload.totalSize); + server.sendHeader("Location","/config-mqtt"); + server.send(303); + } else { + server.send(500, "text/plain", "500: couldn't create file"); + } + } +} + +void AmsWebServer::mqttKeyDelete() { + +} + + void AmsWebServer::printD(String fmt, ...) { va_list args; va_start(args, fmt); diff --git a/src/web/AmsWebServer.h b/src/web/AmsWebServer.h index da221b7b..5f62f10a 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -45,6 +45,9 @@ private: AmsData data; MQTTClient* mqtt; File firmwareFile; + File mqttCaFile; + File mqttCertFile; + File mqttKeyFile; bool performRestart = false; #if defined(ESP8266) @@ -67,11 +70,21 @@ private: void handleSave(); void configSystemHtml(); - void configSystemPost(); void configSystemUpload(); void restartWaitHtml(); void isAliveCheck(); + void uploadPost(); + void mqttCa(); + void mqttCaUpload(); + void mqttCaDelete(); + void mqttCert(); + void mqttCertUpload(); + void mqttCertDelete(); + void mqttKey(); + void mqttKeyUpload(); + void mqttKeyDelete(); + void printD(String fmt, ...); void printI(String fmt, ...); void printW(String fmt, ...); diff --git a/web/configmqtt.html b/web/configmqtt.html index 29fdcc12..75417d15 100644 --- a/web/configmqtt.html +++ b/web/configmqtt.html @@ -102,6 +102,49 @@ +
+
+
+ +
+ +
+
+
+
+
+ +
+ Upload + + Delete + +
+
+
+
+
+ +
+ Upload + + Delete + +
+
+
+
+
+ +
+ Upload + + Delete + +
+
+
+

diff --git a/web/upload.html b/web/upload.html new file mode 100644 index 00000000..1d0963eb --- /dev/null +++ b/web/upload.html @@ -0,0 +1,66 @@ + + + + + AMS reader - Meter configuration + + + + + +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ Back +
+
+ +
+
+
+
+ + From 0c93c52e3dce8a3260199b9c78a6ccf26fc104c5 Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Fri, 1 May 2020 12:10:08 +0200 Subject: [PATCH 4/4] Implemented support for MQTT SSL and cleaned up necessary code --- platformio.ini | 2 +- src/AmsConfiguration.cpp | 4 + src/AmsConfiguration.h | 1 + src/AmsStorage.h | 10 ++ src/AmsToMqttBridge.ino | 104 +++++-------- src/web/AmsWebServer.cpp | 316 ++++++++++++++++++++++----------------- src/web/AmsWebServer.h | 10 +- web/boot.css | 11 ++ web/configmqtt.html | 22 ++- web/delete.html | 56 +++++++ web/index.html | 159 +------------------- web/index.js | 159 ++++++++++++++++++++ web/upload.html | 1 - 13 files changed, 482 insertions(+), 373 deletions(-) create mode 100644 src/AmsStorage.h create mode 100644 web/delete.html create mode 100644 web/index.js diff --git a/platformio.ini b/platformio.ini index f31f3d8a..b357a037 100755 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ extra_configs = platformio-user.ini [common] framework = arduino -lib_deps = HanReader@1.0.1, ArduinoJson@6.14.1, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, Base64@1.0.0, RemoteDebug@3.0.5, Time@1.6 +lib_deps = HanReader@1.0.1, ArduinoJson@6.14.1, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, Base64@1.0.0, RemoteDebug@3.0.5, Time@1.6, NTPClient@3.1.0 [env:hw1esp12e] platform = espressif8266@2.3.3 diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index 63b09317..b64ec19f 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -179,6 +179,10 @@ void AmsConfiguration::clearMqtt() { setMqttPassword(""); } +void AmsConfiguration::setMqttChanged() { + mqttChanged = true; +} + bool AmsConfiguration::isMqttChanged() { return mqttChanged; } diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index 68591466..e25638e5 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -52,6 +52,7 @@ public: void setMqttSsl(bool mqttSsl); void clearMqtt(); + void setMqttChanged(); bool isMqttChanged(); void ackMqttChange(); diff --git a/src/AmsStorage.h b/src/AmsStorage.h new file mode 100644 index 00000000..51194c87 --- /dev/null +++ b/src/AmsStorage.h @@ -0,0 +1,10 @@ +#ifndef _AMSSTORAGE_H +#define _AMSSTORAGE_H + +#define FILE_FIRMWARE "/firmware.bin" + +#define FILE_MQTT_CA "/mqtt-ca.pem" +#define FILE_MQTT_CERT "/mqtt-cert.pem" +#define FILE_MQTT_KEY "/mqtt-key.pem" + +#endif diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 7cb9d811..5029001d 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -17,10 +17,12 @@ */ #include "AmsToMqttBridge.h" +#include "AmsStorage.h" #define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e9 #include #include #include +#include #if defined(ESP8266) ADC_MODE(ADC_VCC); @@ -46,6 +48,9 @@ HwTools hw; DNSServer dnsServer; +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 60000); + AmsConfiguration config; RemoteDebug Debug; @@ -121,7 +126,7 @@ void setup() { if(spiffs) { bool flashed = false; - if(SPIFFS.exists("/firmware.bin")) { + if(SPIFFS.exists(FILE_FIRMWARE)) { if(Debug.isActive(RemoteDebug::INFO)) debugI("Found firmware"); #if defined(ESP8266) WiFi.setSleepMode(WIFI_LIGHT_SLEEP); @@ -140,7 +145,7 @@ void setup() { } if(Debug.isActive(RemoteDebug::INFO)) debugI(" flashing"); - File firmwareFile = SPIFFS.open("/firmware.bin", "r"); + File firmwareFile = SPIFFS.open(FILE_FIRMWARE, "r"); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace, U_FLASH)) { if(Debug.isActive(RemoteDebug::ERROR)) { @@ -156,7 +161,7 @@ void setup() { flashed = Update.end(true); } firmwareFile.close(); - SPIFFS.remove("/firmware.bin"); + SPIFFS.remove(FILE_FIRMWARE); } SPIFFS.end(); if(flashed) { @@ -227,6 +232,7 @@ void setup() { } ws.setup(&config, &mqtt); + timeClient.begin(); #if HAS_RGB_LED //Signal startup by blinking red / green / yellow @@ -290,6 +296,8 @@ void loop() { errorBlink(); } + timeClient.update(); + // Only do normal stuff if we're not booted as AP if (WiFi.getMode() != WIFI_AP) { led_off(); @@ -621,7 +629,7 @@ void MQTT_connect() { if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config"); return; } - if(millis() - lastMqttRetry < 5000) { + if(millis() - lastMqttRetry < (mqtt.lastError() == 0 ? 5000 : 60000)) { yield(); return; } @@ -633,79 +641,39 @@ void MQTT_connect() { mqtt.disconnect(); yield(); + WiFiClientSecure *secureClient; Client *client; if(config.isMqttSsl()) { debugI("MQTT SSL is configured"); - WiFiClientSecure *secureClient = new WiFiClientSecure(); - bool spiffs = false; -#if defined(ESP32) - debugD("ESP32 SPIFFS"); - spiffs = SPIFFS.begin(true); -#else - debugD("ESP8266 SPIFFS"); - spiffs = SPIFFS.begin(); + if(!timeClient.update()) debugW("NTP time is not ready"); + + secureClient = new WiFiClientSecure(); +#if defined(ESP8266) + secureClient->setBufferSizes(512, 512); #endif - bool caExists = false; - if(spiffs) { + if(SPIFFS.begin()) { char *ca = NULL; char *cert = NULL; char *key = NULL; - if(SPIFFS.exists("/mqtt-ca.cer")) { + + if(SPIFFS.exists(FILE_MQTT_CA)) { debugI("Found MQTT CA file"); - File file = SPIFFS.open("/mqtt-ca.cer", "r"); - ca = new char[file.size()]; - if (file.size() != file.readBytes(ca, file.size())) { - delete ca; - ca = NULL; - } + File file = SPIFFS.open(FILE_MQTT_CA, "r"); + secureClient->loadCACert(file, file.size()); } - if(SPIFFS.exists("/mqtt-cert.cer")) { + if(SPIFFS.exists(FILE_MQTT_CERT)) { debugI("Found MQTT certificate file"); - File file = SPIFFS.open("/mqtt-cert.cer", "r"); - cert = new char[file.size()]; - if (file.size() != file.readBytes(cert, file.size())) { - delete cert; - cert = NULL; - } + File file = SPIFFS.open(FILE_MQTT_CERT, "r"); + secureClient->loadCertificate(file, file.size()); } - if(SPIFFS.exists("/mqtt-key.cer")) { + if(SPIFFS.exists(FILE_MQTT_KEY)) { debugI("Found MQTT key file"); - File file = SPIFFS.open("/mqtt-key.cer", "r"); - key = new char[file.size()]; - if (file.size() != file.readBytes(key, file.size())) { - delete key; - key = NULL; - } + File file = SPIFFS.open(FILE_MQTT_KEY, "r"); + secureClient->loadPrivateKey(file, file.size()); } SPIFFS.end(); - - if(ca) { -#ifdef ESP32 - secureClient->setCACert(ca); -#else - secureClient->setTrustAnchors(new X509List(ca)); - secureClient->allowSelfSignedCerts(); -#endif - caExists = true; - } - if(cert && key) { -#ifdef ESP32 - secureClient->setCertificate(cert); - secureClient->setPrivateKey(key); -#else - secureClient->setClientRSACert(new X509List(cert), new PrivateKey(key)); -#endif - } - } - if(!caExists) { - debugW("No CA found, using insecure"); -#ifdef ESP32 - // TODO -#else - secureClient->setInsecure(); -#endif } client = secureClient; } else { @@ -714,6 +682,10 @@ void MQTT_connect() { mqtt.begin(config.getMqttHost().c_str(), config.getMqttPort(), *client); +#if defined(ESP8266) + if(secureClient) secureClient->setX509Time(timeClient.getEpochTime()); +#endif + // Connect to a unsecure or secure MQTT server if ((config.getMqttUser().isEmpty() && mqtt.connect(config.getMqttClientId().c_str())) || (!config.getMqttUser().isEmpty() && mqtt.connect(config.getMqttClientId().c_str(), config.getMqttUser().c_str(), config.getMqttPassword().c_str()))) { @@ -732,9 +704,15 @@ void MQTT_connect() { sendSystemStatusToMqtt(); } } else { - lastMqttRetry = millis() + 30000; if (Debug.isActive(RemoteDebug::ERROR)) { - debugI("Failed to connect to MQTT"); + debugE("Failed to connect to MQTT"); +#if defined(ESP8266) + if(secureClient) { + char buf[256]; + secureClient->getLastSSLError(buf,256); + Debug.println(buf); + } +#endif } } yield(); diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index 9f2056f0..127415c4 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -1,7 +1,9 @@ #include "AmsWebServer.h" #include "version.h" +#include "AmsStorage.h" #include "root/index_html.h" +#include "root/index_js.h" #include "root/configmeter_html.h" #include "root/configwifi_html.h" #include "root/configmqtt_html.h" @@ -11,6 +13,7 @@ #include "root/boot_css.h" #include "root/gaugemeter_js.h" #include "root/upload_html.h" +#include "root/delete_html.h" #include "Base64.h" @@ -22,7 +25,8 @@ void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) { this->config = config; this->mqtt = mqtt; - server.on("/", std::bind(&AmsWebServer::indexHtml, this)); + server.on("/", HTTP_GET, std::bind(&AmsWebServer::indexHtml, this)); + server.on("/index.js", HTTP_GET, std::bind(&AmsWebServer::indexJs, this)); server.on("/config-meter", HTTP_GET, std::bind(&AmsWebServer::configMeterHtml, this)); server.on("/config-wifi", HTTP_GET, std::bind(&AmsWebServer::configWifiHtml, this)); server.on("/config-mqtt", HTTP_GET, std::bind(&AmsWebServer::configMqttHtml, this)); @@ -40,10 +44,13 @@ void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) { server.on("/mqtt-ca", HTTP_GET, std::bind(&AmsWebServer::mqttCa, this)); server.on("/mqtt-ca", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttCaUpload, this)); + server.on("/mqtt-ca/delete", HTTP_POST, std::bind(&AmsWebServer::mqttCaDelete, this)); server.on("/mqtt-cert", HTTP_GET, std::bind(&AmsWebServer::mqttCert, this)); server.on("/mqtt-cert", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttCertUpload, this)); + server.on("/mqtt-cert/delete", HTTP_POST, std::bind(&AmsWebServer::mqttCertDelete, this)); server.on("/mqtt-key", HTTP_GET, std::bind(&AmsWebServer::mqttKey, this)); server.on("/mqtt-key", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::mqttKeyUpload, this)); + server.on("/mqtt-key/delete", HTTP_POST, std::bind(&AmsWebServer::mqttKeyDelete, this)); server.begin(); // Web server start } @@ -93,6 +100,10 @@ bool AmsWebServer::checkSecurity(byte level) { server.setContentLength(0); server.send(401, "text/html", ""); } + if(access) + printD(" access granted"); + else + printD(" access denied"); return access; } @@ -163,6 +174,13 @@ void AmsWebServer::indexHtml() { server.send(200, "text/html", html); } +void AmsWebServer::indexJs() { + printD("Serving /index.js over http..."); + + server.sendHeader("Cache-Control", "public, max-age=3600"); + server.send_P(200, "application/javascript", INDEX_JS); +} + void AmsWebServer::configMeterHtml() { printD("Serving /config-meter.html over http..."); @@ -264,12 +282,12 @@ void AmsWebServer::configMqttHtml() { html.replace("${display.ssl}", config->isMqttSsl() ? "" : "none"); if(SPIFFS.begin()) { - html.replace("${display.ca.upload}", SPIFFS.exists("/mqtt-ca.pem") ? "none" : ""); - html.replace("${display.ca.file}", SPIFFS.exists("/mqtt-ca.pem") ? "" : "none"); - html.replace("${display.cert.upload}", SPIFFS.exists("/mqtt-cert.pem") ? "none" : ""); - html.replace("${display.cert.file}", SPIFFS.exists("/mqtt-cert.pem") ? "" : "none"); - html.replace("${display.key.upload}", SPIFFS.exists("/mqtt-key.pem") ? "none" : ""); - html.replace("${display.key.file}", SPIFFS.exists("/mqtt-key.pem") ? "" : "none"); + html.replace("${display.ca.upload}", SPIFFS.exists(FILE_MQTT_CA) ? "none" : ""); + html.replace("${display.ca.file}", SPIFFS.exists(FILE_MQTT_CA) ? "" : "none"); + html.replace("${display.cert.upload}", SPIFFS.exists(FILE_MQTT_CERT) ? "none" : ""); + html.replace("${display.cert.file}", SPIFFS.exists(FILE_MQTT_CERT) ? "" : "none"); + html.replace("${display.key.upload}", SPIFFS.exists(FILE_MQTT_KEY) ? "none" : ""); + html.replace("${display.key.file}", SPIFFS.exists(FILE_MQTT_KEY) ? "" : "none"); SPIFFS.end(); } else { html.replace("${display.ca.upload}", ""); @@ -314,23 +332,15 @@ void AmsWebServer::configWebHtml() { void AmsWebServer::bootCss() { printD("Serving /boot.css over http..."); - String css = String((const __FlashStringHelper*) BOOT_CSS); - server.sendHeader("Cache-Control", "public, max-age=3600"); - - server.setContentLength(css.length()); - server.send(200, "text/css", css); + server.send_P(200, "text/css", BOOT_CSS); } void AmsWebServer::gaugemeterJs() { printD("Serving /gaugemeter.js over http..."); - String js = String((const __FlashStringHelper*) GAUGEMETER_JS); - server.sendHeader("Cache-Control", "public, max-age=3600"); - - server.setContentLength(js.length()); - server.send(200, "application/javascript", js); + server.send_P(200, "application/javascript", GAUGEMETER_JS); } void AmsWebServer::dataJson() { @@ -604,38 +614,56 @@ void AmsWebServer::uploadPost() { server.send(200); } -void AmsWebServer::configSystemUpload() { +void AmsWebServer::uploadFile(const char* path) { HTTPUpload& upload = server.upload(); if(upload.status == UPLOAD_FILE_START){ String filename = upload.filename; - if(!filename.endsWith(".bin")) { - server.send(500, "text/plain", "500: couldn't create file"); - } else if (!SPIFFS.begin()) { + if (!SPIFFS.begin()) { printE("An Error has occurred while mounting SPIFFS"); String html = "

Error uploading!

"; server.send(500, "text/html", html); } else { printD("handleFileUpload Name: %s", filename.c_str()); - firmwareFile = SPIFFS.open("/firmware.bin", "w"); + file = SPIFFS.open(path, "w"); filename = String(); } } else if(upload.status == UPLOAD_FILE_WRITE) { - if(firmwareFile) - firmwareFile.write(upload.buf, upload.currentSize); + if(file) + file.write(upload.buf, upload.currentSize); } else if(upload.status == UPLOAD_FILE_END) { - if(firmwareFile) { - firmwareFile.close(); + if(file) { + file.close(); SPIFFS.end(); printD("handleFileUpload Size: %d", upload.totalSize); - performRestart = true; - server.sendHeader("Location","/restart-wait"); - server.send(303); } else { server.send(500, "text/plain", "500: couldn't create file"); } } } +void AmsWebServer::deleteFile(const char* path) { + if(SPIFFS.begin()) { + SPIFFS.remove(path); + SPIFFS.end(); + } +} + +void AmsWebServer::configSystemUpload() { + HTTPUpload& upload = server.upload(); + if(upload.status == UPLOAD_FILE_START) { + String filename = upload.filename; + if(!filename.endsWith(".bin")) { + server.send(500, "text/plain", "500: couldn't create file"); + } + } + uploadFile(FILE_FIRMWARE); + if(upload.status == UPLOAD_FILE_END) { + performRestart = true; + server.sendHeader("Location","/restart-wait"); + server.send(303); + } +} + void AmsWebServer::restartWaitHtml() { printD("Serving /restart-wait.html over http..."); @@ -680,154 +708,166 @@ void AmsWebServer::isAliveCheck() { server.send(200); } -void AmsWebServer::mqttCa() { - printD("Serving /mqtt-ca.html over http..."); - +void AmsWebServer::uploadHtml(const char* label, const char* action, const char* menu) { String html = String((const __FlashStringHelper*) UPLOAD_HTML); + html.replace("${form.action}", action); + html.replace("${version}", VERSION); + + if(WiFi.getMode() != WIFI_AP) { + html.replace("boot.css", BOOTSTRAP_URL); + } + + html.replace("${menu." + String(menu) + ".class}", "active"); html.replace("${menu.meter.class}", ""); html.replace("${menu.wifi.class}", ""); - html.replace("${menu.mqtt.class}", "active"); + html.replace("${menu.mqtt.class}", ""); html.replace("${menu.web.class}", ""); html.replace("${menu.system.class}", ""); - html.replace("${file.label}", "CA file"); + html.replace("${file.label}", label); - server.sendHeader("Cache-Control", "public, max-age=3600"); + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server.sendHeader("Pragma", "no-cache"); server.setContentLength(html.length()); server.send(200, "text/html", html); } -void AmsWebServer::mqttCaUpload() { - HTTPUpload& upload = server.upload(); - if(upload.status == UPLOAD_FILE_START){ - String filename = upload.filename; - if (!SPIFFS.begin()) { - printE("An Error has occurred while mounting SPIFFS"); - String html = "

Error uploading!

"; - server.send(500, "text/html", html); +void AmsWebServer::deleteHtml(const char* label, const char* action, const char* menu) { + String html = String((const __FlashStringHelper*) DELETE_HTML); + html.replace("${form.action}", action); + html.replace("${version}", VERSION); + + if(WiFi.getMode() != WIFI_AP) { + html.replace("boot.css", BOOTSTRAP_URL); + } + + html.replace("${menu." + String(menu) + ".class}", "active"); + html.replace("${menu.meter.class}", ""); + html.replace("${menu.wifi.class}", ""); + html.replace("${menu.mqtt.class}", ""); + html.replace("${menu.web.class}", ""); + html.replace("${menu.system.class}", ""); + html.replace("${file.label}", label); + + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server.sendHeader("Pragma", "no-cache"); + + server.setContentLength(html.length()); + server.send(200, "text/html", html); +} + +void AmsWebServer::mqttCa() { + printD("Serving /mqtt-ca.html over http..."); + + String html; + if(SPIFFS.begin()) { + if(SPIFFS.exists(FILE_MQTT_CA)) { + deleteHtml("CA file", "/mqtt-ca/delete", "mqtt"); } else { - printD("handleFileUpload Name: %s", filename.c_str()); - mqttCaFile = SPIFFS.open("/mqtt-ca.pem", "w"); - filename = String(); - } - } else if(upload.status == UPLOAD_FILE_WRITE) { - if(mqttCaFile) - mqttCaFile.write(upload.buf, upload.currentSize); - } else if(upload.status == UPLOAD_FILE_END) { - if(mqttCaFile) { - mqttCaFile.close(); - SPIFFS.end(); - printD("handleFileUpload Size: %d", upload.totalSize); - server.sendHeader("Location","/config-mqtt"); - server.send(303); - } else { - server.send(500, "text/plain", "500: couldn't create file"); - } - } + uploadHtml("CA file", "/mqtt-ca", "mqtt"); + } + SPIFFS.end(); + } else { + server.sendHeader("Location","/config-mqtt"); + server.send(303); + } +} + +void AmsWebServer::mqttCaUpload() { + uploadFile(FILE_MQTT_CA); + HTTPUpload& upload = server.upload(); + if(upload.status == UPLOAD_FILE_END) { + server.sendHeader("Location","/config-mqtt"); + server.send(303); + if(config->isMqttSsl()) { + config->setMqttChanged(); + } + } } void AmsWebServer::mqttCaDelete() { - + deleteFile(FILE_MQTT_CA); + server.sendHeader("Location","/config-mqtt"); + server.send(303); + if(config->isMqttSsl()) { + config->setMqttChanged(); + } } void AmsWebServer::mqttCert() { printD("Serving /mqtt-cert.html over http..."); - String html = String((const __FlashStringHelper*) UPLOAD_HTML); - html.replace("${menu.meter.class}", ""); - html.replace("${menu.wifi.class}", ""); - html.replace("${menu.mqtt.class}", "active"); - html.replace("${menu.web.class}", ""); - html.replace("${menu.system.class}", ""); - html.replace("${file.label}", "Certificate"); - - server.sendHeader("Cache-Control", "public, max-age=3600"); - - server.setContentLength(html.length()); - server.send(200, "text/html", html); + String html; + if(SPIFFS.begin()) { + if(SPIFFS.exists(FILE_MQTT_CERT)) { + deleteHtml("Certificate", "/mqtt-cert/delete", "mqtt"); + } else { + uploadHtml("Certificate", "/mqtt-cert", "mqtt"); + } + SPIFFS.end(); + } else { + server.sendHeader("Location","/config-mqtt"); + server.send(303); + } } void AmsWebServer::mqttCertUpload() { + uploadFile(FILE_MQTT_CERT); HTTPUpload& upload = server.upload(); - if(upload.status == UPLOAD_FILE_START){ - String filename = upload.filename; - if (!SPIFFS.begin()) { - printE("An Error has occurred while mounting SPIFFS"); - String html = "

Error uploading!

"; - server.send(500, "text/html", html); - } else { - printD("handleFileUpload Name: %s", filename.c_str()); - mqttCertFile = SPIFFS.open("/mqtt-cert.pem", "w"); - filename = String(); - } - } else if(upload.status == UPLOAD_FILE_WRITE) { - if(mqttCertFile) - mqttCertFile.write(upload.buf, upload.currentSize); - } else if(upload.status == UPLOAD_FILE_END) { - if(mqttCertFile) { - mqttCertFile.close(); - SPIFFS.end(); - printD("handleFileUpload Size: %d", upload.totalSize); - server.sendHeader("Location","/config-mqtt"); - server.send(303); - } else { - server.send(500, "text/plain", "500: couldn't create file"); - } - } + if(upload.status == UPLOAD_FILE_END) { + server.sendHeader("Location","/config-mqtt"); + server.send(303); + if(config->isMqttSsl()) { + config->setMqttChanged(); + } + } } void AmsWebServer::mqttCertDelete() { - + deleteFile(FILE_MQTT_CERT); + server.sendHeader("Location","/config-mqtt"); + server.send(303); + if(config->isMqttSsl()) { + config->setMqttChanged(); + } } void AmsWebServer::mqttKey() { printD("Serving /mqtt-key.html over http..."); - String html = String((const __FlashStringHelper*) UPLOAD_HTML); - html.replace("${menu.meter.class}", ""); - html.replace("${menu.wifi.class}", ""); - html.replace("${menu.mqtt.class}", "active"); - html.replace("${menu.web.class}", ""); - html.replace("${menu.system.class}", ""); - html.replace("${file.label}", "Private key"); - - server.sendHeader("Cache-Control", "public, max-age=3600"); - - server.setContentLength(html.length()); - server.send(200, "text/html", html); + String html; + if(SPIFFS.begin()) { + if(SPIFFS.exists(FILE_MQTT_KEY)) { + deleteHtml("Private key", "/mqtt-key/delete", "mqtt"); + } else { + uploadHtml("Private key", "/mqtt-key", "mqtt"); + } + SPIFFS.end(); + } else { + server.sendHeader("Location","/config-mqtt"); + server.send(303); + } } void AmsWebServer::mqttKeyUpload() { + uploadFile(FILE_MQTT_KEY); HTTPUpload& upload = server.upload(); - if(upload.status == UPLOAD_FILE_START){ - String filename = upload.filename; - if (!SPIFFS.begin()) { - printE("An Error has occurred while mounting SPIFFS"); - String html = "

Error uploading!

"; - server.send(500, "text/html", html); - } else { - printD("handleFileUpload Name: %s", filename.c_str()); - mqttKeyFile = SPIFFS.open("/mqtt-key.pem", "w"); - filename = String(); - } - } else if(upload.status == UPLOAD_FILE_WRITE) { - if(mqttKeyFile) - mqttKeyFile.write(upload.buf, upload.currentSize); - } else if(upload.status == UPLOAD_FILE_END) { - if(mqttKeyFile) { - mqttKeyFile.close(); - SPIFFS.end(); - printD("handleFileUpload Size: %d", upload.totalSize); - server.sendHeader("Location","/config-mqtt"); - server.send(303); - } else { - server.send(500, "text/plain", "500: couldn't create file"); - } - } + if(upload.status == UPLOAD_FILE_END) { + server.sendHeader("Location","/config-mqtt"); + server.send(303); + if(config->isMqttSsl()) { + config->setMqttChanged(); + } + } } void AmsWebServer::mqttKeyDelete() { - + deleteFile(FILE_MQTT_KEY); + server.sendHeader("Location","/config-mqtt"); + server.send(303); + if(config->isMqttSsl()) { + config->setMqttChanged(); + } } diff --git a/src/web/AmsWebServer.h b/src/web/AmsWebServer.h index 5f62f10a..fdffae32 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -44,10 +44,7 @@ private: AmsConfiguration* config; AmsData data; MQTTClient* mqtt; - File firmwareFile; - File mqttCaFile; - File mqttCertFile; - File mqttKeyFile; + File file; bool performRestart = false; #if defined(ESP8266) @@ -59,6 +56,7 @@ private: bool checkSecurity(byte level); void indexHtml(); + void indexJs(); void configMeterHtml(); void configWifiHtml(); void configMqttHtml(); @@ -74,6 +72,10 @@ private: void restartWaitHtml(); void isAliveCheck(); + void uploadHtml(const char* label, const char* action, const char* menu); + void deleteHtml(const char* label, const char* action, const char* menu); + void uploadFile(const char* path); + void deleteFile(const char* path); void uploadPost(); void mqttCa(); void mqttCaUpload(); diff --git a/web/boot.css b/web/boot.css index 9bf91c81..612782ef 100644 --- a/web/boot.css +++ b/web/boot.css @@ -225,6 +225,12 @@ a { border-radius: .25rem; transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; } +.btn-group-sm>.btn, .btn-sm { + padding: .25rem .5rem; + font-size: .875rem; + line-height: 1.5; + border-radius: .2rem; +} .btn-outline-secondary { color: #6c757d; border-color: #6c757d; @@ -234,6 +240,11 @@ a { background-color: #007bff; border-color: #007bff; } +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} .navbar { position: relative; display: -ms-flexbox; diff --git a/web/configmqtt.html b/web/configmqtt.html index 75417d15..e48906e1 100644 --- a/web/configmqtt.html +++ b/web/configmqtt.html @@ -115,20 +115,24 @@
- Upload + + Upload + - Delete + Delete
- -
- Upload + +
+ + Upload + - Delete + Delete
@@ -137,9 +141,11 @@
- Upload + + Upload + - Delete + Delete
diff --git a/web/delete.html b/web/delete.html new file mode 100644 index 00000000..07cffe54 --- /dev/null +++ b/web/delete.html @@ -0,0 +1,56 @@ + + + + + AMS reader - Meter configuration + + + + +
+ +
+
+
Are you sure you want to delete this file?
+
+
+
+
+ Back +
+
+ +
+
+
+
+ + diff --git a/web/index.html b/web/index.html index e8654dd3..0a903d21 100644 --- a/web/index.html +++ b/web/index.html @@ -196,163 +196,6 @@
- + diff --git a/web/index.js b/web/index.js new file mode 100644 index 00000000..b4e51c59 --- /dev/null +++ b/web/index.js @@ -0,0 +1,159 @@ +$(function() { + +}); +var im = $("#importMeter") +im.gaugeMeter({ + percent: 0, + text: "-", + append: "W" +}); + +var em = $("#exportMeter") +em.gaugeMeter({ + percent: 0, + text: "-", + append: "W" +}); + +var setStatus = function(id, status) { + var item = $('#'+id); + item.removeClass('d-none'); + item.removeClass (function (index, className) { + return (className.match (/(^|\s)badge-\S+/g) || []).join(' '); + }); + item.addClass('badge badge-' + status); +}; + +var interval = 5000; +var fetch = function() { + $.ajax({ + url: '/data.json', + timeout: 10000, + dataType: 'json', + }).done(function(json) { + $(".SimpleMeter").hide(); + im.show(); + em.show(); + + for(var id in json) { + var str = json[id]; + if(typeof str === "object") + continue; + if(isNaN(str)) { + $('#'+id).html(str); + } else { + var num = parseFloat(str); + $('#'+id).html(num.toFixed(num < 0 ? 0 : num < 10 ? 2 : 1)); + } + } + + if(window.moment) { + $('#currentMillis').html(moment.duration(parseInt(json.uptime_seconds), 'seconds').humanize()); + $('#currentMillis').closest('.row').show(); + } + + if(json.status) { + for(var id in json.status) { + setStatus(id, json.status[id]); + } + } + + if(json.mqtt) { + $('.mqtt-error').addClass('d-none'); + $('.mqtt-error'+json.mqtt.lastError).removeClass('d-none'); + $('#mqtt-lastError').html(json.mqtt.lastError); + } + + if(json.wifi) { + for(var id in json.wifi) { + var str = json.wifi[id]; + dst = $('#'+id); + if(isNaN(str)) { + dst.html(str); + } else { + var num = parseFloat(str); + dst.html(num.toFixed(0)); + $('#'+id+'-row').show(); + } + } + } + + if(json.data) { + var p = 0; + var p_pct = parseInt(json.p_pct); + var p_append = "W"; + if(json.data.P) { + p = parseFloat(json.data.P); + if(p > 1000) { + p = (p/1000).toFixed(1); + p_append = "kW"; + } + } + im.gaugeMeter({ + percent: p_pct, + text: p, + append: p_append + }); + + var po = 0; + var po_pct = parseInt(json.po_pct); + var po_append = "W"; + if(json.data.PO) { + po = parseFloat(json.data.PO); + if(po > 1000) { + po = (po/1000).toFixed(1); + po_append = "kW"; + } + } + em.gaugeMeter({ + percent: po_pct, + text: po, + append: po_append + }); + + for(var id in json.data) { + var str = json.data[id]; + if(isNaN(str)) { + $('#'+id).html(str); + } else { + var num = parseFloat(str); + $('#'+id).html(num.toFixed(1)); + $('#'+id+'-row').show(); + } + } + } else { + im.gaugeMeter({ + percent: 0, + text: "-", + append: "W" + }); + + em.gaugeMeter({ + percent: 0, + text: "-", + append: "W" + }); + } + setTimeout(fetch, interval); + }).fail(function() { + setTimeout(fetch, interval*4); + + im.gaugeMeter({ + percent: 0, + text: "-", + append: "W" + }); + + em.gaugeMeter({ + percent: 0, + text: "-", + append: "W" + }); + + setStatus("mqtt", "secondary"); + setStatus("wifi", "secondary"); + setStatus("han", "secondary"); + setStatus("esp", "danger"); + }); +} +fetch(); diff --git a/web/upload.html b/web/upload.html index 1d0963eb..caf45909 100644 --- a/web/upload.html +++ b/web/upload.html @@ -5,7 +5,6 @@ AMS reader - Meter configuration -