Compare commits

..

44 Commits

Author SHA1 Message Date
Gunnar Skjold
32fa2f5632 Merge branch 'master' into dev-v2.1.8 2022-10-06 17:30:49 +02:00
Gunnar Skjold
026cd25c8c Setting modem sleep to max 2022-10-06 17:30:37 +02:00
Gunnar Skjold
12be475b02 Reverted HA changes for 2.1.8 release 2022-10-06 17:29:01 +02:00
Gunnar Skjold
06ec97b42a Make sure we dont use more than 5 peaks if defined higher 2022-10-06 17:20:13 +02:00
Gunnar Skjold
b420a0e6f4 Limit peak count if over 5 2022-10-06 17:12:12 +02:00
Gunnar Skjold
fe7be81f1e Rudimentary detection for L&G data. Probably change this in the future 2022-10-01 12:11:46 +02:00
Gunnar Skjold
7674fc2ad0 Merge branch 'lng-parser' 2022-10-01 11:56:08 +02:00
Gunnar Skjold
d50181c347 Upgrade with custom URL 2022-10-01 11:43:42 +02:00
Gunnar Skjold
8ca771fa5a Changed "BUS" to "M-BUS" 2022-10-01 09:25:56 +02:00
Gunnar Skjold
7d557b2679 Only show new version when actually new in firmware page. Also be more specific on USB power in upgrade warning 2022-10-01 09:24:07 +02:00
Gunnar Skjold
2f0c912388 Exclude decimals in BUILD_EPOCH flag 2022-10-01 09:22:09 +02:00
Gunnar Skjold
57e6d0fbe3 Modifications to make price sensors work in HA 2022-09-30 19:18:23 +02:00
Gunnar Skjold
e7c25fafda Prices and realtime HA sensors 2022-09-28 20:13:03 +02:00
Gunnar Skjold
2a4772fe25 Fixed memory leak when reconnecting to MQTT/SSL 2022-09-27 19:59:37 +02:00
Gunnar Skjold
f1f7408208 Added price zones 2022-09-25 11:46:04 +02:00
Gunnar Skjold
feb8e5007b Fixed MQTT buffer size 2022-09-25 10:50:34 +02:00
Gunnar Skjold
c4af1ee74f Fixed saving tariff thresholds 2022-09-25 10:50:09 +02:00
Gunnar Skjold
992e1b6121 Net value for active power (L&G) 2022-09-12 08:03:32 +02:00
Gunnar Skjold
9cc7529934 Fixed L&G scaling 2022-08-31 08:19:09 +02:00
Gunnar Skjold
ef8715be6d Merge branch 'master' into lng-parser 2022-08-31 08:04:24 +02:00
Gunnar Skjold
e232b875fa Fixed Aidon timestamp 2022-08-23 21:07:06 +02:00
Gunnar Skjold
f18171fecc Fixed MQTT byte mode 2022-08-23 20:39:47 +02:00
Gunnar Skjold
a3c7a09211 Adding peaks to data.json and MQTT RAW 2022-08-23 20:21:19 +02:00
Gunnar Skjold
a6f3bc3f71 Added version to system json 2022-08-23 19:49:19 +02:00
Gunnar Skjold
44bcd386d1 aggregate obis 1.8.x and 2.8.x for L&G 2022-08-23 09:04:45 +02:00
Gunnar Skjold
01547f9a52 Adjustments to make L&G parser work 2022-08-19 13:26:29 +02:00
Gunnar Skjold
940d38af5c Merge branch 'master' into lng-parser 2022-08-17 17:33:24 +02:00
Gunnar Skjold
4e451c51e1 HW serial fix for S2 2022-08-17 17:33:06 +02:00
Gunnar Skjold
43def1c311 Fixed Pow-P1 profile 2022-08-17 17:32:53 +02:00
Gunnar Skjold
2978116207 Fixed formatting error in HA payload 2022-08-16 11:55:07 +02:00
Gunnar Skjold
a055465ce0 Untestet L&G data parser 2022-08-16 08:22:43 +02:00
Gunnar Skjold
998b986604 Adjusted modem sleep to a safer value 2022-08-12 20:48:34 +02:00
Gunnar Skjold
7799431405 Remove unused files 2022-08-12 20:42:13 +02:00
Gunnar Skjold
508c14a671 Fixed modem sleep on S2 and fixed default wifi power 2022-08-12 09:02:04 +02:00
Gunnar Skjold
4a7ef87269 Fixed realtime day calculation 2022-08-10 17:43:51 +02:00
Gunnar Skjold
3a4fc707b0 Show real time production 2022-08-10 10:53:16 +02:00
Gunnar Skjold
5d2e320b07 Fixed export labels on graph 2022-08-10 10:28:31 +02:00
Gunnar Skjold
d12613b91a Trying to fix HA accumulated 2022-08-10 10:01:24 +02:00
Gunnar Skjold
e6a02f34ab USB power warning when upgrading 2022-08-10 09:31:50 +02:00
Gunnar Skjold
0b2ffbfd77 Revert to arduino 2.0.3 because of #298 2022-08-10 09:30:31 +02:00
Gunnar Skjold
cab6c54ed9 Correct Vcc GPIO for Pow-K+ 2022-08-01 12:48:31 +02:00
Gunnar Skjold
313024f273 Support OTA upgrade from ESP8266 2022-08-01 08:17:24 +02:00
Gunnar Skjold
91fc078c5e Higher precision on prices 2022-07-31 20:26:38 +02:00
Gunnar Skjold
5b9d44a3e9 Fixed loading of energy accounting 2022-07-31 20:19:01 +02:00
37 changed files with 705 additions and 314 deletions

View File

@@ -43,3 +43,37 @@ FF // Last byte of OBIS in previous block
0600000000 // Accumulated export 0600000000 // Accumulated export
8BA4 8BA4
7E 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

View File

@@ -17,8 +17,10 @@ extra_scripts =
pre:scripts/addversion.py pre:scripts/addversion.py
scripts/makeweb.py scripts/makeweb.py
# Sticking to v2.0.3 because of #298
[env:esp32] [env:esp32]
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4/platform-espressif32-2.0.4.zip platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3/platform-espressif32-2.0.3.zip
framework = arduino framework = arduino
board = esp32dev board = esp32dev
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L
@@ -33,8 +35,8 @@ extra_scripts =
# https://github.com/Jason2866/esp32-arduino-lib-builder # https://github.com/Jason2866/esp32-arduino-lib-builder
[env:esp32s2] [env:esp32s2]
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4/platform-espressif32-2.0.4.zip platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3/platform-espressif32-2.0.3.zip
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.4 platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
framework = arduino framework = arduino
board = esp32dev board = esp32dev
board_build.mcu = esp32s2 board_build.mcu = esp32s2
@@ -50,7 +52,7 @@ extra_scripts =
scripts/makeweb.py scripts/makeweb.py
[env:esp32solo] [env:esp32solo]
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4/platform-espressif32-solo1-2.0.4.zip platform = https://github.com/tasmota/platform-espressif32/releases/download/v.2.0.3/platform-espressif32-solo1-v.2.0.3.zip
framework = arduino framework = arduino
board = esp32dev board = esp32dev
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L

View File

