diff --git a/frames/lng.raw b/frames/lng.raw index 9dc84a6e..00b2b497 100644 --- a/frames/lng.raw +++ b/frames/lng.raw @@ -43,3 +43,37 @@ FF // Last byte of OBIS in previous block 0600000000 // Accumulated export 8BA4 7E + + + + +7E A1 23 CE FF 03 13 21 55 E6 E7 00 + +0F 00 00 08 E2 +0C 07 E5 07 13 01 0C 1A 0A FF 80 00 00 + +02 0B // 11 + 01 0B // 11 + 02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 02 12 00 00 + 02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 01 12 00 00 + 02 04 12 00 01 09 06 00 00 60 01 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 00 01 07 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 00 02 07 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 01 01 08 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 01 02 08 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 01 05 08 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 01 06 08 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 01 07 08 00 FF 0F 02 12 00 00 + 02 04 12 00 03 09 06 01 01 08 08 00 FF 0F 02 12 00 00 + 09 06 00 08 19 09 00 FF + 09 08 34 33 30 39 34 33 35 31 + 06 00 00 00 0B + 06 00 00 00 00 + 06 00 00 00 10 + 06 00 00 00 04 + 06 00 00 00 00 + 06 00 00 00 08 + 06 00 00 00 00 + 06 00 00 00 01 +7C 8B +7E \ No newline at end of file diff --git a/scripts/addversion.py b/scripts/addversion.py index 47fc1d48..c2295ea4 100644 --- a/scripts/addversion.py +++ b/scripts/addversion.py @@ -19,6 +19,6 @@ hf = """ #define VERSION "{}" #endif #define BUILD_EPOCH {} -""".format(version, time()) +""".format(version, round(time())) with open(FILENAME_VERSION_H, 'w+') as f: f.write(hf) diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index c3079bfe..96648bb9 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -517,6 +517,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config) } bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) { + if(config.hours > 5) config.hours = 5; EnergyAccountingConfig existing; if(getEnergyAccountingConfig(existing)) { for(int i = 0; i < 9; i++) { @@ -525,6 +526,7 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) } } config.thresholds[9] = 255; + energyAccountingChanged |= config.hours != existing.hours; } else { energyAccountingChanged = true; } diff --git a/src/AmsData.h b/src/AmsData.h index 3ffcd296..21d29827 100644 --- a/src/AmsData.h +++ b/src/AmsData.h @@ -12,6 +12,7 @@ enum AmsType { AmsTypeIskra = 0x08, AmsTypeLandis = 0x09, AmsTypeSagemcom = 0x0A, + AmsTypeLng = 0x0B, AmsTypeCustom = 0x88, AmsTypeUnknown = 0xFF }; diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 1b5fdc68..edf40b8d 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -65,10 +65,11 @@ ADC_MODE(ADC_VCC); #include "RemoteDebug.h" #define BUF_SIZE_COMMON (2048) -#define BUF_SIZE_HAN (1024) +#define BUF_SIZE_HAN (1280) #include "IEC6205621.h" #include "IEC6205675.h" +#include "LNG.h" #include "ams/DataParsers.h" @@ -509,6 +510,7 @@ void loop() { if (mqttEnabled || config.isMqttChanged()) { if(mqtt == NULL || !mqtt->connected() || config.isMqttChanged()) { MQTT_connect(); + config.ackMqttChange(); } } else if(mqtt != NULL && mqtt->connected()) { mqttClient->stop(); @@ -588,7 +590,7 @@ void loop() { } if(now - lastSysupdate > 10000) { if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) { - mqttHandler->publishSystem(&hw); + mqttHandler->publishSystem(&hw, eapi, &ea); } lastSysupdate = now; } @@ -830,6 +832,7 @@ bool readHanPort() { len += hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len); if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { mqtt->publish(topic.c_str(), toHex(hanBuffer+pos, len)); + mqtt->loop(); } while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above len = 0; @@ -842,19 +845,26 @@ bool readHanPort() { } AmsData data; + char* payload = ((char *) (hanBuffer)) + pos; if(ctx.type == DATA_TAG_DLMS) { // If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { - mqtt->publish(topic.c_str(), toHex(hanBuffer+pos, ctx.length)); + mqtt->publish(topic.c_str(), toHex((byte*) payload, ctx.length)); + mqtt->loop(); } debugV("Using application data:"); - if(Debug.isActive(RemoteDebug::VERBOSE)) debugPrint(hanBuffer+pos, 0, ctx.length); + if(Debug.isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length); - // TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats - data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, ctx); + // Rudimentary detector for L&G proprietary format + if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) { + data = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug); + } else { + // TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats + data = IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx); + } } else if(ctx.type == DATA_TAG_DSMR) { - data = IEC6205621(((char *) (hanBuffer)) + pos); + data = IEC6205621(payload); } len = 0; @@ -1036,7 +1046,7 @@ void WiFi_connect() { } #endif WiFi.mode(WIFI_STA); - WiFi.setSleep(WIFI_PS_MIN_MODEM); + WiFi.setSleep(WIFI_PS_MAX_MODEM); #if defined(ESP32) if(wifi.power >= 195) WiFi.setTxPower(WIFI_POWER_19_5dBm); @@ -1170,6 +1180,7 @@ int16_t unwrapData(uint8_t *buf, DataParserContext &context) { // If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { mqtt->publish(topic.c_str(), toHex(buf, curLen)); + mqtt->loop(); } break; case DATA_TAG_MBUS: @@ -1177,6 +1188,7 @@ int16_t unwrapData(uint8_t *buf, DataParserContext &context) { // If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { mqtt->publish(topic.c_str(), toHex(buf, curLen)); + mqtt->loop(); } break; case DATA_TAG_GBT: @@ -1195,6 +1207,7 @@ int16_t unwrapData(uint8_t *buf, DataParserContext &context) { debugV("DSMR frame:"); if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { mqtt->publish(topic.c_str(), (char*) buf); + mqtt->loop(); } break; } @@ -1233,7 +1246,6 @@ void MQTT_connect() { if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config"); mqttEnabled = false; ws.setMqttEnabled(false); - config.ackMqttChange(); return; } if(mqtt != NULL) { @@ -1248,20 +1260,19 @@ void MQTT_connect() { } mqtt->disconnect(); + if(config.isMqttChanged()) { + if(mqttSecureClient != NULL) { + mqttSecureClient->stop(); + delete mqttSecureClient; + mqttSecureClient = NULL; + } else { + mqttClient->stop(); + } + mqttClient = NULL; + } yield(); } else { - uint16_t size = 256; - switch(mqttConfig.payloadFormat) { - case 0: // JSON - case 4: // Home Assistant - size = 768; - break; - case 255: // Raw frame - size = 1024; - break; - } - - mqtt = new MQTTClient(size); + mqtt = new MQTTClient(1024); ws.setMqtt(mqtt); } @@ -1296,54 +1307,54 @@ void MQTT_connect() { debugI("MQTT SSL is configured (%dkb free heap)", ESP.getFreeHeap()); if(mqttSecureClient == NULL) { mqttSecureClient = new WiFiClientSecure(); - } - #if defined(ESP8266) - mqttSecureClient->setBufferSizes(512, 512); - #endif - - if(LittleFS.begin()) { - File file; + #if defined(ESP8266) + mqttSecureClient->setBufferSizes(512, 512); + #endif + + if(LittleFS.begin()) { + File file; - if(LittleFS.exists(FILE_MQTT_CA)) { - debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap()); - file = LittleFS.open(FILE_MQTT_CA, "r"); - #if defined(ESP8266) - BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file); - mqttSecureClient->setTrustAnchors(serverTrustedCA); - #elif defined(ESP32) - mqttSecureClient->loadCACert(file, file.size()); - #endif - file.close(); + if(LittleFS.exists(FILE_MQTT_CA)) { + debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap()); + file = LittleFS.open(FILE_MQTT_CA, "r"); + #if defined(ESP8266) + BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file); + mqttSecureClient->setTrustAnchors(serverTrustedCA); + #elif defined(ESP32) + mqttSecureClient->loadCACert(file, file.size()); + #endif + file.close(); + } + + if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) { + #if defined(ESP8266) + debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap()); + file = LittleFS.open(FILE_MQTT_CERT, "r"); + BearSSL::X509List *serverCertList = new BearSSL::X509List(file); + file.close(); + + debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap()); + file = LittleFS.open(FILE_MQTT_KEY, "r"); + BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file); + file.close(); + + debugD("Setting client certificates (%dkb free heap)", ESP.getFreeHeap()); + mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey); + #elif defined(ESP32) + debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap()); + file = LittleFS.open(FILE_MQTT_CERT, "r"); + mqttSecureClient->loadCertificate(file, file.size()); + file.close(); + + debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap()); + file = LittleFS.open(FILE_MQTT_KEY, "r"); + mqttSecureClient->loadPrivateKey(file, file.size()); + file.close(); + #endif + } + LittleFS.end(); + debugD("MQTT SSL setup complete (%dkb free heap)", ESP.getFreeHeap()); } - - if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) { - #if defined(ESP8266) - debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap()); - file = LittleFS.open(FILE_MQTT_CERT, "r"); - BearSSL::X509List *serverCertList = new BearSSL::X509List(file); - file.close(); - - debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap()); - file = LittleFS.open(FILE_MQTT_KEY, "r"); - BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file); - file.close(); - - debugD("Setting client certificates (%dkb free heap)", ESP.getFreeHeap()); - mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey); - #elif defined(ESP32) - debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap()); - file = LittleFS.open(FILE_MQTT_CERT, "r"); - mqttSecureClient->loadCertificate(file, file.size()); - file.close(); - - debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap()); - file = LittleFS.open(FILE_MQTT_KEY, "r"); - mqttSecureClient->loadPrivateKey(file, file.size()); - file.close(); - #endif - } - LittleFS.end(); - debugD("MQTT SSL setup complete (%dkb free heap)", ESP.getFreeHeap()); } mqttClient = mqttSecureClient; } else if(mqttClient == NULL) { @@ -1368,10 +1379,9 @@ void MQTT_connect() { if ((strlen(mqttConfig.username) == 0 && mqtt->connect(mqttConfig.clientId)) || (strlen(mqttConfig.username) > 0 && mqtt->connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) { if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!"); - config.ackMqttChange(); if(mqttHandler != NULL) { - mqttHandler->publishSystem(&hw); + mqttHandler->publishSystem(&hw, eapi, &ea); } // Subscribe to the chosen MQTT topic, if set in configuration diff --git a/src/EnergyAccounting.cpp b/src/EnergyAccounting.cpp index 4b54af21..d31441fb 100644 --- a/src/EnergyAccounting.cpp +++ b/src/EnergyAccounting.cpp @@ -237,7 +237,35 @@ float EnergyAccounting::getMonthMax() { uint32_t maxHour = 0.0; bool included[5] = { false, false, false, false, false }; - while(count < config->hours) { + while(count < config->hours && count <= 5) { + uint8_t maxIdx = 0; + uint16_t maxVal = 0; + for(uint8_t i = 0; i < 5; i++) { + if(included[i]) continue; + if(data.peaks[i].day == 0) continue; + if(data.peaks[i].value > maxVal) { + maxVal = data.peaks[i].value; + maxIdx = i; + } + } + included[maxIdx] = true; + count++; + } + + for(uint8_t i = 0; i < 5; i++) { + if(!included[i]) continue; + maxHour += data.peaks[i].value; + } + return maxHour > 0 ? maxHour / count / 100.0 : 0.0; +} + +float EnergyAccounting::getPeak(uint8_t num) { + if(num < 1 || num > 5) return 0.0; + + uint8_t count = 0; + bool included[5] = { false, false, false, false, false }; + + while(count < config->hours && count <= 5) { uint8_t maxIdx = 0; uint16_t maxVal = 0; for(uint8_t i = 0; i < 5; i++) { @@ -251,13 +279,15 @@ float EnergyAccounting::getMonthMax() { count++; } + uint8_t pos = 0; for(uint8_t i = 0; i < 5; i++) { if(!included[i]) continue; - if(data.peaks[i].day > 0) { - maxHour += data.peaks[i].value; + pos++; + if(pos == num) { + return data.peaks[i].value / 100.0; } } - return maxHour > 0 ? maxHour / count / 100.0 : 0.0; + return 0.0; } bool EnergyAccounting::load() { @@ -310,7 +340,7 @@ bool EnergyAccounting::load() { this->data.peaks[b].day = b; memcpy(&this->data.peaks[b].value, buf+i, 2); b++; - if(b >= config->hours) break; + if(b >= config->hours || b >= 5) break; } ret = true; } else if(buf[0] == 1) { diff --git a/src/EnergyAccounting.h b/src/EnergyAccounting.h index 30a894ed..c0c76e32 100644 --- a/src/EnergyAccounting.h +++ b/src/EnergyAccounting.h @@ -57,6 +57,7 @@ public: float getMonthMax(); uint8_t getCurrentThreshold(); + float getPeak(uint8_t); EnergyAccountingData getData(); void setData(EnergyAccountingData&); diff --git a/src/IEC6205675.cpp b/src/IEC6205675.cpp index 15cb81d5..8ebe54f8 100644 --- a/src/IEC6205675.cpp +++ b/src/IEC6205675.cpp @@ -266,7 +266,9 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo if(meterTs != NULL) { AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs; time_t ts = decodeCosemDateTime(amst->dt); - if(meterType == AmsTypeKamstrup || meterType == AmsTypeAidon) { + if(meterType == AmsTypeAidon) { + meterTimestamp = ts - 3600; + } else if(meterType == AmsTypeKamstrup) { meterTimestamp = tz.toUTC(ts); } else { meterTimestamp = ts; diff --git a/src/LNG.cpp b/src/LNG.cpp new file mode 100644 index 00000000..b5c9817b --- /dev/null +++ b/src/LNG.cpp @@ -0,0 +1,111 @@ +#include "LNG.h" +#include "lwip/def.h" +#include "ams/Cosem.h" + +LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) { + LngHeader* h = (LngHeader*) payload; + if(h->tag == CosemTypeStructure && h->arrayTag == CosemTypeArray) { + meterType = AmsTypeLng; + this->packageTimestamp = ctx.timestamp; + + uint8_t* ptr = (uint8_t*) &h[1]; + uint8_t* data = ptr + (18*h->arrayLength); // Skip descriptors + + uint16_t o170 = 0, o270 = 0; + uint16_t o181 = 0, o182 = 0; + uint16_t o281 = 0, o282 = 0; + LngObisDescriptor* descriptor = (LngObisDescriptor*) ptr; + for(uint8_t x = 0; x < h->arrayLength-1; x++) { + ptr = (uint8_t*) &descriptor[1]; + descriptor = (LngObisDescriptor*) ptr; + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(L&G) OBIS %d.%d.%d with type 0x%02X", descriptor->obis[2], descriptor->obis[3], descriptor->obis[4], *data); + + CosemData* item = (CosemData*) data; + if(descriptor->obis[2] == 1) { + if(descriptor->obis[3] == 7) { + if(descriptor->obis[4] == 0) { + o170 = ntohl(item->dlu.data); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } + } else if(descriptor->obis[3] == 8) { + if(descriptor->obis[4] == 0) { + activeImportCounter = ntohl(item->dlu.data) / 1000.0; + listType = listType >= 3 ? listType : 3; + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } else if(descriptor->obis[4] == 1) { + o181 = ntohl(item->dlu.data); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } else if(descriptor->obis[4] == 2) { + o182 = ntohl(item->dlu.data); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } + } + } else if(descriptor->obis[2] == 2) { + if(descriptor->obis[3] == 7) { + if(descriptor->obis[4] == 0) { + o270 = ntohl(item->dlu.data); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } + } else if(descriptor->obis[3] == 8) { + if(descriptor->obis[4] == 0) { + activeExportCounter = ntohl(item->dlu.data) / 1000.0; + listType = listType >= 3 ? listType : 3; + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } else if(descriptor->obis[4] == 1) { + o281 = ntohl(item->dlu.data); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } else if(descriptor->obis[4] == 2) { + o282 = ntohl(item->dlu.data); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %d (dlu)", ntohl(item->dlu.data)); + } + } + } else if(descriptor->obis[2] == 96) { + if(descriptor->obis[3] == 1) { + if(descriptor->obis[4] == 0) { + char str[item->oct.length+1]; + memcpy(str, item->oct.data, item->oct.length); + str[item->oct.length] = '\0'; + meterId = String(str); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %s (oct)", str); + } else if(descriptor->obis[4] == 1) { + char str[item->oct.length+1]; + memcpy(str, item->oct.data, item->oct.length); + str[item->oct.length] = '\0'; + meterModel = String(str); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %s (oct)", str); + } + } + } + + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("\n"); + + if(o170 > 0 || o270 > 0) { + int32_t sum = o170-o270; + if(sum > 0) { + listType = listType >= 1 ? listType : 1; + activeImportPower = sum; + } else { + listType = listType >= 2 ? listType : 2; + activeExportPower = sum * -1; + } + } + + if(o181 > 0 || o182 > 0) { + activeImportCounter = (o181 + o182) / 1000.0; + listType = listType >= 3 ? listType : 3; + } + if(o281 > 0 || o282 > 0) { + activeExportCounter = (o281 + o282) / 1000.0; + listType = listType >= 3 ? listType : 3; + } + + if((*data) == 0x09) { + data += (*(data+1))+2; + } else { + data += 5; + } + + lastUpdateMillis = millis(); + } + } +} \ No newline at end of file diff --git a/src/LNG.h b/src/LNG.h new file mode 100644 index 00000000..f448b105 --- /dev/null +++ b/src/LNG.h @@ -0,0 +1,30 @@ +#ifndef _LNG_H +#define _LNG_H + +#include "AmsData.h" +#include "AmsConfiguration.h" +#include "ams/DataParser.h" +#include "RemoteDebug.h" + +struct LngHeader { + uint8_t tag; + uint8_t values; + uint8_t arrayTag; + uint8_t arrayLength; +} __attribute__((packed)); + +struct LngObisDescriptor { + uint8_t ignore1[5]; + uint8_t octetTag; + uint8_t octetLength; + uint8_t obis[6]; + uint8_t ignore2[5]; +} __attribute__((packed)); + + +class LNG : public AmsData { +public: + LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger); +}; + +#endif diff --git a/src/mqtt/AmsMqttHandler.h b/src/mqtt/AmsMqttHandler.h index e5364e4e..4ec71634 100644 --- a/src/mqtt/AmsMqttHandler.h +++ b/src/mqtt/AmsMqttHandler.h @@ -19,7 +19,7 @@ public: virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); virtual bool publishTemperatures(AmsConfiguration*, HwTools*); virtual bool publishPrices(EntsoeApi* eapi); - virtual bool publishSystem(HwTools*); + virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*); protected: MQTTClient* mqtt; diff --git a/src/mqtt/DomoticzMqttHandler.cpp b/src/mqtt/DomoticzMqttHandler.cpp index 11975d34..d173e0dd 100644 --- a/src/mqtt/DomoticzMqttHandler.cpp +++ b/src/mqtt/DomoticzMqttHandler.cpp @@ -71,6 +71,6 @@ bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) { return false; } -bool DomoticzMqttHandler::publishSystem(HwTools* hw) { +bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) { return false; } diff --git a/src/mqtt/DomoticzMqttHandler.h b/src/mqtt/DomoticzMqttHandler.h index 145bffe5..2c1d68b0 100644 --- a/src/mqtt/DomoticzMqttHandler.h +++ b/src/mqtt/DomoticzMqttHandler.h @@ -12,7 +12,7 @@ public: bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishPrices(EntsoeApi*); - bool publishSystem(HwTools*); + bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); private: DomoticzConfig config; diff --git a/src/mqtt/HomeAssistantMqttHandler.cpp b/src/mqtt/HomeAssistantMqttHandler.cpp index 85ad96d1..ff273e3e 100644 --- a/src/mqtt/HomeAssistantMqttHandler.cpp +++ b/src/mqtt/HomeAssistantMqttHandler.cpp @@ -9,6 +9,7 @@ #include "web/root/jsonsys_json.h" #include "web/root/jsonprices_json.h" #include "web/root/hadiscover_json.h" +#include "web/root/realtime_json.h" bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) { if(topic.isEmpty() || !mqtt->connected()) @@ -32,7 +33,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower() ); - return mqtt->publish(topic + "/power", json); + mqtt->publish(topic + "/power", json); } else if(data->getListType() >= 2) { // publish power counts and volts/amps snprintf_P(json, BufferSize, HA3_JSON, data->getListId().c_str(), @@ -53,9 +54,33 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(), data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor() ); - return mqtt->publish(topic + "/power", json); + mqtt->publish(topic + "/power", json); } - return false; + + String peaks = ""; + uint8_t peakCount = ea->getConfig()->hours; + if(peakCount > 5) peakCount = 5; + for(uint8_t i = 1; i <= peakCount; i++) { + if(!peaks.isEmpty()) peaks += ","; + peaks += String(ea->getPeak(i), 2); + } + snprintf_P(json, BufferSize, REALTIME_JSON, + ea->getMonthMax(), + peaks.c_str(), + ea->getCurrentThreshold(), + ea->getUseThisHour(), + ea->getCostThisHour(), + ea->getProducedThisHour(), + ea->getUseToday(), + ea->getCostToday(), + ea->getProducedToday(), + ea->getUseThisMonth(), + ea->getCostThisMonth(), + ea->getProducedThisMonth() + ); + mqtt->publish(topic + "/realtime", json); + + return true; } bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) { @@ -187,10 +212,10 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) { ts3hr, ts6hr ); - return mqtt->publish(topic + "/prices", json); + return mqtt->publish(topic + "/prices", json, true, 0); } -bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) { +bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) { if(topic.isEmpty() || !mqtt->connected()){ sequence = 0; return false; @@ -203,7 +228,8 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) { (uint32_t) (millis64()/1000), hw->getVcc(), hw->getWifiRssi(), - hw->getTemperature() + hw->getTemperature(), + VERSION ); mqtt->publish(topic + "/state", json); } @@ -216,27 +242,46 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) { #endif String haUrl = "http://" + haUID + ".local/"; // Could this be necessary? haUID.replace("-", "_"); + uint8_t peakCount = ea->getConfig()->hours; + if(peakCount > 5) peakCount = 5; - for(int i=0;igetCurrency()); + } + if(strncmp(sensor.path, "peaks[", 6) == 0) { + if(peaks >= peakCount) continue; + peaks++; + } snprintf_P(json, BufferSize, HADISCOVER_JSON, - FPSTR(HA_NAMES[i]), - topic.c_str(), FPSTR(HA_TOPICS[i]), - haUID.c_str(), FPSTR(HA_PARAMS[i]), - haUID.c_str(), FPSTR(HA_PARAMS[i]), - FPSTR(HA_UOM[i]), - FPSTR(HA_PARAMS[i]), - FPSTR(HA_DEVCL[i]), + FPSTR(sensor.name), + topic.c_str(), FPSTR(sensor.topic), + haUID.c_str(), uid.c_str(), + haUID.c_str(), uid.c_str(), + uom.c_str(), + FPSTR(sensor.path), + FPSTR(sensor.devcl), haUID.c_str(), haName.c_str(), haModel.c_str(), VERSION, haManuf.c_str(), haUrl.c_str(), - strlen_P(HA_STACL[i]) > 0 ? ", \"stat_cla\" :" : "", - strlen_P(HA_STACL[i]) > 0 ? (char *) FPSTR(HA_STACL[i]) : "" + strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "", + strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : "" ); - mqtt->publish(haTopic + haUID + "_" + FPSTR(HA_PARAMS[i]) + "/config", json, true, 0); + mqtt->publish(haTopic + haUID + "_" + uid.c_str() + "/config", json, true, 0); } + autodiscoverInit = true; } if(listType>0) sequence++; diff --git a/src/mqtt/HomeAssistantMqttHandler.h b/src/mqtt/HomeAssistantMqttHandler.h index d4b8f50e..05675eee 100644 --- a/src/mqtt/HomeAssistantMqttHandler.h +++ b/src/mqtt/HomeAssistantMqttHandler.h @@ -13,11 +13,9 @@ public: bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishPrices(EntsoeApi*); - bool publishSystem(HwTools*); + bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); private: - static const uint8_t sensors = 17; - String haTopic = "homeassistant/sensor/"; String haName = "AMS reader"; diff --git a/src/mqtt/HomeAssistantStatic.h b/src/mqtt/HomeAssistantStatic.h index 5f5b5b26..7428959a 100644 --- a/src/mqtt/HomeAssistantStatic.h +++ b/src/mqtt/HomeAssistantStatic.h @@ -3,12 +3,69 @@ #include "Arduino.h" -const char* HA_TOPICS[17] PROGMEM = {"/state", "/state", "/state", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/energy", "/energy", "/energy", "/energy"}; -const char* HA_NAMES[17] PROGMEM = {"Status", "Supply volt", "Temperature", "Active import", "Reactive import", "Active export", "Reactive export", "L1 current", "L2 current", "L3 current", - "L1 voltage", "L2 voltage", "L3 voltage", "Accumulated active import", "Accumulated active export", "Accumulated reactive import", "Accumulated reactive export"}; -const char* HA_PARAMS[17] PROGMEM = {"rssi", "vcc", "temp", "P", "Q", "PO", "QO", "I1", "I2", "I3", "U1", "U2", "U3", "tPI", "tPO", "tQI", "tQO"}; -const char* HA_UOM[17] PROGMEM = {"dBm", "V", "C", "W", "W", "W", "W", "A", "A", "A", "V", "V", "V", "kWh", "kWh", "kWh", "kWh"}; -const char* HA_DEVCL[17] PROGMEM = {"signal_strength", "voltage", "temperature", "power", "power", "power", "power", "current", "current", "current", "voltage", "voltage", "voltage", "energy", "energy", "energy", "energy"}; -const char* HA_STACL[17] PROGMEM = {"", "", "", "\"measurement\"", "\"measurement\"", "\"measurement\"", "\"measurement\"", "", "", "", "", "", "", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\""}; +struct HomeAssistantSensor { + char* name; + char* topic; + char* path; + char* uom; + char* devcl; + char* stacl; +}; + + +const uint8_t HA_SENSOR_COUNT PROGMEM = 50; +HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = { + {"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""}, + {"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""}, + {"Temperature", "/state", "temp", "C", "temperature", "\"measurement\""}, + {"Active import", "/power", "P", "W", "power", "\"measurement\""}, + {"Reactive import", "/power", "Q", "VAr", "reactive_power", "\"measurement\""}, + {"Active export", "/power", "PO", "W", "power", "\"measurement\""}, + {"Reactive export", "/power", "QO", "VAr", "reactive_power", "\"measurement\""}, + {"L1 current", "/power", "I1", "A", "current", "\"measurement\""}, + {"L2 current", "/power", "I2", "A", "current", "\"measurement\""}, + {"L3 current", "/power", "I3", "A", "current", "\"measurement\""}, + {"L1 voltage", "/power", "U1", "V", "voltage", "\"measurement\""}, + {"L2 voltage", "/power", "U2", "V", "voltage", "\"measurement\""}, + {"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""}, + {"Accumulated active import", "/energy", "tPI", "kWh", "energy", "\"total_increasing\""}, + {"Accumulated active export", "/energy", "tPO", "kWh", "energy", "\"total_increasing\""}, + {"Accumulated reactive import","/energy", "tQI", "kVArh","energy", "\"total_increasing\""}, + {"Accumulated reactive export","/energy", "tQO", "kVArh","energy", "\"total_increasing\""}, + {"Price current hour", "/prices", "prices['0']", "", "monetary", ""}, + {"Price next hour", "/prices", "prices['1']", "", "monetary", ""}, + {"Price in two hour", "/prices", "prices['2']", "", "monetary", ""}, + {"Price in three hour", "/prices", "prices['3']", "", "monetary", ""}, + {"Price in four hour", "/prices", "prices['4']", "", "monetary", ""}, + {"Price in five hour", "/prices", "prices['5']", "", "monetary", ""}, + {"Price in six hour", "/prices", "prices['6']", "", "monetary", ""}, + {"Price in seven hour", "/prices", "prices['7']", "", "monetary", ""}, + {"Price in eight hour", "/prices", "prices['8']", "", "monetary", ""}, + {"Price in nine hour", "/prices", "prices['9']", "", "monetary", ""}, + {"Price in ten hour", "/prices", "prices['10']", "", "monetary", ""}, + {"Price in eleven hour", "/prices", "prices['11']", "", "monetary", ""}, + {"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""}, + {"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""}, + {"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""}, + {"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""}, + {"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""}, + {"Month max", "/realtime","max", "kWh", "energy", "\"total_increasing\""}, + {"Tariff threshold", "/realtime","threshold", "kWh", "energy", "\"total_increasing\""}, + {"Current hour used", "/realtime","hour.use", "kWh", "energy", "\"total_increasing\""}, + {"Current hour cost", "/realtime","hour.cost", "", "monetary", "\"total_increasing\""}, + {"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "\"total_increasing\""}, + {"Current day used", "/realtime","day.use", "kWh", "energy", "\"total_increasing\""}, + {"Current day cost", "/realtime","day.cost", "", "monetary", "\"total_increasing\""}, + {"Current day produced", "/realtime","day.produced", "kWh", "energy", "\"total_increasing\""}, + {"Current month used", "/realtime","month.use", "kWh", "energy", "\"total_increasing\""}, + {"Current month cost", "/realtime","month.cost", "", "monetary", "\"total_increasing\""}, + {"Current month produced", "/realtime","month.produced", "kWh", "energy", "\"total_increasing\""}, + {"Current month peak 1", "/realtime","peaks[0]", "kWh", "energy", ""}, + {"Current month peak 2", "/realtime","peaks[1]", "kWh", "energy", ""}, + {"Current month peak 3", "/realtime","peaks[2]", "kWh", "energy", ""}, + {"Current month peak 4", "/realtime","peaks[3]", "kWh", "energy", ""}, + {"Current month peak 5", "/realtime","peaks[4]", "kWh", "energy", ""}, +}; + #endif diff --git a/src/mqtt/JsonMqttHandler.cpp b/src/mqtt/JsonMqttHandler.cpp index 82847182..6329b389 100644 --- a/src/mqtt/JsonMqttHandler.cpp +++ b/src/mqtt/JsonMqttHandler.cpp @@ -1,4 +1,5 @@ #include "JsonMqttHandler.h" +#include "version.h" #include "hexutils.h" #include "Uptime.h" #include "web/root/json1_json.h" @@ -270,7 +271,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) { return mqtt->publish(topic, json); } -bool JsonMqttHandler::publishSystem(HwTools* hw) { +bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) { if(init || topic.isEmpty() || !mqtt->connected()) return false; @@ -280,7 +281,8 @@ bool JsonMqttHandler::publishSystem(HwTools* hw) { (uint32_t) (millis64()/1000), hw->getVcc(), hw->getWifiRssi(), - hw->getTemperature() + hw->getTemperature(), + VERSION ); init = mqtt->publish(topic, json); return init; diff --git a/src/mqtt/JsonMqttHandler.h b/src/mqtt/JsonMqttHandler.h index b799bbbe..db3e12bc 100644 --- a/src/mqtt/JsonMqttHandler.h +++ b/src/mqtt/JsonMqttHandler.h @@ -13,7 +13,7 @@ public: bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishPrices(EntsoeApi*); - bool publishSystem(HwTools*); + bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); private: String clientId; diff --git a/src/mqtt/RawMqttHandler.cpp b/src/mqtt/RawMqttHandler.cpp index dffdb8a6..c6763ed3 100644 --- a/src/mqtt/RawMqttHandler.cpp +++ b/src/mqtt/RawMqttHandler.cpp @@ -74,6 +74,9 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin } mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3)); mqtt->publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2)); + for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) { + mqtt->publish(topic + "/realtime/import/peak/" + String(i, 10), String(ea->getPeak(i), 10), true, 0); + } mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0); mqtt->publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0); mqtt->publish(topic + "/realtime/export/hour", String(ea->getProducedThisHour(), 3)); @@ -208,7 +211,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) { return true; } -bool RawMqttHandler::publishSystem(HwTools* hw) { +bool RawMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) { if(topic.isEmpty() || !mqtt->connected()) return false; diff --git a/src/mqtt/RawMqttHandler.h b/src/mqtt/RawMqttHandler.h index 091103cb..737b72a3 100644 --- a/src/mqtt/RawMqttHandler.h +++ b/src/mqtt/RawMqttHandler.h @@ -12,7 +12,7 @@ public: bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishPrices(EntsoeApi*); - bool publishSystem(HwTools*); + bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); private: String topic; diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index b686d9ce..a02c7a83 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -90,7 +90,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter server.on("/debugging", HTTP_GET, std::bind(&AmsWebServer::configDebugHtml, this)); server.on("/firmware", HTTP_GET, std::bind(&AmsWebServer::firmwareHtml, this)); - server.on("/firmware", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::firmwareUpload, this)); + server.on("/firmware", HTTP_POST, std::bind(&AmsWebServer::firmwarePost, this), std::bind(&AmsWebServer::firmwareUpload, this)); server.on("/upgrade", HTTP_GET, std::bind(&AmsWebServer::firmwareDownload, this)); server.on("/restart", HTTP_GET, std::bind(&AmsWebServer::restartHtml, this)); server.on("/restart", HTTP_POST, std::bind(&AmsWebServer::restartPost, this)); @@ -336,6 +336,9 @@ void AmsWebServer::configMeterHtml() { case AmsTypeSagemcom: manufacturer = "Sagemcom"; break; + case AmsTypeLng: + manufacturer = "L&G"; + break; default: manufacturer = "Unknown"; break; @@ -552,6 +555,12 @@ void AmsWebServer::configPriceApiHtml() { if(ESP.getFreeHeap() > 32000) { html.replace("{et}", entsoe.token); html.replace("{dt}", ""); + html.replace("{em}", String(entsoe.multiplier / 1000.0, 3)); + + server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); + server.send_P(200, MIME_HTML, HEAD_HTML); + server.sendContent(html); + server.sendContent_P(FOOT_HTML); } else { html.replace("{et}", ""); html.replace("{dt}", "d-none"); @@ -572,6 +581,20 @@ void AmsWebServer::configPriceApiHtml() { html.replace("{eaDk1}", strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : ""); html.replace("{eaDk2}", strcmp(entsoe.area, "10YDK-2--------M") == 0 ? "selected" : ""); + html.replace("{at}", strcmp(entsoe.area, "10YAT-APG------L") == 0 ? "selected" : ""); + html.replace("{be}", strcmp(entsoe.area, "10YBE----------2") == 0 ? "selected" : ""); + html.replace("{cz}", strcmp(entsoe.area, "10YCZ-CEPS-----N") == 0 ? "selected" : ""); + html.replace("{ee}", strcmp(entsoe.area, "10Y1001A1001A39I") == 0 ? "selected" : ""); + html.replace("{fi}", strcmp(entsoe.area, "10YFI-1--------U") == 0 ? "selected" : ""); + html.replace("{fr}", strcmp(entsoe.area, "10YFR-RTE------C") == 0 ? "selected" : ""); + html.replace("{de}", strcmp(entsoe.area, "10Y1001A1001A83F") == 0 ? "selected" : ""); + html.replace("{gb}", strcmp(entsoe.area, "10YGB----------A") == 0 ? "selected" : ""); + html.replace("{lv}", strcmp(entsoe.area, "10YLV-1001A00074") == 0 ? "selected" : ""); + html.replace("{lt}", strcmp(entsoe.area, "10YLT-1001A0008Q") == 0 ? "selected" : ""); + html.replace("{nl}", strcmp(entsoe.area, "10YNL----------L") == 0 ? "selected" : ""); + html.replace("{pl}", strcmp(entsoe.area, "10YPL-AREA-----S") == 0 ? "selected" : ""); + html.replace("{ch}", strcmp(entsoe.area, "10YCH-SWISSGRIDZ") == 0 ? "selected" : ""); + html.replace("{ecNOK}", strcmp(entsoe.currency, "NOK") == 0 ? "selected" : ""); html.replace("{ecSEK}", strcmp(entsoe.currency, "SEK") == 0 ? "selected" : ""); html.replace("{ecDKK}", strcmp(entsoe.currency, "DKK") == 0 ? "selected" : ""); @@ -593,9 +616,9 @@ void AmsWebServer::configThresholdsHtml() { String html = String((const __FlashStringHelper*) THRESHOLDS_HTML); for(int i = 0; i < 9; i++) { - html.replace("{t" + String(i) + "}", String(config->thresholds[i])); + html.replace("{t" + String(i) + "}", String(config->thresholds[i], 10)); } - html.replace("{h}", String(config->hours)); + html.replace("{h}", String(config->hours, 10)); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, MIME_HTML, HEAD_HTML); @@ -710,6 +733,12 @@ void AmsWebServer::dataJson() { if(eapi != NULL) price = eapi->getValueForHour(0); + String peaks = ""; + for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) { + if(!peaks.isEmpty()) peaks += ","; + peaks += String(ea->getPeak(i)); + } + snprintf_P(buf, BufferSize, DATA_JSON, maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr, meterConfig->productionCapacity, @@ -746,6 +775,7 @@ void AmsWebServer::dataJson() { meterState->getMeterType(), meterConfig->distributionSystem, ea->getMonthMax(), + peaks.c_str(), ea->getCurrentThreshold(), ea->getUseThisHour(), ea->getCostThisHour(), @@ -1644,13 +1674,40 @@ void AmsWebServer::firmwareHtml() { server.sendContent_P(FOOT_HTML); } +void AmsWebServer::firmwarePost() { + printD("Handlling firmware post..."); + if(!checkSecurity(1)) + return; + + if(rebootForUpgrade) { + server.send(200); + } else { + if(server.hasArg("url")) { + String url = server.arg("url"); + if(!url.isEmpty() && (url.startsWith("http://") || url.startsWith("https://"))) { + printD("Custom firmware URL was provided"); + customFirmwareUrl = url; + performUpgrade = true; + server.sendHeader("Location","/restart-wait"); + server.send(303); + return; + } + } + server.sendHeader("Location","/firmware"); + server.send(303); + } +} + void AmsWebServer::firmwareUpload() { + printD("Handlling firmware upload..."); if(!checkSecurity(1)) return; HTTPUpload& upload = server.upload(); + String filename = upload.filename; + if(filename.isEmpty()) return; + if(upload.status == UPLOAD_FILE_START) { - String filename = upload.filename; if(!filename.endsWith(".bin")) { server.send(500, MIME_PLAIN, "500: couldn't create file"); } else { @@ -1757,7 +1814,7 @@ void AmsWebServer::restartWaitHtml() { performRestart = false; } else if(performUpgrade) { WiFiClient client; - String url = "http://ams2mqtt.rewiredinvent.no/hub/firmware/update"; + String url = customFirmwareUrl.isEmpty() || !customFirmwareUrl.startsWith("http") ? "http://ams2mqtt.rewiredinvent.no/hub/firmware/update" : customFirmwareUrl; #if defined(ESP8266) String chipType = "esp8266"; #elif defined(CONFIG_IDF_TARGET_ESP32S2) @@ -1771,9 +1828,11 @@ void AmsWebServer::restartWaitHtml() { #endif #if defined(ESP8266) + ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION); #elif defined(ESP32) HTTPUpdate httpUpdate; + httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); HTTPUpdateResult ret = httpUpdate.update(client, url, String(VERSION) + "-" + chipType); #endif diff --git a/src/web/AmsWebServer.h b/src/web/AmsWebServer.h index bee1b1f0..7136c698 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -61,6 +61,7 @@ private: bool performRestart = false; bool performUpgrade = false; bool rebootForUpgrade = false; + String customFirmwareUrl; static const uint16_t BufferSize = 2048; char* buf; @@ -104,6 +105,7 @@ private: String getSerialSelectOptions(int selected); void firmwareHtml(); + void firmwarePost(); void firmwareUpload(); void firmwareDownload(); void restartHtml(); diff --git a/web/application.js b/web/application.js index 04d603d6..152bc726 100644 --- a/web/application.js +++ b/web/application.js @@ -316,6 +316,7 @@ $(function() { url: swv.data('url'), dataType: 'json' }).done(function(releases) { + var isnew = false; if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) { releases.reverse(); var next_patch; @@ -352,10 +353,13 @@ $(function() { }); if(next_minor) { nextVersion = next_minor; + isnew = true; } else if(next_major) { nextVersion = next_major; + isnew = true; } else if(next_patch) { nextVersion = next_patch; + isnew = true; } } else { nextVersion = releases[0]; @@ -375,9 +379,11 @@ $(function() { } }); }; - $('#newVersionTag').text(nextVersion.tag_name); - $('#newVersionUrl').prop('href', nextVersion.html_url); - $('#newVersion').removeClass('d-none'); + if(isnew) { + $('#newVersionTag').text(nextVersion.tag_name); + $('#newVersionUrl').prop('href', nextVersion.html_url); + $('#newVersion').removeClass('d-none'); + } } }); } @@ -884,7 +890,7 @@ var fetch = function() { var upgrade = function() { if(nextVersion) { - if(confirm("WARNING: Please keep USB power connected while upgrading. Are you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) { + if(confirm("WARNING: If you have a M-BUS powered device (Pow-U), please keep USB power connected while upgrading.\n\nAre you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) { $('#loading-indicator').show(); window.location.href="/upgrade?version=" + nextVersion.tag_name; } diff --git a/web/data.json b/web/data.json index 0265bb0f..0581d6ac 100644 --- a/web/data.json +++ b/web/data.json @@ -35,6 +35,7 @@ "ds" : %d, "ea" : { "x" : %.1f, + "p" : [ %s ], "t" : %d, "h" : { "u" : %.2f, diff --git a/web/firmware.html b/web/firmware.html index 0ed23db2..0bdbf086 100644 --- a/web/firmware.html +++ b/web/firmware.html @@ -1,18 +1,21 @@
- WARNING: Units powered over M-bus must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit. + WARNING: Units powered by M-BUS (Pow-U) must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result in non-functioning unit.
+
+ When using URL, only a valid ESP OTA server response will be accepted. +
-
+
- Upload + Upload file
@@ -21,6 +24,19 @@
+
+
or
+
+
+
+
+
+ Use URL +
+ +
+
+

