diff --git a/platformio.ini b/platformio.ini index 4b403457..a4987a50 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 eec7a963..42794a83 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); @@ -170,6 +179,10 @@ void AmsConfiguration::clearMqtt() { setMqttPassword(""); } +void AmsConfiguration::setMqttChanged() { + mqttChanged = true; +} + bool AmsConfiguration::isMqttChanged() { return mqttChanged; } @@ -341,7 +354,7 @@ bool AmsConfiguration::hasConfig() { case 75: case 80: case 81: - case 91: // domoticz (based on 81) + case 82: return true; default: configVersion = 0; @@ -374,8 +387,8 @@ bool AmsConfiguration::load() { case 81: success = loadConfig81(address); break; - case 91: - success = loadConfig91(address); + case 82: + success = loadConfig82(address); break; } EEPROM.end(); @@ -678,9 +691,9 @@ bool AmsConfiguration::loadConfig81(int address) { return true; } // -// domoticz (based on 81) // -bool AmsConfiguration::loadConfig91(int address) { +// +bool AmsConfiguration::loadConfig82(int address) { char* temp; address += readString(address, &temp); @@ -785,9 +798,7 @@ bool AmsConfiguration::loadConfig91(int address) { int domoCL1IDX; address += readInt(address, &domoCL1IDX); setDomoCL1IDX(domoCL1IDX); - // address += readString(address, &temp); - // domoEnergy = String(temp).toDouble(); - // setDomoEnergy(domoEnergy); + } else { clearDomo(); } @@ -797,6 +808,101 @@ bool AmsConfiguration::loadConfig91(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; @@ -832,6 +938,7 @@ bool AmsConfiguration::save() { address += saveBool(address, false); } address += saveInt(address, mqttPayloadFormat); + address += saveBool(address, mqttSsl); } else { address += saveBool(address, false); } @@ -860,7 +967,6 @@ bool AmsConfiguration::save() { address += saveInt(address, domoVL2IDX); address += saveInt(address, domoVL3IDX); address += saveInt(address, domoCL1IDX); - //address += saveString(address, String(domoEnergy).c_str()); } else { address += saveBool(address, false); } diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index b5f50f65..066ba3bc 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -48,8 +48,11 @@ public: void setMqttPassword(String mqttPassword); int getMqttPayloadFormat(); void setMqttPayloadFormat(int mqttPayloadFormat); + bool isMqttSsl(); + void setMqttSsl(bool mqttSsl); void clearMqtt(); + void setMqttChanged(); bool isMqttChanged(); void ackMqttChange(); @@ -121,6 +124,7 @@ private: String mqttUser; String mqttPassword; int mqttPayloadFormat = 0; + bool mqttSsl; bool mqttChanged = false; byte authSecurity; @@ -144,15 +148,14 @@ private: bool domoChanged; const int EEPROM_SIZE = 512; - const int EEPROM_CHECK_SUM = 91; // 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 loadConfig91(int address); // domoticz - + bool loadConfig82(int address); int saveString(int pAddress, const char* pString); int readString(int pAddress, char* pString[]); 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.h b/src/AmsToMqttBridge.h index ca980547..73f11674 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 43c20bea..0bda9b1e 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,13 +48,15 @@ HwTools hw; DNSServer dnsServer; +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 60000); + AmsConfiguration config; RemoteDebug Debug; AmsWebServer ws(&Debug); -WiFiClient *client; MQTTClient mqtt(512); HanReader hanReader; @@ -122,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); @@ -141,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)) { @@ -157,7 +161,7 @@ void setup() { flashed = Update.end(true); } firmwareFile.close(); - SPIFFS.remove("/firmware.bin"); + SPIFFS.remove(FILE_FIRMWARE); } SPIFFS.end(); if(flashed) { @@ -188,7 +192,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"); @@ -229,6 +232,7 @@ void setup() { } ws.setup(&config, &mqtt); + timeClient.begin(); #if HAS_RGB_LED //Signal startup by blinking red / green / yellow @@ -295,6 +299,8 @@ void loop() { errorBlink(); } + timeClient.update(); + // Only do normal stuff if we're not booted as AP if (WiFi.getMode() != WIFI_AP) { led_off(); @@ -468,7 +474,7 @@ void readHanPort() { // // Start DOMOTICZ // - } else if(config.getMqttPayloadFormat() == 2) { + } else if(config.getMqttPayloadFormat() == 3) { StaticJsonDocument<512> json; hanToJson(json, data, hw, temperature); if (Debug.isActive(RemoteDebug::INFO)) { @@ -613,7 +619,7 @@ void readHanPort() { // // End DOMOTICZ // - } 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: @@ -627,41 +633,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())); } } @@ -774,7 +780,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; } @@ -786,8 +792,51 @@ void MQTT_connect() { mqtt.disconnect(); yield(); + WiFiClientSecure *secureClient; + Client *client; + if(config.isMqttSsl()) { + debugI("MQTT SSL is configured"); + + if(!timeClient.update()) debugW("NTP time is not ready"); + + secureClient = new WiFiClientSecure(); +#if defined(ESP8266) + secureClient->setBufferSizes(512, 512); +#endif + + if(SPIFFS.begin()) { + char *ca = NULL; + char *cert = NULL; + char *key = NULL; + + if(SPIFFS.exists(FILE_MQTT_CA)) { + debugI("Found MQTT CA file"); + File file = SPIFFS.open(FILE_MQTT_CA, "r"); + secureClient->loadCACert(file, file.size()); + } + if(SPIFFS.exists(FILE_MQTT_CERT)) { + debugI("Found MQTT certificate file"); + File file = SPIFFS.open(FILE_MQTT_CERT, "r"); + secureClient->loadCertificate(file, file.size()); + } + if(SPIFFS.exists(FILE_MQTT_KEY)) { + debugI("Found MQTT key file"); + File file = SPIFFS.open(FILE_MQTT_KEY, "r"); + secureClient->loadPrivateKey(file, file.size()); + } + SPIFFS.end(); + } + client = secureClient; + } else { + client = new WiFiClient(); + } + 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()))) { @@ -807,7 +856,14 @@ void MQTT_connect() { } } else { 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 12e6015d..f30e9fb8 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,8 @@ #include "root/restartwait_html.h" #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 +26,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)); @@ -32,13 +37,23 @@ 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-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 } @@ -87,6 +102,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; } @@ -157,6 +176,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..."); @@ -254,6 +280,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(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}", ""); + 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); } @@ -322,23 +368,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() { @@ -537,6 +575,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(); } @@ -634,42 +673,60 @@ void AmsWebServer::configSystemHtml() { server.send(200, "text/html", html); } -void AmsWebServer::configSystemPost() { +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..."); @@ -714,6 +771,169 @@ void AmsWebServer::isAliveCheck() { server.send(200); } +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}", ""); + 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::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 { + 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; + 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_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; + 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_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(); + } +} + + 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 9d4c4312..de563245 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -44,7 +44,7 @@ private: AmsConfiguration* config; AmsData data; MQTTClient* mqtt; - File firmwareFile; + File file; bool performRestart = false; #if defined(ESP8266) @@ -56,6 +56,7 @@ private: bool checkSecurity(byte level); void indexHtml(); + void indexJs(); void configMeterHtml(); void configWifiHtml(); void configMqttHtml(); @@ -68,11 +69,25 @@ private: void handleSave(); void configSystemHtml(); - void configSystemPost(); void configSystemUpload(); 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(); + 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/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 2caf116d..d5061948 100644 --- a/web/configmqtt.html +++ b/web/configmqtt.html @@ -57,8 +57,9 @@
@@ -106,6 +107,55 @@ +
+
+
+ +
+ +
+
+
+
+
+ +
+ + Upload + + + Delete + +
+
+
+
+
+ +
+ + Upload + + + Delete + +
+
+
+
+
+ +
+ + Upload + + + 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 2427b757..4ce3b2a1 100644 --- a/web/index.html +++ b/web/index.html @@ -200,164 +200,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 new file mode 100644 index 00000000..caf45909 --- /dev/null +++ b/web/upload.html @@ -0,0 +1,65 @@ + + + + + AMS reader - Meter configuration + + + + +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ Back +
+
+ +
+
+
+
+ +