@@ -19,6 +19,6 @@ hf = """
#define VERSION "{}" #define VERSION "{}"
#endif #endif
#define BUILD_EPOCH {} #define BUILD_EPOCH {}
""".format(version, time()) """.format(version, round(time()))
with open(FILENAME_VERSION_H, 'w+') as f: with open(FILENAME_VERSION_H, 'w+') as f:
f.write(hf) f.write(hf)

View File

@@ -63,8 +63,10 @@ void AmsConfiguration::clearWifi(WiFiConfig& config) {
uint16_t chipId; uint16_t chipId;
#if defined(ESP32) #if defined(ESP32)
chipId = ESP.getEfuseMac(); chipId = ESP.getEfuseMac();
config.power = 195;
#else #else
chipId = ESP.getChipId(); chipId = ESP.getChipId();
config.power = 205;
#endif #endif
strcpy(config.hostname, (String("ams-") + String(chipId, HEX)).c_str()); strcpy(config.hostname, (String("ams-") + String(chipId, HEX)).c_str());
config.mdns = true; config.mdns = true;
@@ -515,6 +517,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
} }
bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) { bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) {
if(config.hours > 5) config.hours = 5;
EnergyAccountingConfig existing; EnergyAccountingConfig existing;
if(getEnergyAccountingConfig(existing)) { if(getEnergyAccountingConfig(existing)) {
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
@@ -523,6 +526,7 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config)
} }
} }
config.thresholds[9] = 255; config.thresholds[9] = 255;
energyAccountingChanged |= config.hours != existing.hours;
} else { } else {
energyAccountingChanged = true; energyAccountingChanged = true;
} }

View File

@@ -12,6 +12,7 @@ enum AmsType {
AmsTypeIskra = 0x08, AmsTypeIskra = 0x08,
AmsTypeLandis = 0x09, AmsTypeLandis = 0x09,
AmsTypeSagemcom = 0x0A, AmsTypeSagemcom = 0x0A,
AmsTypeLng = 0x0B,
AmsTypeCustom = 0x88, AmsTypeCustom = 0x88,
AmsTypeUnknown = 0xFF AmsTypeUnknown = 0xFF
}; };

View File

@@ -32,6 +32,10 @@ ADC_MODE(ADC_VCC);
#endif #endif
#define WDT_TIMEOUT 60 #define WDT_TIMEOUT 60
#if defined(CONFIG_IDF_TARGET_ESP32S2)
#include <driver/uart.h>
#endif
#include "version.h" #include "version.h"
#include "AmsToMqttBridge.h" #include "AmsToMqttBridge.h"
@@ -61,10 +65,11 @@ ADC_MODE(ADC_VCC);
#include "RemoteDebug.h" #include "RemoteDebug.h"
#define BUF_SIZE_COMMON (2048) #define BUF_SIZE_COMMON (2048)
#define BUF_SIZE_HAN (1024) #define BUF_SIZE_HAN (1280)
#include "IEC6205621.h" #include "IEC6205621.h"
#include "IEC6205675.h" #include "IEC6205675.h"
#include "LNG.h"
#include "ams/DataParsers.h" #include "ams/DataParsers.h"
@@ -148,6 +153,8 @@ void setup() {
gpioConfig.hanPin = 3; gpioConfig.hanPin = 3;
gpioConfig.ledPin = 2; gpioConfig.ledPin = 2;
gpioConfig.ledInverted = true; gpioConfig.ledInverted = true;
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
gpioConfig.hanPin = 18;
#elif defined(ESP32) #elif defined(ESP32)
gpioConfig.hanPin = 16; gpioConfig.hanPin = 16;
gpioConfig.ledPin = 2; gpioConfig.ledPin = 2;
@@ -155,6 +162,7 @@ void setup() {
gpioConfig.tempSensorPin = 14; gpioConfig.tempSensorPin = 14;
#endif #endif
} }
delay(1); delay(1);
config.loadTempSensors(); config.loadTempSensors();
hw.setup(&gpioConfig, &config); hw.setup(&gpioConfig, &config);
@@ -505,6 +513,7 @@ void loop() {
if (mqttEnabled || config.isMqttChanged()) { if (mqttEnabled || config.isMqttChanged()) {
if(mqtt == NULL || !mqtt->connected() || config.isMqttChanged()) { if(mqtt == NULL || !mqtt->connected() || config.isMqttChanged()) {
MQTT_connect(); MQTT_connect();
config.ackMqttChange();
} }
} else if(mqtt != NULL && mqtt->connected()) { } else if(mqtt != NULL && mqtt->connected()) {
mqttClient->stop(); mqttClient->stop();
@@ -586,7 +595,7 @@ void loop() {
} }
if(now - lastSysupdate > 10000) { if(now - lastSysupdate > 10000) {
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) { 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; lastSysupdate = now;
} }
@@ -616,9 +625,7 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
hwSerial = &Serial2; hwSerial = &Serial2;
} }
#elif defined(CONFIG_IDF_TARGET_ESP32S2) #elif defined(CONFIG_IDF_TARGET_ESP32S2)
if(pin == 18) { hwSerial = &Serial1;
hwSerial = &Serial1;
}
#elif defined(CONFIG_IDF_TARGET_ESP32C3) #elif defined(CONFIG_IDF_TARGET_ESP32C3)
#endif #endif
#endif #endif
@@ -651,7 +658,11 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
break; break;
} }
#if defined(ESP32) #if defined(CONFIG_IDF_TARGET_ESP32S2)
hwSerial->begin(baud, serialConfig, -1, -1, invert);
hwSerial->setRxBufferSize(768);
uart_set_pin(UART_NUM_1, UART_PIN_NO_CHANGE, pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
#elif defined(ESP32)
hwSerial->begin(baud, serialConfig, -1, -1, invert); hwSerial->begin(baud, serialConfig, -1, -1, invert);
hwSerial->setRxBufferSize(768); hwSerial->setRxBufferSize(768);
#else #else
@@ -826,6 +837,7 @@ bool readHanPort() {
len += hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len); len += hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len);
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
mqtt->publish(topic.c_str(), toHex(hanBuffer+pos, len)); 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 while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above
len = 0; len = 0;
@@ -838,19 +850,26 @@ bool readHanPort() {
} }
AmsData data; AmsData data;
char* payload = ((char *) (hanBuffer)) + pos;
if(ctx.type == DATA_TAG_DLMS) { if(ctx.type == DATA_TAG_DLMS) {
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT // If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { 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:"); 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 // Rudimentary detector for L&G proprietary format
data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, ctx); 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) { } else if(ctx.type == DATA_TAG_DSMR) {
data = IEC6205621(((char *) (hanBuffer)) + pos); data = IEC6205621(payload);
} }
len = 0; len = 0;
@@ -1032,6 +1051,7 @@ void WiFi_connect() {
} }
#endif #endif
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.setSleep(WIFI_PS_MAX_MODEM);
#if defined(ESP32) #if defined(ESP32)
if(wifi.power >= 195) if(wifi.power >= 195)
WiFi.setTxPower(WIFI_POWER_19_5dBm); WiFi.setTxPower(WIFI_POWER_19_5dBm);
@@ -1055,6 +1075,8 @@ void WiFi_connect() {
WiFi.setTxPower(WIFI_POWER_5dBm); WiFi.setTxPower(WIFI_POWER_5dBm);
else if(wifi.power >= 20) else if(wifi.power >= 20)
WiFi.setTxPower(WIFI_POWER_2dBm); WiFi.setTxPower(WIFI_POWER_2dBm);
else
WiFi.setTxPower(WIFI_POWER_MINUS_1dBm);
#elif defined(ESP8266) #elif defined(ESP8266)
WiFi.setOutputPower(wifi.power / 10.0); WiFi.setOutputPower(wifi.power / 10.0);
#endif #endif
@@ -1163,6 +1185,7 @@ int16_t unwrapData(uint8_t *buf, DataParserContext &context) {
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT // If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
mqtt->publish(topic.c_str(), toHex(buf, curLen)); mqtt->publish(topic.c_str(), toHex(buf, curLen));
mqtt->loop();
} }
break; break;
case DATA_TAG_MBUS: case DATA_TAG_MBUS:
@@ -1170,6 +1193,7 @@ int16_t unwrapData(uint8_t *buf, DataParserContext &context) {
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT // If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
mqtt->publish(topic.c_str(), toHex(buf, curLen)); mqtt->publish(topic.c_str(), toHex(buf, curLen));
mqtt->loop();
} }
break; break;
case DATA_TAG_GBT: case DATA_TAG_GBT:
@@ -1188,6 +1212,7 @@ int16_t unwrapData(uint8_t *buf, DataParserContext &context) {
debugV("DSMR frame:"); debugV("DSMR frame:");
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
mqtt->publish(topic.c_str(), (char*) buf); mqtt->publish(topic.c_str(), (char*) buf);
mqtt->loop();
} }
break; break;
} }
@@ -1226,7 +1251,6 @@ void MQTT_connect() {
if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config"); if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config");
mqttEnabled = false; mqttEnabled = false;
ws.setMqttEnabled(false); ws.setMqttEnabled(false);
config.ackMqttChange();
return; return;
} }
if(mqtt != NULL) { if(mqtt != NULL) {
@@ -1241,20 +1265,19 @@ void MQTT_connect() {
} }
mqtt->disconnect(); mqtt->disconnect();
if(config.isMqttChanged()) {
if(mqttSecureClient != NULL) {
mqttSecureClient->stop();
delete mqttSecureClient;
mqttSecureClient = NULL;
} else {
mqttClient->stop();
}
mqttClient = NULL;
}
yield(); yield();
} else { } else {
uint16_t size = 256; mqtt = new MQTTClient(1024);
switch(mqttConfig.payloadFormat) {
case 0: // JSON
case 4: // Home Assistant
size = 768;
break;
case 255: // Raw frame
size = 1024;
break;
}
mqtt = new MQTTClient(size);
ws.setMqtt(mqtt); ws.setMqtt(mqtt);
} }
@@ -1289,54 +1312,54 @@ void MQTT_connect() {
debugI("MQTT SSL is configured (%dkb free heap)", ESP.getFreeHeap()); debugI("MQTT SSL is configured (%dkb free heap)", ESP.getFreeHeap());
if(mqttSecureClient == NULL) { if(mqttSecureClient == NULL) {
mqttSecureClient = new WiFiClientSecure(); mqttSecureClient = new WiFiClientSecure();
} #if defined(ESP8266)
#if defined(ESP8266) mqttSecureClient->setBufferSizes(512, 512);
mqttSecureClient->setBufferSizes(512, 512); #endif
#endif
if(LittleFS.begin()) {
if(LittleFS.begin()) { File file;
File file;
if(LittleFS.exists(FILE_MQTT_CA)) { if(LittleFS.exists(FILE_MQTT_CA)) {
debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap()); debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CA, "r"); file = LittleFS.open(FILE_MQTT_CA, "r");
#if defined(ESP8266) #if defined(ESP8266)
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file); BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
mqttSecureClient->setTrustAnchors(serverTrustedCA); mqttSecureClient->setTrustAnchors(serverTrustedCA);
#elif defined(ESP32) #elif defined(ESP32)
mqttSecureClient->loadCACert(file, file.size()); mqttSecureClient->loadCACert(file, file.size());
#endif #endif
file.close(); 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; mqttClient = mqttSecureClient;
} else if(mqttClient == NULL) { } else if(mqttClient == NULL) {
@@ -1361,10 +1384,9 @@ void MQTT_connect() {
if ((strlen(mqttConfig.username) == 0 && mqtt->connect(mqttConfig.clientId)) || if ((strlen(mqttConfig.username) == 0 && mqtt->connect(mqttConfig.clientId)) ||
(strlen(mqttConfig.username) > 0 && mqtt->connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) { (strlen(mqttConfig.username) > 0 && mqtt->connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!"); if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!");
config.ackMqttChange();
if(mqttHandler != NULL) { if(mqttHandler != NULL) {
mqttHandler->publishSystem(&hw); mqttHandler->publishSystem(&hw, eapi, &ea);
} }
// Subscribe to the chosen MQTT topic, if set in configuration // Subscribe to the chosen MQTT topic, if set in configuration

View File

@@ -80,6 +80,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
} }
use = 0; use = 0;
produce = 0;
costHour = 0; costHour = 0;
currentHour = local.Hour; currentHour = local.Hour;
@@ -106,19 +107,24 @@ bool EnergyAccounting::update(AmsData* amsData) {
} }
unsigned long ms = this->lastUpdateMillis > amsData->getLastUpdateMillis() ? 0 : amsData->getLastUpdateMillis() - this->lastUpdateMillis; unsigned long ms = this->lastUpdateMillis > amsData->getLastUpdateMillis() ? 0 : amsData->getLastUpdateMillis() - this->lastUpdateMillis;
float kwh = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0; float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
lastUpdateMillis = amsData->getLastUpdateMillis(); lastUpdateMillis = amsData->getLastUpdateMillis();
if(kwh > 0) { if(kwhi > 0) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh\n", kwh); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh import\n", kwhi);
use += kwh; use += kwhi;
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) { if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
float price = eapi->getValueForHour(0); float price = eapi->getValueForHour(0);
float cost = price * kwh; float cost = price * kwhi;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency()); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency());
costHour += cost; costHour += cost;
costDay += cost; costDay += cost;
} }
} }
if(kwhe > 0) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh export\n", kwhe);
produce += kwhe;
}
if(config != NULL) { if(config != NULL) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) calculating threshold, currently at %d\n", currentThresholdIdx); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) calculating threshold, currently at %d\n", currentThresholdIdx);
@@ -136,10 +142,10 @@ void EnergyAccounting::calcDayCost() {
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) { if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
if(initPrice) costDay = 0; if(initPrice) costDay = 0;
for(int i = 0; i < local.Hour; i++) { for(int i = 0; i < currentHour; i++) {
float price = eapi->getValueForHour(i - local.Hour); float price = eapi->getValueForHour(i - currentHour);
if(price == ENTSOE_NO_VALUE) break; if(price == ENTSOE_NO_VALUE) break;
breakTime(now - ((local.Hour - i) * 3600), utc); breakTime(now - ((currentHour - i) * 3600), utc);
int16_t wh = ds->getHourImport(utc.Hour); int16_t wh = ds->getHourImport(utc.Hour);
costDay += price * (wh / 1000.0); costDay += price * (wh / 1000.0);
} }
@@ -151,23 +157,59 @@ double EnergyAccounting::getUseThisHour() {
return use; return use;
} }
double EnergyAccounting::getCostThisHour() {
return costHour;
}
double EnergyAccounting::getUseToday() { double EnergyAccounting::getUseToday() {
float ret = 0.0; float ret = 0.0;
time_t now = time(nullptr); time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0; if(now < BUILD_EPOCH) return 0;
tmElements_t local, utc; tmElements_t utc;
breakTime(tz->toLocal(now), local); for(int i = 0; i < currentHour; i++) {
for(int i = 0; i < local.Hour; i++) { breakTime(now - ((currentHour - i) * 3600), utc);
breakTime(now - ((local.Hour - i) * 3600), utc);
ret += ds->getHourImport(utc.Hour) / 1000.0; ret += ds->getHourImport(utc.Hour) / 1000.0;
} }
return ret + getUseThisHour(); return ret + getUseThisHour();
} }
double EnergyAccounting::getUseThisMonth() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
float ret = 0;
for(int i = 0; i < currentDay; i++) {
ret += ds->getDayImport(i) / 1000.0;
}
return ret + getUseToday();
}
double EnergyAccounting::getProducedThisHour() {
return produce;
}
double EnergyAccounting::getProducedToday() {
float ret = 0.0;
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
tmElements_t utc;
for(int i = 0; i < currentHour; i++) {
breakTime(now - ((currentHour - i) * 3600), utc);
ret += ds->getHourExport(utc.Hour) / 1000.0;
}
return ret + getProducedThisHour();
}
double EnergyAccounting::getProducedThisMonth() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
float ret = 0;
for(int i = 0; i < currentDay; i++) {
ret += ds->getDayExport(i) / 1000.0;
}
return ret + getProducedToday();
}
double EnergyAccounting::getCostThisHour() {
return costHour;
}
double EnergyAccounting::getCostToday() { double EnergyAccounting::getCostToday() {
return costDay; return costDay;
} }
@@ -176,21 +218,6 @@ double EnergyAccounting::getCostYesterday() {
return data.costYesterday / 10.0; return data.costYesterday / 10.0;
} }
double EnergyAccounting::getUseThisMonth() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
tmElements_t tm;
if(tz != NULL)
breakTime(tz->toLocal(now), tm);
else
breakTime(now, tm);
float ret = 0;
for(int i = 0; i < tm.Day; i++) {
ret += ds->getDayImport(i) / 1000.0;
}
return ret + getUseToday();
}
double EnergyAccounting::getCostThisMonth() { double EnergyAccounting::getCostThisMonth() {
return data.costThisMonth + getCostToday(); return data.costThisMonth + getCostToday();
} }
@@ -210,7 +237,35 @@ float EnergyAccounting::getMonthMax() {
uint32_t maxHour = 0.0; uint32_t maxHour = 0.0;
bool included[5] = { false, false, false, false, false }; 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; uint8_t maxIdx = 0;
uint16_t maxVal = 0; uint16_t maxVal = 0;
for(uint8_t i = 0; i < 5; i++) { for(uint8_t i = 0; i < 5; i++) {
@@ -224,13 +279,15 @@ float EnergyAccounting::getMonthMax() {
count++; count++;
} }
uint8_t pos = 0;
for(uint8_t i = 0; i < 5; i++) { for(uint8_t i = 0; i < 5; i++) {
if(!included[i]) continue; if(!included[i]) continue;
if(data.peaks[i].day > 0) { pos++;
maxHour += data.peaks[i].value; 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() { bool EnergyAccounting::load() {
@@ -251,6 +308,7 @@ bool EnergyAccounting::load() {
if(buf[0] == 4) { if(buf[0] == 4) {
EnergyAccountingData* data = (EnergyAccountingData*) buf; EnergyAccountingData* data = (EnergyAccountingData*) buf;
memcpy(&this->data, data, sizeof(this->data)); memcpy(&this->data, data, sizeof(this->data));
ret = true;
} else if(buf[0] == 3) { } else if(buf[0] == 3) {
EnergyAccountingData* data = (EnergyAccountingData*) buf; EnergyAccountingData* data = (EnergyAccountingData*) buf;
this->data = { 4, data->month, this->data = { 4, data->month,
@@ -282,7 +340,7 @@ bool EnergyAccounting::load() {
this->data.peaks[b].day = b; this->data.peaks[b].day = b;
memcpy(&this->data.peaks[b].value, buf+i, 2); memcpy(&this->data.peaks[b].value, buf+i, 2);
b++; b++;
if(b >= config->hours) break; if(b >= config->hours || b >= 5) break;
} }
ret = true; ret = true;
} else if(buf[0] == 1) { } else if(buf[0] == 1) {

View File

@@ -42,16 +42,22 @@ public:
bool save(); bool save();
double getUseThisHour(); double getUseThisHour();
double getCostThisHour();
double getUseToday(); double getUseToday();
double getUseThisMonth();
double getProducedThisHour();
double getProducedToday();
double getProducedThisMonth();
double getCostThisHour();
double getCostToday(); double getCostToday();
double getCostYesterday(); double getCostYesterday();
double getUseThisMonth();
double getCostThisMonth(); double getCostThisMonth();
uint16_t getCostLastMonth(); uint16_t getCostLastMonth();
float getMonthMax(); float getMonthMax();
uint8_t getCurrentThreshold(); uint8_t getCurrentThreshold();
float getPeak(uint8_t);
EnergyAccountingData getData(); EnergyAccountingData getData();
void setData(EnergyAccountingData&); void setData(EnergyAccountingData&);
@@ -66,6 +72,7 @@ private:
Timezone *tz = NULL; Timezone *tz = NULL;
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0; uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
double use, costHour, costDay; double use, costHour, costDay;
double produce;
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 }; EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
void calcDayCost(); void calcDayCost();

View File

@@ -266,7 +266,9 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
if(meterTs != NULL) { if(meterTs != NULL) {
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs; AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
time_t ts = decodeCosemDateTime(amst->dt); 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); meterTimestamp = tz.toUTC(ts);
} else { } else {
meterTimestamp = ts; meterTimestamp = ts;

111
src/LNG.cpp Normal file
View File

@@ -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();
}
}
}

30
src/LNG.h Normal file
View File

@@ -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

View File

@@ -19,7 +19,7 @@ public:
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
virtual bool publishTemperatures(AmsConfiguration*, HwTools*); virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
virtual bool publishPrices(EntsoeApi* eapi); virtual bool publishPrices(EntsoeApi* eapi);
virtual bool publishSystem(HwTools*); virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*);
protected: protected:
MQTTClient* mqtt; MQTTClient* mqtt;

View File

@@ -71,6 +71,6 @@ bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
return false; return false;
} }
bool DomoticzMqttHandler::publishSystem(HwTools* hw) { bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
return false; return false;
} }

View File

@@ -12,7 +12,7 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private: private:
DomoticzConfig config; DomoticzConfig config;

View File

@@ -9,6 +9,7 @@
#include "web/root/jsonsys_json.h" #include "web/root/jsonsys_json.h"
#include "web/root/jsonprices_json.h" #include "web/root/jsonprices_json.h"
#include "web/root/hadiscover_json.h" #include "web/root/hadiscover_json.h"
#include "web/root/realtime_json.h"
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) { bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected()) if(topic.isEmpty() || !mqtt->connected())
@@ -24,6 +25,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
data->getMeterTimestamp() data->getMeterTimestamp()
); );
mqtt->publish(topic + "/energy", json); mqtt->publish(topic + "/energy", json);
mqtt->loop();
} }
String meterModel = data->getMeterModel(); String meterModel = data->getMeterModel();
meterModel.replace("\\", "\\\\"); meterModel.replace("\\", "\\\\");
@@ -54,8 +56,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
); );
return mqtt->publish(topic + "/power", json); return mqtt->publish(topic + "/power", json);
} }
return false; return false;}
}
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) { bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
int count = hw->getTempSensorCount(); int count = hw->getTempSensorCount();
@@ -189,7 +190,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
return mqtt->publish(topic + "/prices", json); return mqtt->publish(topic + "/prices", json);
} }
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) { bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected()){ if(topic.isEmpty() || !mqtt->connected()){
sequence = 0; sequence = 0;
return false; return false;
@@ -202,7 +203,8 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
(uint32_t) (millis64()/1000), (uint32_t) (millis64()/1000),
hw->getVcc(), hw->getVcc(),
hw->getWifiRssi(), hw->getWifiRssi(),
hw->getTemperature() hw->getTemperature(),
VERSION
); );
mqtt->publish(topic + "/state", json); mqtt->publish(topic + "/state", json);
} }
@@ -216,7 +218,7 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
String haUrl = "http://" + haUID + ".local/"; String haUrl = "http://" + haUID + ".local/";
// Could this be necessary? haUID.replace("-", "_"); // Could this be necessary? haUID.replace("-", "_");
for(int i=0;i<sensors;i++){ for(int i=0;i<17;i++){
snprintf_P(json, BufferSize, HADISCOVER_JSON, snprintf_P(json, BufferSize, HADISCOVER_JSON,
FPSTR(HA_NAMES[i]), FPSTR(HA_NAMES[i]),
topic.c_str(), FPSTR(HA_TOPICS[i]), topic.c_str(), FPSTR(HA_TOPICS[i]),
@@ -239,5 +241,4 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
autodiscoverInit = true; autodiscoverInit = true;
} }
if(listType>0) sequence++; if(listType>0) sequence++;
return true; return true;}
}

View File

@@ -13,11 +13,9 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private: private:
static const uint8_t sensors = 17;
String haTopic = "homeassistant/sensor/"; String haTopic = "homeassistant/sensor/";
String haName = "AMS reader"; String haName = "AMS reader";

View File

@@ -11,4 +11,4 @@ const char* HA_UOM[17] PROGMEM = {"dBm", "V", "C", "W", "W", "W", "W", "A", "A",
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_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\""}; const char* HA_STACL[17] PROGMEM = {"", "", "", "\"measurement\"", "\"measurement\"", "\"measurement\"", "\"measurement\"", "", "", "", "", "", "", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\""};
#endif #endif

View File

@@ -1,4 +1,5 @@
#include "JsonMqttHandler.h" #include "JsonMqttHandler.h"
#include "version.h"
#include "hexutils.h" #include "hexutils.h"
#include "Uptime.h" #include "Uptime.h"
#include "web/root/json1_json.h" #include "web/root/json1_json.h"
@@ -27,7 +28,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
ea->getUseThisHour(), ea->getUseThisHour(),
ea->getUseToday(), ea->getUseToday(),
ea->getCurrentThreshold(), ea->getCurrentThreshold(),
ea->getMonthMax() ea->getMonthMax(),
ea->getProducedThisHour(),
ea->getProducedToday()
); );
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} else if(data->getListType() == 2) { } else if(data->getListType() == 2) {
@@ -55,7 +58,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
ea->getUseThisHour(), ea->getUseThisHour(),
ea->getUseToday(), ea->getUseToday(),
ea->getCurrentThreshold(), ea->getCurrentThreshold(),
ea->getMonthMax() ea->getMonthMax(),
ea->getProducedThisHour(),
ea->getProducedToday()
); );
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} else if(data->getListType() == 3) { } else if(data->getListType() == 3) {
@@ -88,7 +93,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
ea->getUseThisHour(), ea->getUseThisHour(),
ea->getUseToday(), ea->getUseToday(),
ea->getCurrentThreshold(), ea->getCurrentThreshold(),
ea->getMonthMax() ea->getMonthMax(),
ea->getProducedThisHour(),
ea->getProducedToday()
); );
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} else if(data->getListType() == 4) { } else if(data->getListType() == 4) {
@@ -125,7 +132,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
ea->getUseThisHour(), ea->getUseThisHour(),
ea->getUseToday(), ea->getUseToday(),
ea->getCurrentThreshold(), ea->getCurrentThreshold(),
ea->getMonthMax() ea->getMonthMax(),
ea->getProducedThisHour(),
ea->getProducedToday()
); );
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} }
@@ -262,7 +271,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
return mqtt->publish(topic, json); 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()) if(init || topic.isEmpty() || !mqtt->connected())
return false; return false;
@@ -272,7 +281,8 @@ bool JsonMqttHandler::publishSystem(HwTools* hw) {
(uint32_t) (millis64()/1000), (uint32_t) (millis64()/1000),
hw->getVcc(), hw->getVcc(),
hw->getWifiRssi(), hw->getWifiRssi(),
hw->getTemperature() hw->getTemperature(),
VERSION
); );
init = mqtt->publish(topic, json); init = mqtt->publish(topic, json);
return init; return init;

View File

@@ -13,7 +13,7 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private: private:
String clientId; String clientId;

View File

@@ -74,8 +74,13 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
} }
mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3)); mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
mqtt->publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2)); 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/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
mqtt->publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0); mqtt->publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0);
mqtt->publish(topic + "/realtime/export/hour", String(ea->getProducedThisHour(), 3));
mqtt->publish(topic + "/realtime/export/day", String(ea->getProducedToday(), 2));
return true; return true;
} }
@@ -206,7 +211,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
return true; return true;
} }
bool RawMqttHandler::publishSystem(HwTools* hw) { bool RawMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected()) if(topic.isEmpty() || !mqtt->connected())
return false; return false;

View File

@@ -12,7 +12,7 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea); bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private: private:
String topic; String topic;

View File

@@ -90,7 +90,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
server.on("/debugging", HTTP_GET, std::bind(&AmsWebServer::configDebugHtml, this)); server.on("/debugging", HTTP_GET, std::bind(&AmsWebServer::configDebugHtml, this));
server.on("/firmware", HTTP_GET, std::bind(&AmsWebServer::firmwareHtml, 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("/upgrade", HTTP_GET, std::bind(&AmsWebServer::firmwareDownload, this));
server.on("/restart", HTTP_GET, std::bind(&AmsWebServer::restartHtml, this)); server.on("/restart", HTTP_GET, std::bind(&AmsWebServer::restartHtml, this));
server.on("/restart", HTTP_POST, std::bind(&AmsWebServer::restartPost, this)); server.on("/restart", HTTP_POST, std::bind(&AmsWebServer::restartPost, this));
@@ -336,6 +336,9 @@ void AmsWebServer::configMeterHtml() {
case AmsTypeSagemcom: case AmsTypeSagemcom:
manufacturer = "Sagemcom"; manufacturer = "Sagemcom";
break; break;
case AmsTypeLng:
manufacturer = "L&G";
break;
default: default:
manufacturer = "Unknown"; manufacturer = "Unknown";
break; break;
@@ -567,6 +570,20 @@ void AmsWebServer::configEntsoeHtml() {
html.replace("{eaDk1}", strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : ""); html.replace("{eaDk1}", strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : "");
html.replace("{eaDk2}", strcmp(entsoe.area, "10YDK-2--------M") == 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("{ecNOK}", strcmp(entsoe.currency, "NOK") == 0 ? "selected" : "");
html.replace("{ecSEK}", strcmp(entsoe.currency, "SEK") == 0 ? "selected" : ""); html.replace("{ecSEK}", strcmp(entsoe.currency, "SEK") == 0 ? "selected" : "");
html.replace("{ecDKK}", strcmp(entsoe.currency, "DKK") == 0 ? "selected" : ""); html.replace("{ecDKK}", strcmp(entsoe.currency, "DKK") == 0 ? "selected" : "");
@@ -591,9 +608,9 @@ void AmsWebServer::configThresholdsHtml() {
String html = String((const __FlashStringHelper*) THRESHOLDS_HTML); String html = String((const __FlashStringHelper*) THRESHOLDS_HTML);
for(int i = 0; i < 9; i++) { 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.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
server.send_P(200, MIME_HTML, HEAD_HTML); server.send_P(200, MIME_HTML, HEAD_HTML);
@@ -708,6 +725,12 @@ void AmsWebServer::dataJson() {
if(eapi != NULL && strlen(eapi->getToken()) > 0) if(eapi != NULL && strlen(eapi->getToken()) > 0)
price = eapi->getValueForHour(0); 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, snprintf_P(buf, BufferSize, DATA_JSON,
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr, maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
meterConfig->productionCapacity, meterConfig->productionCapacity,
@@ -744,13 +767,17 @@ void AmsWebServer::dataJson() {
meterState->getMeterType(), meterState->getMeterType(),
meterConfig->distributionSystem, meterConfig->distributionSystem,
ea->getMonthMax(), ea->getMonthMax(),
peaks.c_str(),
ea->getCurrentThreshold(), ea->getCurrentThreshold(),
ea->getUseThisHour(), ea->getUseThisHour(),
ea->getCostThisHour(), ea->getCostThisHour(),
ea->getProducedThisHour(),
ea->getUseToday(), ea->getUseToday(),
ea->getCostToday(), ea->getCostToday(),
ea->getProducedToday(),
ea->getUseThisMonth(), ea->getUseThisMonth(),
ea->getCostThisMonth(), ea->getCostThisMonth(),
ea->getProducedThisMonth(),
(uint32_t) time(nullptr) (uint32_t) time(nullptr)
); );
@@ -927,42 +954,42 @@ void AmsWebServer::energyPriceJson() {
snprintf_P(buf, BufferSize, ENERGYPRICE_JSON, snprintf_P(buf, BufferSize, ENERGYPRICE_JSON,
eapi == NULL ? "" : eapi->getCurrency(), eapi == NULL ? "" : eapi->getCurrency(),
prices[0] == ENTSOE_NO_VALUE ? "null" : String(prices[0], 2).c_str(), prices[0] == ENTSOE_NO_VALUE ? "null" : String(prices[0], 4).c_str(),
prices[1] == ENTSOE_NO_VALUE ? "null" : String(prices[1], 2).c_str(), prices[1] == ENTSOE_NO_VALUE ? "null" : String(prices[1], 4).c_str(),
prices[2] == ENTSOE_NO_VALUE ? "null" : String(prices[2], 2).c_str(), prices[2] == ENTSOE_NO_VALUE ? "null" : String(prices[2], 4).c_str(),
prices[3] == ENTSOE_NO_VALUE ? "null" : String(prices[3], 2).c_str(), prices[3] == ENTSOE_NO_VALUE ? "null" : String(prices[3], 4).c_str(),
prices[4] == ENTSOE_NO_VALUE ? "null" : String(prices[4], 2).c_str(), prices[4] == ENTSOE_NO_VALUE ? "null" : String(prices[4], 4).c_str(),
prices[5] == ENTSOE_NO_VALUE ? "null" : String(prices[5], 2).c_str(), prices[5] == ENTSOE_NO_VALUE ? "null" : String(prices[5], 4).c_str(),
prices[6] == ENTSOE_NO_VALUE ? "null" : String(prices[6], 2).c_str(), prices[6] == ENTSOE_NO_VALUE ? "null" : String(prices[6], 4).c_str(),
prices[7] == ENTSOE_NO_VALUE ? "null" : String(prices[7], 2).c_str(), prices[7] == ENTSOE_NO_VALUE ? "null" : String(prices[7], 4).c_str(),
prices[8] == ENTSOE_NO_VALUE ? "null" : String(prices[8], 2).c_str(), prices[8] == ENTSOE_NO_VALUE ? "null" : String(prices[8], 4).c_str(),
prices[9] == ENTSOE_NO_VALUE ? "null" : String(prices[9], 2).c_str(), prices[9] == ENTSOE_NO_VALUE ? "null" : String(prices[9], 4).c_str(),
prices[10] == ENTSOE_NO_VALUE ? "null" : String(prices[10], 2).c_str(), prices[10] == ENTSOE_NO_VALUE ? "null" : String(prices[10], 4).c_str(),
prices[11] == ENTSOE_NO_VALUE ? "null" : String(prices[11], 2).c_str(), prices[11] == ENTSOE_NO_VALUE ? "null" : String(prices[11], 4).c_str(),
prices[12] == ENTSOE_NO_VALUE ? "null" : String(prices[12], 2).c_str(), prices[12] == ENTSOE_NO_VALUE ? "null" : String(prices[12], 4).c_str(),
prices[13] == ENTSOE_NO_VALUE ? "null" : String(prices[13], 2).c_str(), prices[13] == ENTSOE_NO_VALUE ? "null" : String(prices[13], 4).c_str(),
prices[14] == ENTSOE_NO_VALUE ? "null" : String(prices[14], 2).c_str(), prices[14] == ENTSOE_NO_VALUE ? "null" : String(prices[14], 4).c_str(),
prices[15] == ENTSOE_NO_VALUE ? "null" : String(prices[15], 2).c_str(), prices[15] == ENTSOE_NO_VALUE ? "null" : String(prices[15], 4).c_str(),
prices[16] == ENTSOE_NO_VALUE ? "null" : String(prices[16], 2).c_str(), prices[16] == ENTSOE_NO_VALUE ? "null" : String(prices[16], 4).c_str(),
prices[17] == ENTSOE_NO_VALUE ? "null" : String(prices[17], 2).c_str(), prices[17] == ENTSOE_NO_VALUE ? "null" : String(prices[17], 4).c_str(),
prices[18] == ENTSOE_NO_VALUE ? "null" : String(prices[18], 2).c_str(), prices[18] == ENTSOE_NO_VALUE ? "null" : String(prices[18], 4).c_str(),
prices[19] == ENTSOE_NO_VALUE ? "null" : String(prices[19], 2).c_str(), prices[19] == ENTSOE_NO_VALUE ? "null" : String(prices[19], 4).c_str(),
prices[20] == ENTSOE_NO_VALUE ? "null" : String(prices[20], 2).c_str(), prices[20] == ENTSOE_NO_VALUE ? "null" : String(prices[20], 4).c_str(),
prices[21] == ENTSOE_NO_VALUE ? "null" : String(prices[21], 2).c_str(), prices[21] == ENTSOE_NO_VALUE ? "null" : String(prices[21], 4).c_str(),
prices[22] == ENTSOE_NO_VALUE ? "null" : String(prices[22], 2).c_str(), prices[22] == ENTSOE_NO_VALUE ? "null" : String(prices[22], 4).c_str(),
prices[23] == ENTSOE_NO_VALUE ? "null" : String(prices[23], 2).c_str(), prices[23] == ENTSOE_NO_VALUE ? "null" : String(prices[23], 4).c_str(),
prices[24] == ENTSOE_NO_VALUE ? "null" : String(prices[24], 2).c_str(), prices[24] == ENTSOE_NO_VALUE ? "null" : String(prices[24], 4).c_str(),
prices[25] == ENTSOE_NO_VALUE ? "null" : String(prices[25], 2).c_str(), prices[25] == ENTSOE_NO_VALUE ? "null" : String(prices[25], 4).c_str(),
prices[26] == ENTSOE_NO_VALUE ? "null" : String(prices[26], 2).c_str(), prices[26] == ENTSOE_NO_VALUE ? "null" : String(prices[26], 4).c_str(),
prices[27] == ENTSOE_NO_VALUE ? "null" : String(prices[27], 2).c_str(), prices[27] == ENTSOE_NO_VALUE ? "null" : String(prices[27], 4).c_str(),
prices[28] == ENTSOE_NO_VALUE ? "null" : String(prices[28], 2).c_str(), prices[28] == ENTSOE_NO_VALUE ? "null" : String(prices[28], 4).c_str(),
prices[29] == ENTSOE_NO_VALUE ? "null" : String(prices[29], 2).c_str(), prices[29] == ENTSOE_NO_VALUE ? "null" : String(prices[29], 4).c_str(),
prices[30] == ENTSOE_NO_VALUE ? "null" : String(prices[30], 2).c_str(), prices[30] == ENTSOE_NO_VALUE ? "null" : String(prices[30], 4).c_str(),
prices[31] == ENTSOE_NO_VALUE ? "null" : String(prices[31], 2).c_str(), prices[31] == ENTSOE_NO_VALUE ? "null" : String(prices[31], 4).c_str(),
prices[32] == ENTSOE_NO_VALUE ? "null" : String(prices[32], 2).c_str(), prices[32] == ENTSOE_NO_VALUE ? "null" : String(prices[32], 4).c_str(),
prices[33] == ENTSOE_NO_VALUE ? "null" : String(prices[33], 2).c_str(), prices[33] == ENTSOE_NO_VALUE ? "null" : String(prices[33], 4).c_str(),
prices[34] == ENTSOE_NO_VALUE ? "null" : String(prices[34], 2).c_str(), prices[34] == ENTSOE_NO_VALUE ? "null" : String(prices[34], 4).c_str(),
prices[35] == ENTSOE_NO_VALUE ? "null" : String(prices[35], 2).c_str() prices[35] == ENTSOE_NO_VALUE ? "null" : String(prices[35], 4).c_str()
); );
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
@@ -1036,12 +1063,12 @@ void AmsWebServer::handleSetup() {
gpioConfig->ledPinRed = 13; gpioConfig->ledPinRed = 13;
gpioConfig->ledPinGreen = 14; gpioConfig->ledPinGreen = 14;
gpioConfig->ledRgbInverted = true; gpioConfig->ledRgbInverted = true;
gpioConfig->vccPin = 35; gpioConfig->vccPin = 10;
gpioConfig->vccResistorGnd = 22; gpioConfig->vccResistorGnd = 22;
gpioConfig->vccResistorVcc = 33; gpioConfig->vccResistorVcc = 33;
break; break;
case 6: // Pow-P1 case 6: // Pow-P1
gpioConfig->hanPin = 18; gpioConfig->hanPin = 16;
gpioConfig->apPin = 0; gpioConfig->apPin = 0;
gpioConfig->ledPinRed = 13; gpioConfig->ledPinRed = 13;
gpioConfig->ledPinGreen = 14; gpioConfig->ledPinGreen = 14;
@@ -1639,13 +1666,40 @@ void AmsWebServer::firmwareHtml() {
server.sendContent_P(FOOT_HTML); 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() { void AmsWebServer::firmwareUpload() {
printD("Handlling firmware upload...");
if(!checkSecurity(1)) if(!checkSecurity(1))
return; return;
HTTPUpload& upload = server.upload(); HTTPUpload& upload = server.upload();
String filename = upload.filename;
if(filename.isEmpty()) return;
if(upload.status == UPLOAD_FILE_START) { if(upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
if(!filename.endsWith(".bin")) { if(!filename.endsWith(".bin")) {
server.send(500, MIME_PLAIN, "500: couldn't create file"); server.send(500, MIME_PLAIN, "500: couldn't create file");
} else { } else {
@@ -1659,7 +1713,7 @@ void AmsWebServer::firmwareUpload() {
} }
uploadFile(FILE_FIRMWARE); uploadFile(FILE_FIRMWARE);
if(upload.status == UPLOAD_FILE_END) { if(upload.status == UPLOAD_FILE_END) {
performRestart = true; rebootForUpgrade = true;
server.sendHeader("Location","/restart-wait"); server.sendHeader("Location","/restart-wait");
server.send(303); server.send(303);
} }
@@ -1672,109 +1726,9 @@ void AmsWebServer::firmwareDownload() {
return; return;
printD("Firmware download URL triggered"); printD("Firmware download URL triggered");
if(server.hasArg("version")) { performUpgrade = true;
String version = server.arg("version"); server.sendHeader("Location","/restart-wait");
String versionStripped = version.substring(1); server.send(303);
printI("Downloading firmware...");
HTTPClient httpClient;
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
httpClient.setReuse(false);
httpClient.setTimeout(60000);
httpClient.setUserAgent("ams2mqtt/" + String(VERSION));
#if defined(ESP8266)
WiFiClient client;
String url = "http://ams2mqtt.no23.cc/releases/download/" + version + "/ams2mqtt-esp8266-" + versionStripped + ".bin";
/*
t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION);
switch(ret) {
case HTTP_UPDATE_FAILED:
printE("[update] Update failed.");
server.sendHeader("Location","/");
server.send(303);
break;
case HTTP_UPDATE_NO_UPDATES:
printI("[update] Update no Update.");
server.sendHeader("Location","/");
server.send(303);
break;
case HTTP_UPDATE_OK:
printI("[update] Update ok."); // may not be called since we reboot the ESP
performRestart = true;
server.sendHeader("Location","/restart-wait");
server.send(303);
break;
}
return;
*/
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
httpClient.setConnectTimeout(60000);
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32s2-" + versionStripped + ".bin";
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
#elif defined(ESP32)
httpClient.setConnectTimeout(60000);
#if defined(CONFIG_FREERTOS_UNICORE)
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32solo-" + versionStripped + ".bin";
#else
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32-" + versionStripped + ".bin";
#endif
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
#endif
printD("Downloading from URL:");
printD(url);
#if defined(ESP8266)
if(httpClient.begin(client, url)) {
#elif defined(ESP32)
if(httpClient.begin(url)) {
#endif
printD("HTTP client setup successful");
int status = httpClient.GET();
if(status == HTTP_CODE_OK) {
printD("Received OK from server");
if(LittleFS.begin()) {
#if defined(ESP32)
esp_task_wdt_delete(NULL);
esp_task_wdt_deinit();
#elif defined(ESP8266)
ESP.wdtDisable();
#endif
printI("Downloading firmware to LittleFS");
file = LittleFS.open(FILE_FIRMWARE, "w");
int len = httpClient.writeToStream(&file);
file.close();
LittleFS.end();
performRestart = true;
server.sendHeader("Location","/restart-wait");
server.send(303);
} else {
printE("Unable to open LittleFS for writing");
server.sendHeader("Location","/");
server.send(303);
}
} else {
printE("Communication error: ");
debugger->printf("%d\n", status);
printE(httpClient.errorToString(status));
printD(httpClient.getString());
server.sendHeader("Location","/");
server.send(303);
}
} else {
printE("Unable to configure HTTP client");
server.sendHeader("Location","/");
server.send(303);
}
httpClient.end();
} else {
printI("No firmware version specified...");
server.sendHeader("Location","/");
server.send(303);
}
} }
void AmsWebServer::restartHtml() { void AmsWebServer::restartHtml() {
@@ -1824,6 +1778,14 @@ void AmsWebServer::restartWaitHtml() {
} }
html.replace("${hostname}", wifi.hostname); html.replace("${hostname}", wifi.hostname);
if(performUpgrade || rebootForUpgrade) {
html.replace("{rs}", "d-none");
html.replace("{us}", "");
} else {
html.replace("{rs}", "");
html.replace("{us}", "d-none");
}
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE); server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF); server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
@@ -1832,7 +1794,7 @@ void AmsWebServer::restartWaitHtml() {
server.send(200, MIME_HTML, html); server.send(200, MIME_HTML, html);
yield(); yield();
if(performRestart) { if(performRestart || rebootForUpgrade) {
if(ds != NULL) { if(ds != NULL) {
ds->save(); ds->save();
} }
@@ -1844,6 +1806,42 @@ void AmsWebServer::restartWaitHtml() {
ESP.restart(); ESP.restart();
#endif #endif
performRestart = false; performRestart = false;
} else if(performUpgrade) {
WiFiClient client;
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)
String chipType = "esp32s2";
#elif defined(ESP32)
#if defined(CONFIG_FREERTOS_UNICORE)
String chipType = "esp32solo";
#else
String chipType = "esp32";
#endif
#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
switch(ret) {
case HTTP_UPDATE_FAILED:
printE("Update failed");
break;
case HTTP_UPDATE_NO_UPDATES:
printI("No Update");
break;
case HTTP_UPDATE_OK:
printI("Update OK");
break;
}
performUpgrade = false;
} }
} }

View File

@@ -24,6 +24,7 @@
#include <WiFi.h> #include <WiFi.h>
#include <WebServer.h> #include <WebServer.h>
#include <HTTPClient.h> #include <HTTPClient.h>
#include <HTTPUpdate.h>
#else #else
#warning "Unsupported board type" #warning "Unsupported board type"
#endif #endif
@@ -58,6 +59,9 @@ private:
bool uploading = false; bool uploading = false;
File file; File file;
bool performRestart = false; bool performRestart = false;
bool performUpgrade = false;
bool rebootForUpgrade = false;
String customFirmwareUrl;
static const uint16_t BufferSize = 2048; static const uint16_t BufferSize = 2048;
char* buf; char* buf;
@@ -101,6 +105,7 @@ private:
String getSerialSelectOptions(int selected); String getSerialSelectOptions(int selected);
void firmwareHtml(); void firmwareHtml();
void firmwarePost();
void firmwareUpload(); void firmwareUpload();
void firmwareDownload(); void firmwareDownload();
void restartHtml(); void restartHtml();

View File

@@ -316,6 +316,7 @@ $(function() {
url: swv.data('url'), url: swv.data('url'),
dataType: 'json' dataType: 'json'
}).done(function(releases) { }).done(function(releases) {
var isnew = false;
if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) { if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) {
releases.reverse(); releases.reverse();
var next_patch; var next_patch;
@@ -352,10 +353,13 @@ $(function() {
}); });
if(next_minor) { if(next_minor) {
nextVersion = next_minor; nextVersion = next_minor;
isnew = true;
} else if(next_major) { } else if(next_major) {
nextVersion = next_major; nextVersion = next_major;
isnew = true;
} else if(next_patch) { } else if(next_patch) {
nextVersion = next_patch; nextVersion = next_patch;
isnew = true;
} }
} else { } else {
nextVersion = releases[0]; nextVersion = releases[0];
@@ -375,9 +379,11 @@ $(function() {
} }
}); });
}; };
$('#newVersionTag').text(nextVersion.tag_name); if(isnew) {
$('#newVersionUrl').prop('href', nextVersion.html_url); $('#newVersionTag').text(nextVersion.tag_name);
$('#newVersion').removeClass('d-none'); $('#newVersionUrl').prop('href', nextVersion.html_url);
$('#newVersion').removeClass('d-none');
}
} }
}); });
} }
@@ -478,7 +484,7 @@ var drawDay = function() {
timeout: 30000, timeout: 30000,
dataType: 'json', dataType: 'json',
}).done(function(json) { }).done(function(json) {
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }]]; data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
var r = 1; var r = 1;
var hour = moment.utc().hours(); var hour = moment.utc().hours();
var offset = moment().utcOffset()/60; var offset = moment().utcOffset()/60;
@@ -486,13 +492,13 @@ var drawDay = function() {
for(var i = hour; i<24; i++) { for(var i = hour; i<24; i++) {
var imp = json["i"+zeropad(i)]; var imp = json["i"+zeropad(i)];
var exp = json["e"+zeropad(i)]; var exp = json["e"+zeropad(i)];
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(1) : imp.toFixed(1) + '\n' + -exp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;"]; data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
min = Math.min(0, -exp); min = Math.min(0, -exp);
}; };
for(var i = 0; i < hour; i++) { for(var i = 0; i < hour; i++) {
var imp = json["i"+zeropad(i)]; var imp = json["i"+zeropad(i)];
var exp = json["e"+zeropad(i)]; var exp = json["e"+zeropad(i)];
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(1) : imp.toFixed(1) + '\n' + -exp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;"]; data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
min = Math.min(0, -exp); min = Math.min(0, -exp);
}; };
ea = google.visualization.arrayToDataTable(data); ea = google.visualization.arrayToDataTable(data);
@@ -511,7 +517,7 @@ var drawMonth = function() {
timeout: 30000, timeout: 30000,
dataType: 'json', dataType: 'json',
}).done(function(json) { }).done(function(json) {
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }]]; data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
var r = 1; var r = 1;
var day = moment().date(); var day = moment().date();
var eom = moment().subtract(1, 'months').endOf('month').date(); var eom = moment().subtract(1, 'months').endOf('month').date();
@@ -519,13 +525,13 @@ var drawMonth = function() {
for(var i = day; i<=eom; i++) { for(var i = day; i<=eom; i++) {
var imp = json["i"+zeropad(i)]; var imp = json["i"+zeropad(i)];
var exp = json["e"+zeropad(i)]; var exp = json["e"+zeropad(i)];
data[r++] = [zeropad(i), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(0) : imp.toFixed(0) + '\n' + -exp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;"]; data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
min = Math.min(0, -exp); min = Math.min(0, -exp);
} }
for(var i = 1; i < day; i++) { for(var i = 1; i < day; i++) {
var imp = json["i"+zeropad(i)]; var imp = json["i"+zeropad(i)];
var exp = json["e"+zeropad(i)]; var exp = json["e"+zeropad(i)];
data[r++] = [zeropad(i), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(0) : imp.toFixed(0) + '\n' + -exp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;"]; data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
min = Math.min(0, -exp); min = Math.min(0, -exp);
} }
ma = google.visualization.arrayToDataTable(data); ma = google.visualization.arrayToDataTable(data);
@@ -790,6 +796,12 @@ var fetch = function() {
if(currency) { if(currency) {
$('.sp').show(); $('.sp').show();
} }
if(om > 0) {
$('.se').removeClass('d-none');
$('#eache').html(json.ea.h.p.toFixed(2));
$('#eacde').html(json.ea.d.p.toFixed(1));
$('#eacme').html(json.ea.m.p.toFixed(0));
}
} }
if(json.me) { if(json.me) {
@@ -878,7 +890,7 @@ var fetch = function() {
var upgrade = function() { var upgrade = function() {
if(nextVersion) { if(nextVersion) {
if(confirm("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(); $('#loading-indicator').show();
window.location.href="/upgrade?version=" + nextVersion.tag_name; window.location.href="/upgrade?version=" + nextVersion.tag_name;
} }

View File

@@ -35,18 +35,22 @@
"ds" : %d, "ds" : %d,
"ea" : { "ea" : {
"x" : %.1f, "x" : %.1f,
"p" : [ %s ],
"t" : %d, "t" : %d,
"h" : { "h" : {
"u" : %.2f, "u" : %.2f,
"c" : %.2f "c" : %.2f,
"p" : %.2f
}, },
"d" : { "d" : {
"u" : %.2f, "u" : %.2f,
"c" : %.2f "c" : %.2f,
"p" : %.2f
}, },
"m" : { "m" : {
"u" : %.2f, "u" : %.2f,
"c" : %.2f "c" : %.2f,
"p" : %.2f
} }
}, },
"c" : %lu "c" : %lu

View File

@@ -34,6 +34,19 @@
<option value="10YDK-1--------W" {eaDk1}>DK1</option> <option value="10YDK-1--------W" {eaDk1}>DK1</option>
<option value="10YDK-2--------M" {eaDk2}>DK2</option> <option value="10YDK-2--------M" {eaDk2}>DK2</option>
</optgroup> </optgroup>
<option value="10YAT-APG------L" {at}>Austria</option>
<option value="10YBE----------2" {be}>Belgium</option>
<option value="10YCZ-CEPS-----N" {cz}>Czech Republic</option>
<option value="10Y1001A1001A39I" {ee}>Estonia</option>
<option value="10YFI-1--------U" {fi}>Finland</option>
<option value="10YFR-RTE------C" {fr}>France</option>
<option value="10Y1001A1001A83F" {de}>Germany</option>
<option value="10YGB----------A" {gb}>Great Britain</option>
<option value="10YLV-1001A00074" {lv}>Latvia</option>
<option value="10YLT-1001A0008Q" {lt}>Lithuania</option>
<option value="10YNL----------L" {nl}>Netherland</option>
<option value="10YPL-AREA-----S" {pl}>Poland</option>
<option value="10YCH-SWISSGRIDZ" {ch}>Switzerland</option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -1,18 +1,21 @@
<div class="alert alert-danger"> <div class="alert alert-danger">
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.
</div> </div>
<div class="alert alert-warning"> <div class="alert alert-warning">
Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result 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.
<span id="fwDownload" style="display: none;"><br/>Download latest firmware file <a id="fwLink" href="#" data-chipset="{chipset}">here</a></span> <span id="fwDownload" style="display: none;"><br/>Download latest firmware file <a id="fwLink" href="#" data-chipset="{chipset}">here</a></span>
</div> </div>
<div class="alert alert-warning">
When using URL, only a valid ESP OTA server response will be accepted.
</div>
<form method="post" enctype="multipart/form-data" class="upload-form"> <form method="post" enctype="multipart/form-data" class="upload-form">
<div class="my-3 p-3 bg-white rounded shadow"> <div class="my-3 p-3 bg-white rounded shadow">
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="input-group mb-3"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">Upload</span> <span class="input-group-text">Upload file</span>
</div> </div>
<div class="custom-file"> <div class="custom-file">
<input name="file" type="file" class="custom-file-input" id="fileUploadField"> <input name="file" type="file" class="custom-file-input" id="fileUploadField">
@@ -21,6 +24,19 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-lg-6">or</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Use URL</span>
</div>
<input type="text" name="url" class="form-control"/>
</div>
</div>
</div>
</div> </div>
<hr/> <hr/>
<div class="row form-group"> <div class="row form-group">
@@ -28,7 +44,7 @@
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a> <a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
</div> </div>
<div class="col-6 text-right"> <div class="col-6 text-right">
<button class="btn btn-primary">Upload</button> <button class="btn btn-primary">Upgrade firmware</button>
</div> </div>
</div> </div>
</form> </form>

View File

@@ -1,6 +1,5 @@
<div id="newVersion" class="alert alert-info d-none">New version <span id="newVersionTag"></span>! <div id="newVersion" class="alert alert-info d-none">New version <span id="newVersionTag"></span>!
<a id="newVersionUrl" href="#" target="_blank">view</a> <a id="newVersionUrl" href="#" target="_blank">view</a> or <a href="javascript:upgrade();">click here to upgrade</a>
<span class="d-none ssl-capable"> or <a href="javascript:upgrade();">upgrade</a></span>
</div> </div>
</main> </main>

View File

@@ -3,5 +3,5 @@
"tPO" : %.2f, "tPO" : %.2f,
"tQI" : %.2f, "tQI" : %.2f,
"tQO" : %.2f, "tQO" : %.2f,
"rtc" : %llu "rtc" : %lu
} }

View File

@@ -113,7 +113,7 @@
<div class="col-xl-12 mb-3"> <div class="col-xl-12 mb-3">
<div class="bg-white rounded shadow pt-3 pb-3" style="font-size: 14px;"> <div class="bg-white rounded shadow pt-3 pb-3" style="font-size: 14px;">
<strong class="mr-3 ml-3">Real time calculation</strong><br/> <strong class="mr-3 ml-3">Real time consumption</strong><br/>
<div class="row"> <div class="row">
<div class="col-lg-3 col-sm-6"> <div class="col-lg-3 col-sm-6">
<div class="mr-3 ml-3 d-flex"> <div class="mr-3 ml-3 d-flex">
@@ -151,6 +151,35 @@
</div> </div>
</div> </div>
</div> </div>
<strong class="mr-3 ml-3 se d-none">Real time production</strong><br/>
<div class="row se d-none">
<div class="col-lg-3 col-sm-6">
<div class="mr-3 ml-3 d-flex">
<div>Hour</div>
<div class="flex-fill text-right">
<span id="eache"></span> kWh
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6">
<div class="mr-3 ml-3 d-flex">
<div>Day</div>
<div class="flex-fill text-right">
<span id="eacde"></span> kWh
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6">
<div class="mr-3 ml-3 d-flex">
<div>Month</div>
<div class="flex-fill text-right">
<span id="eacme"></span> kWh
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -13,6 +13,8 @@
"h" : %.2f, "h" : %.2f,
"d" : %.1f, "d" : %.1f,
"t" : %d, "t" : %d,
"x" : %.2f "x" : %.2f,
"he" : %.2f,
"de" : %.1f
} }
} }

View File

@@ -25,6 +25,8 @@
"h" : %.2f, "h" : %.2f,
"d" : %.1f, "d" : %.1f,
"t" : %d, "t" : %d,
"x" : %.2f "x" : %.2f,
"he" : %.2f,
"de" : %.1f
} }
} }

View File

@@ -30,6 +30,8 @@
"h" : %.2f, "h" : %.2f,
"d" : %.1f, "d" : %.1f,
"t" : %d, "t" : %d,
"x" : %.2f "x" : %.2f,
"he" : %.2f,
"de" : %.1f
} }
} }

View File

@@ -34,6 +34,8 @@
"h" : %.2f, "h" : %.2f,
"d" : %.1f, "d" : %.1f,
"t" : %d, "t" : %d,
"x" : %.2f "x" : %.2f,
"he" : %.2f,
"de" : %.1f
} }
} }

View File

@@ -4,5 +4,6 @@
"up" : %d, "up" : %d,
"vcc" : %.3f, "vcc" : %.3f,
"rssi": %d, "rssi": %d,
"temp": %.2f "temp": %.2f,
"version": "%s"
} }

20
web/realtime.json Normal file
View File

@@ -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
}
}

View File

@@ -18,9 +18,10 @@
</li> </li>
</ul> </ul>
</header> </header>
<div class="my-3 p-3 bg-white rounded shadow"> <div class="my-3 p-3 bg-white rounded shadow {rs}">
Device is rebooting. You will be redirected to the main page when it is available again. Device is rebooting. You will be redirected to the main page when it is available again.
</div> </div>
<div class="alert alert-warning shadow {us}">Firmware upgrade in progress, DO NOT disconnect power from the device. You will be redirected to the main page when firmware upgrade is complete.</div>
</main> </main>
<script> <script>
var tries = 0; var tries = 0;