@@ -28,7 +44,7 @@ Back
- +
- + \ No newline at end of file diff --git a/web/jsonsys.json b/web/jsonsys.json index 9c1bdccb..55d0b443 100644 --- a/web/jsonsys.json +++ b/web/jsonsys.json @@ -4,5 +4,6 @@ "up" : %d, "vcc" : %.3f, "rssi": %d, - "temp": %.2f + "temp": %.2f, + "version": "%s" } diff --git a/web/priceapi.html b/web/priceapi.html index 217fdc71..bd41e398 100644 --- a/web/priceapi.html +++ b/web/priceapi.html @@ -27,6 +27,19 @@ + + + + + + + + + + + + +
diff --git a/web/realtime.json b/web/realtime.json new file mode 100644 index 00000000..06ca2d73 --- /dev/null +++ b/web/realtime.json @@ -0,0 +1,20 @@ +{ + "max" : %.1f, + "peaks" : [ %s ], + "threshold" : %d, + "hour" : { + "use" : %.2f, + "cost" : %.2f, + "produced" : %.2f + }, + "day" : { + "use" : %.2f, + "cost" : %.2f, + "produced" : %.2f + }, + "month" : { + "use" : %.2f, + "cost" : %.2f, + "produced" : %.2f + } +} \ No newline at end of file