From 148fb14c939509a4170a96dad1f60dc14edcb1b8 Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Fri, 2 Dec 2022 19:03:16 +0100 Subject: [PATCH] More v2.2 --- lib/AmsData/include/AmsData.h | 5 + lib/AmsData/src/AmsData.cpp | 8 + lib/ClassicUi/html/data.json | 9 +- lib/ClassicUi/src/AmsWebServer.cpp | 3 + .../include/EnergyAccounting.h | 20 +- lib/EnergyAccounting/src/EnergyAccounting.cpp | 89 +++- lib/SvelteUi/app/src/App.svelte | 2 +- .../app/src/lib/AccountingData.svelte | 6 +- .../app/src/lib/ConsentComponent.svelte | 2 +- .../app/src/lib/FileUploadComponent.svelte | 6 +- lib/SvelteUi/app/src/lib/Header.svelte | 21 +- lib/SvelteUi/app/src/lib/Helpers.js | 30 ++ lib/SvelteUi/app/src/lib/StatusPage.svelte | 14 +- lib/SvelteUi/app/vite.config.js | 23 +- lib/SvelteUi/include/AmsWebServer.h | 1 - lib/SvelteUi/json/conf_debug.json | 5 + lib/SvelteUi/json/conf_general.json | 7 + lib/SvelteUi/json/conf_gpio.json | 28 ++ lib/SvelteUi/json/conf_meter.json | 20 + lib/SvelteUi/json/conf_mqtt.json | 15 + lib/SvelteUi/json/conf_net.json | 11 + lib/SvelteUi/json/conf_price.json | 7 + lib/SvelteUi/json/conf_thresholds.json | 15 + lib/SvelteUi/json/conf_wifi.json | 6 + lib/SvelteUi/json/data.json | 10 +- lib/SvelteUi/json/peak.json | 4 + lib/SvelteUi/json/sysinfo.json | 25 + lib/SvelteUi/json/tariff.json | 17 + lib/SvelteUi/src/AmsWebServer.cpp | 448 +++++++++--------- platformio.ini | 2 +- src/AmsToMqttBridge.ino | 98 ++-- 31 files changed, 645 insertions(+), 312 deletions(-) create mode 100644 lib/SvelteUi/json/conf_debug.json create mode 100644 lib/SvelteUi/json/conf_general.json create mode 100644 lib/SvelteUi/json/conf_gpio.json create mode 100644 lib/SvelteUi/json/conf_meter.json create mode 100644 lib/SvelteUi/json/conf_mqtt.json create mode 100644 lib/SvelteUi/json/conf_net.json create mode 100644 lib/SvelteUi/json/conf_price.json create mode 100644 lib/SvelteUi/json/conf_thresholds.json create mode 100644 lib/SvelteUi/json/conf_wifi.json create mode 100644 lib/SvelteUi/json/peak.json create mode 100644 lib/SvelteUi/json/sysinfo.json create mode 100644 lib/SvelteUi/json/tariff.json diff --git a/lib/AmsData/include/AmsData.h b/lib/AmsData/include/AmsData.h index 9445472a..8c92f033 100644 --- a/lib/AmsData/include/AmsData.h +++ b/lib/AmsData/include/AmsData.h @@ -70,6 +70,9 @@ public: bool isThreePhase(); bool isTwoPhase(); + int8_t getLastError(); + void setLastError(int8_t); + protected: unsigned long lastUpdateMillis = 0; unsigned long lastList2 = 0; @@ -84,6 +87,8 @@ protected: float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0; double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0; bool threePhase = false, twoPhase = false, counterEstimated = false; + + int8_t lastError = 0x00; }; #endif diff --git a/lib/AmsData/src/AmsData.cpp b/lib/AmsData/src/AmsData.cpp index e6d536bf..6315658b 100644 --- a/lib/AmsData/src/AmsData.cpp +++ b/lib/AmsData/src/AmsData.cpp @@ -214,3 +214,11 @@ bool AmsData::isThreePhase() { bool AmsData::isTwoPhase() { return this->twoPhase; } + +int8_t AmsData::getLastError() { + return lastError; +} + +void AmsData::setLastError(int8_t lastError) { + this->lastError = lastError; +} \ No newline at end of file diff --git a/lib/ClassicUi/html/data.json b/lib/ClassicUi/html/data.json index a713055a..02bf78d0 100644 --- a/lib/ClassicUi/html/data.json +++ b/lib/ClassicUi/html/data.json @@ -40,17 +40,20 @@ "h" : { "u" : %.2f, "c" : %.2f, - "p" : %.2f + "p" : %.2f, + "i" : %.2f }, "d" : { "u" : %.2f, "c" : %.2f, - "p" : %.2f + "p" : %.2f, + "i" : %.2f }, "m" : { "u" : %.2f, "c" : %.2f, - "p" : %.2f + "p" : %.2f, + "i" : %.2f } }, "c" : %u diff --git a/lib/ClassicUi/src/AmsWebServer.cpp b/lib/ClassicUi/src/AmsWebServer.cpp index 9b2f0c9e..71da2b5f 100644 --- a/lib/ClassicUi/src/AmsWebServer.cpp +++ b/lib/ClassicUi/src/AmsWebServer.cpp @@ -776,12 +776,15 @@ void AmsWebServer::dataJson() { ea->getUseThisHour(), ea->getCostThisHour(), ea->getProducedThisHour(), + ea->getIncomeThisHour(), ea->getUseToday(), ea->getCostToday(), ea->getProducedToday(), + ea->getIncomeToday(), ea->getUseThisMonth(), ea->getCostThisMonth(), ea->getProducedThisMonth(), + ea->getIncomeThisMonth(), (uint32_t) time(nullptr) ); diff --git a/lib/EnergyAccounting/include/EnergyAccounting.h b/lib/EnergyAccounting/include/EnergyAccounting.h index 2e9c02a6..912fd870 100644 --- a/lib/EnergyAccounting/include/EnergyAccounting.h +++ b/lib/EnergyAccounting/include/EnergyAccounting.h @@ -12,6 +12,18 @@ struct EnergyAccountingPeak { }; struct EnergyAccountingData { + uint8_t version; + uint8_t month; + uint16_t costYesterday; + uint16_t costThisMonth; + uint16_t costLastMonth; + uint16_t incomeYesterday; + uint16_t incomeThisMonth; + uint16_t incomeLastMonth; + EnergyAccountingPeak peaks[5]; +}; + +struct EnergyAccountingData4 { uint8_t version; uint8_t month; uint16_t costYesterday; @@ -55,6 +67,12 @@ public: double getCostThisMonth(); uint16_t getCostLastMonth(); + double getIncomeThisHour(); + double getIncomeToday(); + double getIncomeYesterday(); + double getIncomeThisMonth(); + uint16_t getIncomeLastMonth(); + float getMonthMax(); uint8_t getCurrentThreshold(); EnergyAccountingPeak getPeak(uint8_t); @@ -72,7 +90,7 @@ private: Timezone *tz = NULL; uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0; double use, costHour, costDay; - double produce; + double produce, incomeHour, incomeDay; EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 }; void calcDayCost(); diff --git a/lib/EnergyAccounting/src/EnergyAccounting.cpp b/lib/EnergyAccounting/src/EnergyAccounting.cpp index a4263547..6e718213 100644 --- a/lib/EnergyAccounting/src/EnergyAccounting.cpp +++ b/lib/EnergyAccounting/src/EnergyAccounting.cpp @@ -45,8 +45,9 @@ bool EnergyAccounting::update(AmsData* amsData) { if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now); if(!load()) { if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n"); - data = { 4, local.Month, - 0, 0, 0, + data = { 5, local.Month, + 0, 0, 0, // Cost + 0, 0, 0, // Income 0, 0, // Peak 1 0, 0, // Peak 2 0, 0, // Peak 3 @@ -58,6 +59,7 @@ bool EnergyAccounting::update(AmsData* amsData) { debugger->printf("(EnergyAccounting) Peak hour from day %d: %d\n", data.peaks[i].day, data.peaks[i].value*10); } debugger->printf("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n", data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth); + debugger->printf("(EnergyAccounting) Loaded income yesterday: %.2f, this month: %d, last month: %d\n", data.incomeYesterday / 10.0, data.incomeThisMonth, data.incomeLastMonth); } init = true; } @@ -70,11 +72,14 @@ bool EnergyAccounting::update(AmsData* amsData) { if(local.Hour != currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) { if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New local hour %d\n", local.Hour); - tmElements_t oneHrAgo; + tmElements_t oneHrAgo, oneHrAgoLocal; breakTime(now-3600, oneHrAgo); uint16_t val = ds->getHourImport(oneHrAgo.Hour) / 10; - ret |= updateMax(val, local.Day); + breakTime(tz->toLocal(now-3600), oneHrAgoLocal); + ret |= updateMax(val, oneHrAgoLocal.Day); + + currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated if(local.Hour > 0) { calcDayCost(); } @@ -82,13 +87,18 @@ bool EnergyAccounting::update(AmsData* amsData) { use = 0; produce = 0; costHour = 0; - currentHour = local.Hour; + incomeHour = 0; if(local.Day != currentDay) { if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New day %d\n", local.Day); data.costYesterday = costDay * 10; data.costThisMonth += costDay; costDay = 0; + + data.incomeYesterday = incomeDay * 10; + data.incomeThisMonth += incomeDay; + incomeDay = 0; + currentDay = local.Day; ret = true; } @@ -97,6 +107,8 @@ bool EnergyAccounting::update(AmsData* amsData) { if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New month %d\n", local.Month); data.costLastMonth = data.costThisMonth; data.costThisMonth = 0; + data.incomeLastMonth = data.incomeThisMonth; + data.incomeThisMonth = 0; for(uint8_t i = 0; i < 5; i++) { data.peaks[i] = { 0, 0 }; } @@ -124,6 +136,13 @@ bool EnergyAccounting::update(AmsData* amsData) { if(kwhe > 0) { if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh export\n", kwhe); produce += kwhe; + if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) { + float price = eapi->getValueForHour(0); + float income = price * kwhe; + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", income / 100.0, eapi->getCurrency()); + incomeHour += income; + incomeDay += income; + } } if(config != NULL) { @@ -141,13 +160,20 @@ void EnergyAccounting::calcDayCost() { breakTime(tz->toLocal(now), local); if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) { - if(initPrice) costDay = 0; + if(initPrice) { + costDay = 0; + incomeDay = 0; + } for(int i = 0; i < currentHour; i++) { - float price = eapi->getValueForHour(i - currentHour); + float price = eapi->getValueForHour(i - local.Hour); if(price == ENTSOE_NO_VALUE) break; - breakTime(now - ((currentHour - i) * 3600), utc); + breakTime(now - ((local.Hour - i) * 3600), utc); + int16_t wh = ds->getHourImport(utc.Hour); costDay += price * (wh / 1000.0); + + wh = ds->getHourExport(utc.Hour); + incomeDay += price * (wh / 1000.0); } initPrice = true; } @@ -161,9 +187,10 @@ double EnergyAccounting::getUseToday() { float ret = 0.0; time_t now = time(nullptr); if(now < BUILD_EPOCH) return 0; - tmElements_t utc; + tmElements_t utc, local; + breakTime(tz->toLocal(now), local); for(int i = 0; i < currentHour; i++) { - breakTime(now - ((currentHour - i) * 3600), utc); + breakTime(now - ((local.Hour - i) * 3600), utc); ret += ds->getHourImport(utc.Hour) / 1000.0; } return ret + getUseThisHour(); @@ -226,6 +253,26 @@ uint16_t EnergyAccounting::getCostLastMonth() { return data.costLastMonth; } +double EnergyAccounting::getIncomeThisHour() { + return incomeHour; +} + +double EnergyAccounting::getIncomeToday() { + return incomeDay; +} + +double EnergyAccounting::getIncomeYesterday() { + return data.incomeYesterday / 10.0; +} + +double EnergyAccounting::getIncomeThisMonth() { + return data.incomeThisMonth + getIncomeToday(); +} + +uint16_t EnergyAccounting::getIncomeLastMonth() { + return data.incomeLastMonth; +} + uint8_t EnergyAccounting::getCurrentThreshold() { if(config == NULL) return 0; @@ -309,14 +356,27 @@ bool EnergyAccounting::load() { file.readBytes(buf, file.size()); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Data version %d\n", buf[0]); - if(buf[0] == 4) { + if(buf[0] == 5) { EnergyAccountingData* data = (EnergyAccountingData*) buf; memcpy(&this->data, data, sizeof(this->data)); ret = true; + } else if(buf[0] == 4) { + EnergyAccountingData4* data = (EnergyAccountingData4*) buf; + this->data = { 5, data->month, + (uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100), + 0,0,0, // Income from production + data->peaks[0].day, data->peaks[0].value, + data->peaks[1].day, data->peaks[1].value, + data->peaks[2].day, data->peaks[2].value, + data->peaks[3].day, data->peaks[3].value, + data->peaks[4].day, data->peaks[4].value + }; + ret = true; } else if(buf[0] == 3) { EnergyAccountingData* data = (EnergyAccountingData*) buf; - this->data = { 4, data->month, + this->data = { 5, data->month, (uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100), + 0,0,0, // Income from production data->peaks[0].day, data->peaks[0].value, data->peaks[1].day, data->peaks[1].value, data->peaks[2].day, data->peaks[2].value, @@ -325,8 +385,9 @@ bool EnergyAccounting::load() { }; ret = true; } else { - data = { 4, 0, - 0, 0, 0, + data = { 5, 0, + 0, 0, 0, // Cost + 0,0,0, // Income from production 0, 0, // Peak 1 0, 0, // Peak 2 0, 0, // Peak 3 diff --git a/lib/SvelteUi/app/src/App.svelte b/lib/SvelteUi/app/src/App.svelte index f07b05a0..19b4d9c9 100644 --- a/lib/SvelteUi/app/src/App.svelte +++ b/lib/SvelteUi/app/src/App.svelte @@ -31,7 +31,7 @@
-
+
diff --git a/lib/SvelteUi/app/src/lib/AccountingData.svelte b/lib/SvelteUi/app/src/lib/AccountingData.svelte index 544f9743..c21dafbb 100644 --- a/lib/SvelteUi/app/src/lib/AccountingData.svelte +++ b/lib/SvelteUi/app/src/lib/AccountingData.svelte @@ -25,15 +25,15 @@ {#if hasExport}
Hour
-
{data.h.p ? data.h.p.toFixed(2) : '-'} kWh {#if currency}/ {data.h.pc ? data.h.pc.toFixed(2) : '-'} {currency}{/if}
+
{data.h.p ? data.h.p.toFixed(2) : '-'} kWh {#if currency}/ {data.h.i ? data.h.i.toFixed(2) : '-'} {currency}{/if}
Day
-
{data.d.p ? data.d.p.toFixed(1) : '-'} kWh {#if currency}/ {data.d.pc ? data.d.pc.toFixed(2) : '-'} {currency}{/if}
+
{data.d.p ? data.d.p.toFixed(1) : '-'} kWh {#if currency}/ {data.d.i ? data.d.i.toFixed(2) : '-'} {currency}{/if}
Month
-
{data.m.p ? data.m.p.toFixed(0) : '-'} kWh {#if currency}/ {data.m.pc ? data.m.pc.toFixed(2) : '-'} {currency}{/if}
+
{data.m.p ? data.m.p.toFixed(0) : '-'} kWh {#if currency}/ {data.m.i ? data.m.i.toFixed(2) : '-'} {currency}{/if}
{:else}
diff --git a/lib/SvelteUi/app/src/lib/ConsentComponent.svelte b/lib/SvelteUi/app/src/lib/ConsentComponent.svelte index d1ed8a81..8bfa9007 100644 --- a/lib/SvelteUi/app/src/lib/ConsentComponent.svelte +++ b/lib/SvelteUi/app/src/lib/ConsentComponent.svelte @@ -36,7 +36,7 @@
- Below are some stuff we need to know + Various permissions we need to do stuff:

diff --git a/lib/SvelteUi/app/src/lib/FileUploadComponent.svelte b/lib/SvelteUi/app/src/lib/FileUploadComponent.svelte index 46c4dc78..eee6c877 100644 --- a/lib/SvelteUi/app/src/lib/FileUploadComponent.svelte +++ b/lib/SvelteUi/app/src/lib/FileUploadComponent.svelte @@ -1,14 +1,17 @@
Upload {title}

Select a suitable file and click upload

- + uploading=true}>
@@ -16,3 +19,4 @@
+ diff --git a/lib/SvelteUi/app/src/lib/Header.svelte b/lib/SvelteUi/app/src/lib/Header.svelte index c6265dd1..bb41bf27 100644 --- a/lib/SvelteUi/app/src/lib/Header.svelte +++ b/lib/SvelteUi/app/src/lib/Header.svelte @@ -2,7 +2,7 @@ import { Link } from "svelte-navigator"; import { sysinfoStore, getGitHubReleases, gitHubReleaseStore } from './DataStores.js'; import { upgrade, getNextVersion } from './UpgradeHelper'; - import { boardtype } from './Helpers.js'; + import { boardtype, hanError, mqttError } from './Helpers.js'; import GitHubLogo from './../assets/github.svg'; import Uptime from "./Uptime.svelte"; import Badge from './Badge.svelte'; @@ -13,7 +13,7 @@ import DownloadIcon from "./DownloadIcon.svelte"; export let data = {} - export let sysinfo = {} + let sysinfo = {} let nextVersion = {}; @@ -28,11 +28,16 @@ } } } + sysinfoStore.subscribe(update => { + sysinfo = update; + if(update.fwconsent === 1) { + getGitHubReleases(); + } + }); gitHubReleaseStore.subscribe(releases => { nextVersion = getNextVersion(sysinfo.version, releases); }); - getGitHubReleases();
-
+
2.0 ? data.v.toFixed(2)+"V" : "ESP"} color={sysinfo.booting ? 'yellow' : data.em === 1 ? 'green' : data.em === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
-
+ {#if data.he < 0} +
{ 'HAN error: ' + hanError(data.he) }
+ {/if} + {#if data.me < 0} +
{ 'MQTT error: ' + mqttError(data.me) }
+ {/if} +
diff --git a/lib/SvelteUi/app/src/lib/Helpers.js b/lib/SvelteUi/app/src/lib/Helpers.js index 14ce27c6..2cb3ea50 100644 --- a/lib/SvelteUi/app/src/lib/Helpers.js +++ b/lib/SvelteUi/app/src/lib/Helpers.js @@ -91,3 +91,33 @@ export function boardtype(c, b) { return "Generic ESP8266"; } } + +export function hanError(err) { + switch(err) { + case -1: return "Parse error"; + case -2: return "Incomplete data received"; + case -3: return "Payload boundry flag missing"; + case -4: return "Header checksum error"; + case -5: return "Footer checksum error"; + case -9: return "Unknown data received, check meter config"; + case -41: return "Frame length not equal"; + case -51: return "Authentication failed"; + case -52: return "Decryption failed"; + case -53: return "Encryption key invalid"; + } + if(err < 0) return "Unspecified error "+err; + return ""; +} + +export function mqttError(err) { + switch(err) { + case -3: return "Connection failed"; + case -4: return "Network timeout"; + case -10: return "Connection denied"; + case -11: return "Failed to subscribe"; + case -13: return "Connection lost"; + } + + if(err < 0) return "Unspecified error "+err; + return ""; +} \ No newline at end of file diff --git a/lib/SvelteUi/app/src/lib/StatusPage.svelte b/lib/SvelteUi/app/src/lib/StatusPage.svelte index 18e0bfcc..5cc83797 100644 --- a/lib/SvelteUi/app/src/lib/StatusPage.svelte +++ b/lib/SvelteUi/app/src/lib/StatusPage.svelte @@ -4,6 +4,7 @@ import { upgrade, getNextVersion } from './UpgradeHelper'; import DownloadIcon from './DownloadIcon.svelte'; import { Link } from 'svelte-navigator'; + import Mask from './Mask.svelte'; export let sysinfo; @@ -45,6 +46,8 @@ } let fileinput; + let files = []; + let uploading = false; getSysinfo(); @@ -125,15 +128,16 @@
{/if}
-
- - {#if fileinput && fileinput.files.length == 0} + uploading=true}> + + {#if files.length == 0} - {:else if fileinput} - {fileinput.files[0].name} + {:else} + {files[0].name} {/if}
+ diff --git a/lib/SvelteUi/app/vite.config.js b/lib/SvelteUi/app/vite.config.js index dbfba4be..af80afb9 100644 --- a/lib/SvelteUi/app/vite.config.js +++ b/lib/SvelteUi/app/vite.config.js @@ -17,18 +17,17 @@ export default defineConfig({ plugins: [svelte()], server: { proxy: { - "/data.json": "http://192.168.233.244", - "/energyprice.json": "http://192.168.233.244", - "/dayplot.json": "http://192.168.233.244", - "/monthplot.json": "http://192.168.233.244", - "/temperature.json": "http://192.168.233.244", - "/sysinfo.json": "http://192.168.233.244", - "/configuration.json": "http://192.168.233.244", - "/tariff.json": "http://192.168.233.244", - "/save": "http://192.168.233.244", - "/reboot": "http://192.168.233.244", - "/firmware": "http://192.168.233.244", - "/upgrade": "http://192.168.233.244" + "/data.json": "http://192.168.233.229", + "/energyprice.json": "http://192.168.233.229", + "/dayplot.json": "http://192.168.233.229", + "/monthplot.json": "http://192.168.233.229", + "/temperature.json": "http://192.168.233.229", + "/sysinfo.json": "http://192.168.233.229", + "/configuration.json": "http://192.168.233.229", + "/tariff.json": "http://192.168.233.229", + "/save": "http://192.168.233.229", + "/reboot": "http://192.168.233.229", + "/upgrade": "http://192.168.233.229" } } }) diff --git a/lib/SvelteUi/include/AmsWebServer.h b/lib/SvelteUi/include/AmsWebServer.h index 4dc01981..6ad94d36 100644 --- a/lib/SvelteUi/include/AmsWebServer.h +++ b/lib/SvelteUi/include/AmsWebServer.h @@ -83,7 +83,6 @@ private: void monthplotJson(); void energyPriceJson(); void temperatureJson(); - void wifiScanJson(); void tariffJson(); void configurationJson(); diff --git a/lib/SvelteUi/json/conf_debug.json b/lib/SvelteUi/json/conf_debug.json new file mode 100644 index 00000000..e9ec284b --- /dev/null +++ b/lib/SvelteUi/json/conf_debug.json @@ -0,0 +1,5 @@ +"d": { + "s": %s, + "t": %s, + "l": %d +}, diff --git a/lib/SvelteUi/json/conf_general.json b/lib/SvelteUi/json/conf_general.json new file mode 100644 index 00000000..b1333cef --- /dev/null +++ b/lib/SvelteUi/json/conf_general.json @@ -0,0 +1,7 @@ +"g": { + "t": "%s", + "h": "%s", + "s": %d, + "u": "%s", + "p": "%s" +}, \ No newline at end of file diff --git a/lib/SvelteUi/json/conf_gpio.json b/lib/SvelteUi/json/conf_gpio.json new file mode 100644 index 00000000..4cea40be --- /dev/null +++ b/lib/SvelteUi/json/conf_gpio.json @@ -0,0 +1,28 @@ +"i": { + "h": %s, + "a": %s, + "l": { + "p": %s, + "i": %s + }, + "r": { + "r": %s, + "g": %s, + "b": %s, + "i": %s + }, + "t": { + "d": %s, + "a": %s + }, + "v": { + "p": %s, + "o": %.2f, + "m": %.3f, + "d": { + "v": %d, + "g": %d + }, + "b": %.1f + } +} diff --git a/lib/SvelteUi/json/conf_meter.json b/lib/SvelteUi/json/conf_meter.json new file mode 100644 index 00000000..b31d753f --- /dev/null +++ b/lib/SvelteUi/json/conf_meter.json @@ -0,0 +1,20 @@ +"m": { + "b": %d, + "p": %d, + "i": %s, + "d": %d, + "f": %d, + "r": %d, + "e": { + "e": %s, + "k": "%s", + "a": "%s" + }, + "m": { + "e": %s, + "w": %.3f, + "v": %.3f, + "a": %.3f, + "c": %.3f + } +}, \ No newline at end of file diff --git a/lib/SvelteUi/json/conf_mqtt.json b/lib/SvelteUi/json/conf_mqtt.json new file mode 100644 index 00000000..c0030dbb --- /dev/null +++ b/lib/SvelteUi/json/conf_mqtt.json @@ -0,0 +1,15 @@ +"q": { + "h": "%s", + "p": %d, + "u": "%s", + "a": "%s", + "c": "%s", + "b": "%s", + "m": %d, + "s": { + "e": %s, + "c": %s, + "r": %s, + "k": %s + } +}, diff --git a/lib/SvelteUi/json/conf_net.json b/lib/SvelteUi/json/conf_net.json new file mode 100644 index 00000000..4eaa1141 --- /dev/null +++ b/lib/SvelteUi/json/conf_net.json @@ -0,0 +1,11 @@ +"n": { + "m": "%s", + "i": "%s", + "s": "%s", + "g": "%s", + "d1": "%s", + "d2": "%s", + "d": %s, + "n1": "%s", + "h": %s +}, diff --git a/lib/SvelteUi/json/conf_price.json b/lib/SvelteUi/json/conf_price.json new file mode 100644 index 00000000..124b4697 --- /dev/null +++ b/lib/SvelteUi/json/conf_price.json @@ -0,0 +1,7 @@ +"p": { + "e": %s, + "t": "%s", + "r": "%s", + "c": "%s", + "m": %.3f +}, diff --git a/lib/SvelteUi/json/conf_thresholds.json b/lib/SvelteUi/json/conf_thresholds.json new file mode 100644 index 00000000..fd911368 --- /dev/null +++ b/lib/SvelteUi/json/conf_thresholds.json @@ -0,0 +1,15 @@ +"t": { + "t": [ + %d, + %d, + %d, + %d, + %d, + %d, + %d, + %d, + %d, + %d + ], + "h": %d +}, \ No newline at end of file diff --git a/lib/SvelteUi/json/conf_wifi.json b/lib/SvelteUi/json/conf_wifi.json new file mode 100644 index 00000000..9071e9b7 --- /dev/null +++ b/lib/SvelteUi/json/conf_wifi.json @@ -0,0 +1,6 @@ +"w": { + "s": "%s", + "p": "%s", + "w": %.1f, + "z": %d +}, diff --git a/lib/SvelteUi/json/data.json b/lib/SvelteUi/json/data.json index eefea149..173c115f 100644 --- a/lib/SvelteUi/json/data.json +++ b/lib/SvelteUi/json/data.json @@ -40,19 +40,23 @@ "h" : { "u" : %.2f, "c" : %.2f, - "p" : %.2f + "p" : %.2f, + "i" : %.2f }, "d" : { "u" : %.2f, "c" : %.2f, - "p" : %.2f + "p" : %.2f, + "i" : %.2f }, "m" : { "u" : %.2f, "c" : %.2f, - "p" : %.2f + "p" : %.2f, + "i" : %.2f } }, "pr" : "%s", + "he" : %d, "c" : %lu } \ No newline at end of file diff --git a/lib/SvelteUi/json/peak.json b/lib/SvelteUi/json/peak.json new file mode 100644 index 00000000..7f58d14c --- /dev/null +++ b/lib/SvelteUi/json/peak.json @@ -0,0 +1,4 @@ +{ + "d": %d, + "v": %.2f +} \ No newline at end of file diff --git a/lib/SvelteUi/json/sysinfo.json b/lib/SvelteUi/json/sysinfo.json new file mode 100644 index 00000000..7844d7ee --- /dev/null +++ b/lib/SvelteUi/json/sysinfo.json @@ -0,0 +1,25 @@ +{ + "version": "%s", + "chip": "%s", + "chipId": "%s", + "mac": "%s", + "board": %d, + "vndcfg": %s, + "usrcfg": %s, + "fwconsent": %d, + "hostname": "%s", + "booting": %s, + "upgrading": %s, + "net": { + "ip": "%s", + "mask": "%s", + "gw": "%s", + "dns1": "%s", + "dns2": "%s" + }, + "meter": { + "mfg": %d, + "model": "%s", + "id": "%s" + } +} \ No newline at end of file diff --git a/lib/SvelteUi/json/tariff.json b/lib/SvelteUi/json/tariff.json new file mode 100644 index 00000000..e832ff11 --- /dev/null +++ b/lib/SvelteUi/json/tariff.json @@ -0,0 +1,17 @@ +{ + "t": [ + %d, + %d, + %d, + %d, + %d, + %d, + %d, + %d, + %d, + %d + ], + "p": [ %s ], + "c": %d, + "m": %.2f +} \ No newline at end of file diff --git a/lib/SvelteUi/src/AmsWebServer.cpp b/lib/SvelteUi/src/AmsWebServer.cpp index a691c74a..003423cc 100644 --- a/lib/SvelteUi/src/AmsWebServer.cpp +++ b/lib/SvelteUi/src/AmsWebServer.cpp @@ -3,8 +3,6 @@ #include "base64.h" #include "hexutils.h" -#include - #include "html/index_html.h" #include "html/index_css.h" #include "html/index_js.h" @@ -15,6 +13,18 @@ #include "html/energyprice_json.h" #include "html/tempsensor_json.h" #include "html/response_json.h" +#include "html/sysinfo_json.h" +#include "html/tariff_json.h" +#include "html/peak_json.h" +#include "html/conf_general_json.h" +#include "html/conf_meter_json.h" +#include "html/conf_wifi_json.h" +#include "html/conf_net_json.h" +#include "html/conf_mqtt_json.h" +#include "html/conf_price_json.h" +#include "html/conf_thresholds_json.h" +#include "html/conf_debug_json.h" +#include "html/conf_gpio_json.h" #include "version.h" @@ -60,8 +70,6 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter server.on(F("/temperature.json"), HTTP_GET, std::bind(&AmsWebServer::temperatureJson, this)); server.on(F("/tariff.json"), HTTP_GET, std::bind(&AmsWebServer::tariffJson, this)); - server.on(F("/wifiscan.json"), HTTP_GET, std::bind(&AmsWebServer::wifiScanJson, this)); - server.on(F("/configuration.json"), HTTP_GET, std::bind(&AmsWebServer::configurationJson, this)); server.on(F("/save"), HTTP_POST, std::bind(&AmsWebServer::handleSave, this)); server.on(F("/reboot"), HTTP_POST, std::bind(&AmsWebServer::reboot, this)); @@ -169,17 +177,8 @@ void AmsWebServer::faviconIco() { void AmsWebServer::sysinfoJson() { if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /sysinfo.json over http...\n"); - DynamicJsonDocument doc(1024); - doc[F("version")] = VERSION; - #if defined(CONFIG_IDF_TARGET_ESP32S2) - doc[F("chip")] = "esp32s2"; - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - doc[F("chip")] = "esp32c3"; - #elif defined(ESP32) - doc[F("chip")] = "esp32"; - #elif defined(ESP8266) - doc[F("chip")] = "esp8266"; - #endif + SystemConfig sys; + config->getSystemConfig(sys); uint32_t chipId; #if defined(ESP32) @@ -188,39 +187,54 @@ void AmsWebServer::sysinfoJson() { chipId = ESP.getChipId(); #endif String chipIdStr = String(chipId, HEX); - doc[F("chipId")] = chipIdStr; - doc[F("mac")] = WiFi.macAddress(); - - SystemConfig sys; - config->getSystemConfig(sys); - doc[F("board")] = sys.boardType; - doc[F("vndcfg")] = sys.vendorConfigured; - doc[F("usrcfg")] = sys.userConfigured; - doc[F("fwconsent")] = sys.dataCollectionConsent; - doc[F("country")] = sys.country; + String hostname; if(sys.userConfigured) { WiFiConfig wifiConfig; config->getWiFiConfig(wifiConfig); - doc[F("hostname")] = wifiConfig.hostname; + hostname = String(wifiConfig.hostname); } else { - doc[F("hostname")] = "ams-"+chipIdStr; + hostname = "ams-"+chipIdStr; } - doc[F("booting")] = performRestart; - doc[F("upgrading")] = rebootForUpgrade; + IPAddress dns1 = WiFi.dnsIP(0); + IPAddress dns2 = WiFi.dnsIP(1); - doc[F("net")][F("ip")] = WiFi.localIP().toString(); - doc[F("net")][F("mask")] = WiFi.subnetMask().toString(); - doc[F("net")][F("gw")] = WiFi.gatewayIP().toString(); - doc[F("net")][F("dns1")] = WiFi.dnsIP(0).toString(); - doc[F("net")][F("dns2")] = WiFi.dnsIP(1).toString(); + snprintf_P(buf, BufferSize, SYSINFO_JSON, + VERSION, + #if defined(CONFIG_IDF_TARGET_ESP32S2) + "esp32s2", + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + "esp32c3", + #elif defined(ESP32) + "esp32", + #elif defined(ESP8266) + "esp8266", + #endif + chipIdStr.c_str(), + WiFi.macAddress().c_str(), + sys.boardType, + sys.vendorConfigured ? "true" : "false", + sys.userConfigured ? "true" : "false", + sys.dataCollectionConsent, + hostname.c_str(), + performRestart ? "true" : "false", + rebootForUpgrade ? "true" : "false", + WiFi.localIP().toString().c_str(), + WiFi.subnetMask().toString().c_str(), + WiFi.gatewayIP().toString().c_str(), + dns1.isSet() ? dns1.toString().c_str() : "", + dns2.isSet() ? dns2.toString().c_str() : "", + meterState->getMeterType(), + meterState->getMeterModel().c_str(), + meterState->getMeterId().c_str() + ); - doc[F("meter")][F("mfg")] = meterState->getMeterType(); - doc[F("meter")][F("model")] = meterState->getMeterModel(); - doc[F("meter")][F("id")] = meterState->getMeterId(); + server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); + server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE); + server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF); - serializeJson(doc, buf, BufferSize); + server.setContentLength(strlen(buf)); server.send(200, MIME_JSON, buf); server.handleClient(); @@ -276,7 +290,9 @@ void AmsWebServer::dataJson() { uint8_t hanStatus; - if(meterConfig->baud == 0 || meterState->getLastUpdateMillis() == 0) { + if(meterState->getLastError() < 0) { + hanStatus = 3; + } else if((meterConfig->baud == 0 || meterState->getLastUpdateMillis() == 0) && now < 15000) { hanStatus = 0; } else if(now - meterState->getLastUpdateMillis() < 15000) { hanStatus = 1; @@ -313,7 +329,7 @@ void AmsWebServer::dataJson() { String peaks = ""; for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) { if(!peaks.isEmpty()) peaks += ","; - peaks += String(ea->getPeak(i).value); + peaks += String(ea->getPeak(i).value / 100.0); } snprintf_P(buf, BufferSize, DATA_JSON, @@ -357,13 +373,17 @@ void AmsWebServer::dataJson() { ea->getUseThisHour(), ea->getCostThisHour(), ea->getProducedThisHour(), + ea->getIncomeThisHour(), ea->getUseToday(), ea->getCostToday(), ea->getProducedToday(), + ea->getIncomeToday(), ea->getUseThisMonth(), ea->getCostThisMonth(), ea->getProducedThisMonth(), + ea->getIncomeThisMonth(), eapi == NULL ? "" : eapi->getArea(), + meterState->getLastError(), (uint32_t) time(nullptr) ); @@ -665,28 +685,13 @@ void AmsWebServer::indexJs() { void AmsWebServer::configurationJson() { if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /configuration.json over http...\n"); - server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); - server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE); - server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF); - if(!checkSecurity(1)) return; - DynamicJsonDocument doc(2048); - doc[F("version")] = VERSION; - NtpConfig ntpConfig; config->getNtpConfig(ntpConfig); WiFiConfig wifiConfig; config->getWiFiConfig(wifiConfig); - WebConfig webConfig; - config->getWebConfig(webConfig); - - doc[F("g")][F("t")] = ntpConfig.timezone; - doc[F("g")][F("h")] = wifiConfig.hostname; - doc[F("g")][F("s")] = webConfig.security; - doc[F("g")][F("u")] = webConfig.username; - doc[F("g")][F("p")] = strlen(webConfig.password) > 0 ? "***" : ""; bool encen = false; for(uint8_t i = 0; i < 16; i++) { @@ -695,166 +700,143 @@ void AmsWebServer::configurationJson() { } } - config->getMeterConfig(*meterConfig); - doc[F("m")][F("b")] = meterConfig->baud; - doc[F("m")][F("p")] = meterConfig->parity; - doc[F("m")][F("i")] = meterConfig->invert; - doc[F("m")][F("d")] = meterConfig->distributionSystem; - doc[F("m")][F("f")] = meterConfig->mainFuse; - doc[F("m")][F("r")] = meterConfig->productionCapacity; - doc[F("m")][F("e")][F("e")] = encen; - doc[F("m")][F("e")][F("k")] = toHex(meterConfig->encryptionKey, 16); - doc[F("m")][F("e")][F("a")] = toHex(meterConfig->authenticationKey, 16); - doc[F("m")][F("m")][F("e")] = meterConfig->wattageMultiplier > 1 || meterConfig->voltageMultiplier > 1 || meterConfig->amperageMultiplier > 1 || meterConfig->accumulatedMultiplier > 1; - doc[F("m")][F("m")][F("w")] = meterConfig->wattageMultiplier / 1000.0; - doc[F("m")][F("m")][F("v")] = meterConfig->voltageMultiplier / 1000.0; - doc[F("m")][F("m")][F("a")] = meterConfig->amperageMultiplier / 1000.0; - doc[F("m")][F("m")][F("c")] = meterConfig->accumulatedMultiplier / 1000.0; - - EnergyAccountingConfig eac; - config->getEnergyAccountingConfig(eac); - doc[F("t")][F("t")][0] = eac.thresholds[0]; - doc[F("t")][F("t")][1] = eac.thresholds[1]; - doc[F("t")][F("t")][2] = eac.thresholds[2]; - doc[F("t")][F("t")][3] = eac.thresholds[3]; - doc[F("t")][F("t")][4] = eac.thresholds[4]; - doc[F("t")][F("t")][5] = eac.thresholds[5]; - doc[F("t")][F("t")][6] = eac.thresholds[6]; - doc[F("t")][F("t")][7] = eac.thresholds[7]; - doc[F("t")][F("t")][8] = eac.thresholds[8]; - doc[F("t")][F("t")][9] = eac.thresholds[9]; - doc[F("t")][F("h")] = eac.hours; - - doc[F("w")][F("s")] = wifiConfig.ssid; - doc[F("w")][F("p")] = strlen(wifiConfig.psk) > 0 ? "***" : ""; - doc[F("w")][F("w")] = wifiConfig.power / 10.0; - doc[F("w")][F("z")] = wifiConfig.sleep; - - doc[F("n")][F("m")] = strlen(wifiConfig.ip) > 0 ? "static" : "dhcp"; - doc[F("n")][F("i")] = wifiConfig.ip; - doc[F("n")][F("s")] = wifiConfig.subnet; - doc[F("n")][F("g")] = wifiConfig.gateway; - doc[F("n")][F("d1")] = wifiConfig.dns1; - doc[F("n")][F("d2")] = wifiConfig.dns2; - doc[F("n")][F("d")] = wifiConfig.mdns; - doc[F("n")][F("n1")] = ntpConfig.server; - doc[F("n")][F("h")] = ntpConfig.dhcp; - + EnergyAccountingConfig* eac = ea->getConfig(); MqttConfig mqttConfig; config->getMqttConfig(mqttConfig); - doc[F("q")][F("h")] = mqttConfig.host; - doc[F("q")][F("p")] = mqttConfig.port; - doc[F("q")][F("u")] = mqttConfig.username; - doc[F("q")][F("a")] = strlen(mqttConfig.password) > 0 ? "***" : ""; - doc[F("q")][F("c")] = mqttConfig.clientId; - doc[F("q")][F("b")] = mqttConfig.publishTopic; - doc[F("q")][F("m")] = mqttConfig.payloadFormat; - doc[F("q")][F("s")][F("e")] = mqttConfig.ssl; - - if(LittleFS.begin()) { - doc[F("q")][F("s")][F("c")] = LittleFS.exists(FILE_MQTT_CA); - doc[F("q")][F("s")][F("r")] = LittleFS.exists(FILE_MQTT_CERT); - doc[F("q")][F("s")][F("k")] = LittleFS.exists(FILE_MQTT_KEY); - LittleFS.end(); - } else { - doc[F("q")][F("s")][F("c")] = false; - doc[F("q")][F("s")][F("r")] = false; - doc[F("q")][F("s")][F("k")] = false; - } EntsoeConfig entsoe; config->getEntsoeConfig(entsoe); - doc[F("p")][F("e")] = strlen(entsoe.token) > 0; - doc[F("p")][F("t")] = entsoe.token; - doc[F("p")][F("r")] = entsoe.area; - doc[F("p")][F("c")] = entsoe.currency; - doc[F("p")][F("m")] = entsoe.multiplier / 1000.0; - DebugConfig debugConfig; config->getDebugConfig(debugConfig); - doc[F("d")][F("s")] = debugConfig.serial; - doc[F("d")][F("t")] = debugConfig.telnet; - doc[F("d")][F("l")] = debugConfig.level; - GpioConfig gpioConfig; - config->getGpioConfig(gpioConfig); - if(gpioConfig.hanPin == 0xff) - doc[F("i")][F("h")] = nullptr; - else - doc[F("i")][F("h")] = gpioConfig.hanPin; - - if(gpioConfig.apPin == 0xff) - doc[F("i")][F("a")] = nullptr; - else - doc[F("i")][F("a")] = gpioConfig.apPin; - - if(gpioConfig.ledPin == 0xff) - doc[F("i")][F("l")][F("p")] = nullptr; - else - doc[F("i")][F("l")][F("p")] = gpioConfig.ledPin; - - doc[F("i")][F("l")][F("i")] = gpioConfig.ledInverted; - - if(gpioConfig.ledPinRed == 0xff) - doc[F("i")][F("r")][F("r")] = nullptr; - else - doc[F("i")][F("r")][F("r")] = gpioConfig.ledPinRed; + bool qsc = false; + bool qsr = false; + bool qsk = false; - if(gpioConfig.ledPinGreen == 0xff) - doc[F("i")][F("r")][F("g")] = nullptr; - else - doc[F("i")][F("r")][F("g")] = gpioConfig.ledPinGreen; + if(LittleFS.begin()) { + qsc = LittleFS.exists(FILE_MQTT_CA); + qsr = LittleFS.exists(FILE_MQTT_CERT); + qsk = LittleFS.exists(FILE_MQTT_KEY); + LittleFS.end(); + } - if(gpioConfig.ledPinBlue == 0xff) - doc[F("i")][F("r")][F("b")] = nullptr; - else - doc[F("i")][F("r")][F("b")] = gpioConfig.ledPinBlue; + server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); + server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE); + server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF); - doc[F("i")][F("r")][F("i")] = gpioConfig.ledRgbInverted; + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send_P(200, MIME_JSON, PSTR("{\"version\":\"")); + server.sendContent_P(VERSION); + server.sendContent_P(PSTR("\",")); + snprintf_P(buf, BufferSize, CONF_GENERAL_JSON, + ntpConfig.timezone, + wifiConfig.hostname, + webConfig.security, + webConfig.username, + strlen(webConfig.password) > 0 ? "***" : "" + ); + server.sendContent(buf); + snprintf_P(buf, BufferSize, CONF_METER_JSON, + meterConfig->baud, + meterConfig->parity, + meterConfig->invert ? "true" : "false", + meterConfig->distributionSystem, + meterConfig->mainFuse, + meterConfig->productionCapacity, + encen ? "true" : "false", + toHex(meterConfig->encryptionKey, 16).c_str(), + toHex(meterConfig->authenticationKey, 16).c_str(), + meterConfig->wattageMultiplier > 1 || meterConfig->voltageMultiplier > 1 || meterConfig->amperageMultiplier > 1 || meterConfig->accumulatedMultiplier > 1 ? "true" : "false", + meterConfig->wattageMultiplier / 1000.0, + meterConfig->voltageMultiplier / 1000.0, + meterConfig->amperageMultiplier / 1000.0, + meterConfig->accumulatedMultiplier / 1000.0 + ); + server.sendContent(buf); - if(gpioConfig.tempSensorPin == 0xff) - doc[F("i")][F("t")][F("d")] = nullptr; - else - doc[F("i")][F("t")][F("d")] = gpioConfig.tempSensorPin; - - if(gpioConfig.tempAnalogSensorPin == 0xff) - doc[F("i")][F("t")][F("a")] = nullptr; - else - doc[F("i")][F("t")][F("a")] = gpioConfig.tempAnalogSensorPin; - - if(gpioConfig.vccPin == 0xff) - doc[F("i")][F("v")][F("p")] = nullptr; - else - doc[F("i")][F("v")][F("p")] = gpioConfig.vccPin; - - if(gpioConfig.vccOffset == 0) - doc[F("i")][F("v")][F("o")] = nullptr; - else - doc[F("i")][F("v")][F("o")] = gpioConfig.vccOffset / 100.0; - - if(gpioConfig.vccMultiplier == 0) - doc[F("i")][F("v")][F("m")] = nullptr; - else - doc[F("i")][F("v")][F("m")] = gpioConfig.vccMultiplier / 1000.0; - - if(gpioConfig.vccResistorVcc == 0) - doc[F("i")][F("v")][F("d")][F("v")] = nullptr; - else - doc[F("i")][F("v")][F("d")][F("v")] = gpioConfig.vccResistorVcc; - - if(gpioConfig.vccResistorGnd == 0) - doc[F("i")][F("v")][F("d")][F("g")] = nullptr; - else - doc[F("i")][F("v")][F("d")][F("g")] = gpioConfig.vccResistorGnd; - - if(gpioConfig.vccBootLimit == 0) - doc[F("i")][F("v")][F("b")] = nullptr; - else - doc[F("i")][F("v")][F("b")] = gpioConfig.vccBootLimit / 10.0; - - serializeJson(doc, buf, BufferSize); - server.send(200, MIME_JSON, buf); + snprintf_P(buf, BufferSize, CONF_THRESHOLDS_JSON, + eac->thresholds[0], + eac->thresholds[1], + eac->thresholds[2], + eac->thresholds[3], + eac->thresholds[4], + eac->thresholds[5], + eac->thresholds[6], + eac->thresholds[7], + eac->thresholds[8], + eac->thresholds[9], + eac->hours + ); + server.sendContent(buf); + snprintf_P(buf, BufferSize, CONF_WIFI_JSON, + wifiConfig.ssid, + strlen(wifiConfig.psk) > 0 ? "***" : "", + wifiConfig.power / 10.0, + wifiConfig.sleep + ); + server.sendContent(buf); + snprintf_P(buf, BufferSize, CONF_NET_JSON, + strlen(wifiConfig.ip) > 0 ? "static" : "dhcp", + wifiConfig.ip, + wifiConfig.subnet, + wifiConfig.gateway, + wifiConfig.dns1, + wifiConfig.dns2, + wifiConfig.mdns ? "true" : "false", + ntpConfig.server, + ntpConfig.dhcp ? "true" : "false" + ); + server.sendContent(buf); + snprintf_P(buf, BufferSize, CONF_MQTT_JSON, + mqttConfig.host, + mqttConfig.port, + mqttConfig.username, + strlen(mqttConfig.password) > 0 ? "***" : "", + mqttConfig.clientId, + mqttConfig.publishTopic, + mqttConfig.payloadFormat, + mqttConfig.ssl ? "true" : "false", + qsc ? "true" : "false", + qsr ? "true" : "false", + qsk ? "true" : "false" + ); + server.sendContent(buf); + snprintf_P(buf, BufferSize, CONF_PRICE_JSON, + strlen(entsoe.token) > 0 ? "true" : "false", + entsoe.token, + entsoe.area, + entsoe.currency, + entsoe.multiplier / 1000.0 + ); + server.sendContent(buf); + snprintf_P(buf, BufferSize, CONF_DEBUG_JSON, + debugConfig.serial ? "true" : "false", + debugConfig.telnet ? "true" : "false", + debugConfig.level + ); + server.sendContent(buf); + snprintf_P(buf, BufferSize, CONF_GPIO_JSON, + gpioConfig->hanPin == 0xff ? "null" : String(gpioConfig->hanPin, 10).c_str(), + gpioConfig->apPin == 0xff ? "null" : String(gpioConfig->apPin, 10).c_str(), + gpioConfig->hanPin == 0xff ? "null" : String(gpioConfig->hanPin, 10).c_str(), + gpioConfig->ledInverted ? "true" : "false", + gpioConfig->ledPinRed == 0xff ? "null" : String(gpioConfig->ledPinRed, 10).c_str(), + gpioConfig->ledPinGreen == 0xff ? "null" : String(gpioConfig->ledPinGreen, 10).c_str(), + gpioConfig->ledPinBlue == 0xff ? "null" : String(gpioConfig->ledPinBlue, 10).c_str(), + gpioConfig->ledRgbInverted ? "true" : "false", + gpioConfig->tempSensorPin == 0xff ? "null" : String(gpioConfig->tempSensorPin, 10).c_str(), + gpioConfig->tempAnalogSensorPin == 0xff ? "null" : String(gpioConfig->tempAnalogSensorPin, 10).c_str(), + gpioConfig->vccPin == 0xff ? "null" : String(gpioConfig->vccPin, 10).c_str(), + gpioConfig->vccOffset / 100.0, + gpioConfig->vccMultiplier / 1000.0, + gpioConfig->vccResistorVcc, + gpioConfig->vccResistorGnd, + gpioConfig->vccBootLimit / 10.0 + ); + server.sendContent(buf); + server.sendContent("}"); } + void AmsWebServer::handleSave() { if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Handling save method from http")); if(!checkSecurity(1)) @@ -1300,23 +1282,10 @@ void AmsWebServer::handleSave() { } } -void AmsWebServer::wifiScanJson() { - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /wifiscan.json over http...\n"); - - DynamicJsonDocument doc(512); - - serializeJson(doc, buf, BufferSize); - server.send(200, MIME_JSON, buf); -} - void AmsWebServer::reboot() { if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /reboot over http...\n"); - DynamicJsonDocument doc(128); - doc[F("reboot")] = true; - - serializeJson(doc, buf, BufferSize); - server.send(200, MIME_JSON, buf); + server.send(200, MIME_JSON, "{\"reboot\":true}"); server.handleClient(); delay(250); @@ -1601,32 +1570,43 @@ void AmsWebServer::mqttKeyUpload() { void AmsWebServer::tariffJson() { if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /tariff.json over http...\n"); - server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); - server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE); - server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF); - if(!checkSecurity(1)) return; EnergyAccountingConfig* eac = ea->getConfig(); - EnergyAccountingData data = ea->getData(); - DynamicJsonDocument doc(512); - JsonArray thresholds = doc.createNestedArray(F("t")); - for(uint8_t x = 0;x < 10; x++) { - thresholds.add(eac->thresholds[x]); - } - JsonArray peaks = doc.createNestedArray(F("p")); + String peaks; for(uint8_t x = 0;x < min((uint8_t) 5, eac->hours); x++) { - JsonObject p = peaks.createNestedObject(); EnergyAccountingPeak peak = ea->getPeak(x+1); - p["d"] = peak.day; - p["v"] = peak.value / 100.0; + int len = snprintf_P(buf, BufferSize, PEAK_JSON, + peak.day, + peak.value / 100.0 + ); + buf[len] = '\0'; + if(!peaks.isEmpty()) peaks += ","; + peaks += String(buf); } - doc["c"] = ea->getCurrentThreshold(); - doc["m"] = ea->getMonthMax(); - serializeJson(doc, buf, BufferSize); + snprintf_P(buf, BufferSize, TARIFF_JSON, + eac->thresholds[0], + eac->thresholds[1], + eac->thresholds[2], + eac->thresholds[3], + eac->thresholds[4], + eac->thresholds[5], + eac->thresholds[6], + eac->thresholds[7], + eac->thresholds[8], + eac->thresholds[9], + peaks.c_str(), + ea->getCurrentThreshold(), + ea->getMonthMax() + ); + + server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); + server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE); + server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF); + + server.setContentLength(strlen(buf)); server.send(200, MIME_JSON, buf); - } \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index c11e0b3e..9f61cd4f 100755 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ extra_configs = platformio-user.ini [common] -lib_deps = EEPROM, LittleFS, DNSServer, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, EntsoePriceApi, EnergyAccounting, AmsMqttHandler, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, ArduinoJson, SvelteUi +lib_deps = EEPROM, LittleFS, DNSServer, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, EntsoePriceApi, EnergyAccounting, AmsMqttHandler, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, SvelteUi lib_ignore = OneWire extra_scripts = pre:scripts/addversion.py diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index d738c585..0a3e9480 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -826,6 +826,7 @@ bool readHanPort() { if(pos == DATA_PARSE_INCOMPLETE) { return false; } else if(pos == DATA_PARSE_UNKNOWN_DATA) { + meterState.setLastError(pos); debugV("Unknown data payload:"); len = len + hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len); debugPrint(hanBuffer, 0, len); @@ -837,6 +838,7 @@ bool readHanPort() { len = 0; return false; } else if(pos < 0) { + meterState.setLastError(pos); printHanReadError(pos); len += hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len); if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) { @@ -852,6 +854,7 @@ bool readHanPort() { for(int i = pos+ctx.length; i 0.0) { - ead.peaks[0] = { 1, (uint16_t) (val*100) }; - } - } else if(i == 3) { - double val = String(pch).toDouble(); - ead.costYesterday = val * 10; - } else if(i == 4) { - double val = String(pch).toDouble(); - ead.costThisMonth = val; - } else if(i == 5) { - double val = String(pch).toDouble(); - ead.costLastMonth = val; - } else if(i >= 6 && i < 18) { - uint8_t hour = i-6; - if(hour%2 == 0) { + if(ead.version < 5) { + if(i == 0) { long val = String(pch).toInt(); - ead.peaks[hour/2].day = val; - } else { + ead.version = val; + } else if(i == 1) { + long val = String(pch).toInt(); + ead.month = val; + } else if(i == 2) { double val = String(pch).toDouble(); - ead.peaks[hour/2].value = val * 100; + if(val > 0.0) { + ead.peaks[0] = { 1, (uint16_t) (val*100) }; + } + } else if(i == 3) { + double val = String(pch).toDouble(); + ead.costYesterday = val * 10; + } else if(i == 4) { + double val = String(pch).toDouble(); + ead.costThisMonth = val; + } else if(i == 5) { + double val = String(pch).toDouble(); + ead.costLastMonth = val; + } else if(i >= 6 && i < 18) { + uint8_t hour = i-6; + if(hour%2 == 0) { + long val = String(pch).toInt(); + ead.peaks[hour/2].day = val; + } else { + double val = String(pch).toDouble(); + ead.peaks[hour/2].value = val * 100; + } + } + } else { + if(i == 1) { + long val = String(pch).toInt(); + ead.month = val; + } else if(i == 2) { + double val = String(pch).toDouble(); + if(val > 0.0) { + ead.peaks[0] = { 1, (uint16_t) (val*100) }; + } + } else if(i == 3) { + double val = String(pch).toDouble(); + ead.costYesterday = val * 10; + } else if(i == 4) { + double val = String(pch).toDouble(); + ead.costThisMonth = val; + } else if(i == 5) { + double val = String(pch).toDouble(); + ead.costLastMonth = val; + } else if(i == 6) { + double val = String(pch).toDouble(); + ead.incomeYesterday= val * 10; + } else if(i == 7) { + double val = String(pch).toDouble(); + ead.incomeThisMonth = val; + } else if(i == 8) { + double val = String(pch).toDouble(); + ead.incomeLastMonth = val; + } else if(i >= 9 && i < 21) { + uint8_t hour = i-9; + if(hour%2 == 0) { + long val = String(pch).toInt(); + ead.peaks[hour/2].day = val; + } else { + double val = String(pch).toDouble(); + ead.peaks[hour/2].value = val * 100; + } } } pch = strtok (NULL, " ");