mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-13 05:51:09 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d902c63de | ||
|
|
a57405a7a5 | ||
|
|
550d32ee33 | ||
|
|
9f7e174c7b | ||
|
|
104617afd2 | ||
|
|
959664f61d | ||
|
|
5834b07393 | ||
|
|
8dbcf2424a | ||
|
|
74345046b1 | ||
|
|
516c80e38c | ||
|
|
17bbac8670 | ||
|
|
ccee3f505d | ||
|
|
48eb640838 | ||
|
|
90ebe3803d | ||
|
|
07ff4e2b0c | ||
|
|
7e011a184b | ||
|
|
324459df97 | ||
|
|
2db38835c5 | ||
|
|
804e43824b | ||
|
|
60c7cea724 | ||
|
|
b9fe6cab83 | ||
|
|
e3f41fdb4c | ||
|
|
9e5cb48101 | ||
|
|
259d8424a3 | ||
|
|
a6ae86abb8 | ||
|
|
a23abf626b | ||
|
|
849eac1c0f | ||
|
|
13dda2bb19 | ||
|
|
eea1782280 | ||
|
|
d729d0c5bd | ||
|
|
72cc0996e1 | ||
|
|
b1ad844bc6 | ||
|
|
4977ad3471 | ||
|
|
a3dcd2cfb2 | ||
|
|
c1701c8ee9 | ||
|
|
1e88148971 | ||
|
|
cef7ed15cb | ||
|
|
7c93537fef | ||
|
|
8b97544a2c |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,7 +7,7 @@
|
||||
.vscode
|
||||
.pio
|
||||
platformio-user.ini
|
||||
/lib/AmsConfiguration/include/version.h
|
||||
/lib/FirmwareVersion/src/generated_version.h
|
||||
/src/web/root
|
||||
/src/AmsToMqttBridge.ino.cpp
|
||||
/test
|
||||
|
||||
@@ -580,6 +580,7 @@ void AmsConfiguration::clearEntsoe(EntsoeConfig& config) {
|
||||
strcpy(config.currency, "");
|
||||
config.multiplier = 1000;
|
||||
config.enabled = false;
|
||||
config.fixedPrice = 0;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isEntsoeChanged() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <lwip/apps/sntp.h>
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "version.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
|
||||
day.version = 5;
|
||||
@@ -28,20 +28,20 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
if(now < BUILD_EPOCH) {
|
||||
if(data->getMeterTimestamp() > BUILD_EPOCH) {
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
if(data->getMeterTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
now = data->getMeterTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Using meter timestamp, which is: %lu\n"), (int32_t) now);
|
||||
}
|
||||
} else if(data->getPackageTimestamp() > BUILD_EPOCH) {
|
||||
} else if(data->getPackageTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
now = data->getPackageTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Using package timestamp, which is: %lu\n"), (int32_t) now);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(now < BUILD_EPOCH) {
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Invalid time: %lu\n"), (int32_t) now);
|
||||
}
|
||||
@@ -65,14 +65,38 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
return true;
|
||||
} else if(day.activeImport == 0 || now - day.lastMeterReadTime > 86400) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) %lu == 0 || %lu - %lu > 86400"), day.activeImport, now, day.lastMeterReadTime);
|
||||
}
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Too long since last day update, clearing data\n"));
|
||||
}
|
||||
for(int i = 0; i<24; i++) {
|
||||
setHourImport(i, 0);
|
||||
setHourExport(i, 0);
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Last day update: %lu\n"), (int32_t) day.lastMeterReadTime);
|
||||
}
|
||||
tmElements_t last;
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
for(int i = last.Hour; i < utc.Hour; i++) {
|
||||
uint8_t endHour = utc.Hour;
|
||||
if(last.Hour > utc.Hour){
|
||||
for(int i = 0; i < utc.Hour; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing hour: %d\n"), i);
|
||||
}
|
||||
setHourImport(i, 0);
|
||||
setHourExport(i, 0);
|
||||
}
|
||||
endHour = 24;
|
||||
}
|
||||
for(int i = last.Hour; i < endHour; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing hour: %d\n"), i);
|
||||
}
|
||||
@@ -89,13 +113,38 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
month.activeImport = importCounter;
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
} else if(month.activeImport == 0 || now - month.lastMeterReadTime > 2682000) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) %lu == 0 || %lu - %lu > 2682000"), month.activeImport, now, month.lastMeterReadTime);
|
||||
}
|
||||
month.activeImport = importCounter;
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Too long since last month update, clearing data\n"));
|
||||
}
|
||||
for(int i = 1; i<=31; i++) {
|
||||
setDayImport(i, 0);
|
||||
setDayExport(i, 0);
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Last month update: %lu\n"), (int32_t) month.lastMeterReadTime);
|
||||
}
|
||||
tmElements_t last;
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
for(int i = last.Day; i < ltz.Day; i++) {
|
||||
uint8_t endDay = ltz.Day;
|
||||
if(last.Day > ltz.Day) {
|
||||
for(int i = 1; i < ltz.Day; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing day: %d\n"), i);
|
||||
}
|
||||
setDayImport(i, 0);
|
||||
setDayExport(i, 0);
|
||||
}
|
||||
endDay = 31;
|
||||
}
|
||||
for(int i = last.Day; i < endDay; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing day: %d\n"), i);
|
||||
}
|
||||
@@ -119,17 +168,6 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
day.lastMeterReadTime = now;
|
||||
setHourImport(utcYesterday.Hour, 0);
|
||||
setHourExport(utcYesterday.Hour, 0);
|
||||
} else if(day.activeImport == 0 || now - day.lastMeterReadTime > 86400) {
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Too long since last day update, clearing data\n"));
|
||||
}
|
||||
for(int i = 0; i<24; i++) {
|
||||
setHourImport(i, 0);
|
||||
setHourExport(i, 0);
|
||||
}
|
||||
} else if(now - day.lastMeterReadTime < 4000) {
|
||||
uint32_t imp = importCounter - day.activeImport;
|
||||
uint32_t exp = exportCounter - day.activeExport;
|
||||
@@ -186,17 +224,6 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
month.lastMeterReadTime = now;
|
||||
setDayImport(ltzYesterDay.Day, 0);
|
||||
setDayExport(ltzYesterDay.Day, 0);
|
||||
} else if(month.activeImport == 0 || now - month.lastMeterReadTime > 2678400) {
|
||||
month.activeImport = importCounter;
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Too long since last month update, clearing data\n"));
|
||||
}
|
||||
for(int i = 1; i<=31; i++) {
|
||||
setDayImport(i, 0);
|
||||
setDayExport(i, 0);
|
||||
}
|
||||
} else if(now - month.lastMeterReadTime < 90100 && now - month.lastMeterReadTime > 82700) { // DST days are 23h (82800s) and 25h (90000)
|
||||
int32_t imp = importCounter - month.activeImport;
|
||||
int32_t exp = exportCounter - month.activeExport;
|
||||
@@ -550,21 +577,27 @@ bool AmsDataStorage::isHappy() {
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isDayHappy() {
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return false;
|
||||
tmElements_t tm, last;
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
if(now < day.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data timestamp %lu < %lu\n"), (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(day.lastMeterReadTime), last);
|
||||
if(now-day.lastMeterReadTime > 3600) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data timestamp age %lu - %lu > 3600\n"), (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
if(tm.Hour > last.Hour) {
|
||||
|
||||
tmElements_t tm, last;
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(day.lastMeterReadTime), last);
|
||||
if(tm.Hour != last.Hour) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data hour of last timestamp %d > %d\n"), tm.Hour, last.Hour);
|
||||
return false;
|
||||
}
|
||||
@@ -579,7 +612,7 @@ bool AmsDataStorage::isMonthHappy() {
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return false;
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < month.lastMeterReadTime) {
|
||||
@@ -589,10 +622,15 @@ bool AmsDataStorage::isMonthHappy() {
|
||||
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
if(tm.Day > last.Day) {
|
||||
if(tm.Day != last.Day) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Month data day of last timestamp %d > %d\n"), tm.Day, last.Day);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(now-month.lastMeterReadTime > 90100) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lu - %lu > 3600\n", (int32_t) now, (int32_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,21 @@ struct EnergyAccountingPeak {
|
||||
};
|
||||
|
||||
struct EnergyAccountingData {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint32_t costYesterday;
|
||||
uint32_t costThisMonth;
|
||||
uint32_t costLastMonth;
|
||||
uint32_t incomeYesterday;
|
||||
uint32_t incomeThisMonth;
|
||||
uint32_t incomeLastMonth;
|
||||
uint32_t lastMonthImport;
|
||||
uint32_t lastMonthExport;
|
||||
uint8_t lastMonthAccuracy;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData5 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
@@ -32,7 +47,7 @@ struct EnergyAccountingData4 {
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData1 {
|
||||
struct EnergyAccountingData2 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t maxHour;
|
||||
@@ -57,22 +72,24 @@ public:
|
||||
float getUseThisHour();
|
||||
float getUseToday();
|
||||
float getUseThisMonth();
|
||||
float getUseLastMonth();
|
||||
|
||||
float getProducedThisHour();
|
||||
float getProducedToday();
|
||||
float getProducedThisMonth();
|
||||
float getProducedLastMonth();
|
||||
|
||||
float getCostThisHour();
|
||||
float getCostToday();
|
||||
float getCostYesterday();
|
||||
float getCostThisMonth();
|
||||
uint16_t getCostLastMonth();
|
||||
float getCostLastMonth();
|
||||
|
||||
float getIncomeThisHour();
|
||||
float getIncomeToday();
|
||||
float getIncomeYesterday();
|
||||
float getIncomeThisMonth();
|
||||
uint16_t getIncomeLastMonth();
|
||||
float getIncomeLastMonth();
|
||||
|
||||
float getMonthMax();
|
||||
uint8_t getCurrentThreshold();
|
||||
@@ -81,7 +98,7 @@ public:
|
||||
EnergyAccountingData getData();
|
||||
void setData(EnergyAccountingData&);
|
||||
|
||||
void setFixedPrice(float price);
|
||||
void setFixedPrice(float price, String currency);
|
||||
float getPriceForHour(uint8_t h);
|
||||
|
||||
private:
|
||||
@@ -95,8 +112,9 @@ private:
|
||||
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
|
||||
float use = 0, costHour = 0, costDay = 0;
|
||||
float produce = 0, incomeHour = 0, incomeDay = 0;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
float fixedPrice = 0;
|
||||
String currency = "";
|
||||
|
||||
void calcDayCost();
|
||||
bool updateMax(uint16_t val, uint8_t day);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "EnergyAccounting.h"
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "version.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger) {
|
||||
data.version = 1;
|
||||
@@ -33,7 +33,7 @@ bool EnergyAccounting::isInitialized() {
|
||||
bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(config == NULL) return false;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return false;
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Timezone is missing\n"));
|
||||
return false;
|
||||
@@ -49,9 +49,10 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Initializing data at %lu\n"), (int32_t) now);
|
||||
if(!load()) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Unable to load existing data\n"));
|
||||
data = { 5, local.Month,
|
||||
data = { 6, local.Month,
|
||||
0, 0, 0, // Cost
|
||||
0, 0, 0, // Income
|
||||
0, 0, 0, // Last month import, export and accuracy
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
@@ -62,8 +63,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Peak hour from day %d: %d\n"), data.peaks[i].day, data.peaks[i].value*10);
|
||||
}
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n"), data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth);
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded income yesterday: %.2f, this month: %d, last month: %d\n"), data.incomeYesterday / 10.0, data.incomeThisMonth, data.incomeLastMonth);
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n"), data.costYesterday / 100.0, data.costThisMonth / 100.0, data.costLastMonth / 100.0);
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded income yesterday: %.2f, this month: %d, last month: %d\n"), data.incomeYesterday / 100.0, data.incomeThisMonth / 100.0, data.incomeLastMonth / 100.0);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
@@ -94,14 +95,15 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
costHour = 0;
|
||||
incomeHour = 0;
|
||||
|
||||
uint8_t prevDay = currentDay;
|
||||
if(local.Day != currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New day %d\n"), local.Day);
|
||||
data.costYesterday = costDay * 10;
|
||||
data.costThisMonth += costDay;
|
||||
data.costYesterday = costDay * 100;
|
||||
data.costThisMonth += costDay * 100;
|
||||
costDay = 0;
|
||||
|
||||
data.incomeYesterday = incomeDay * 10;
|
||||
data.incomeThisMonth += incomeDay;
|
||||
data.incomeYesterday = incomeDay * 100;
|
||||
data.incomeThisMonth += incomeDay * 100;
|
||||
incomeDay = 0;
|
||||
|
||||
currentDay = local.Day;
|
||||
@@ -117,6 +119,23 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
data.peaks[i] = { 0, 0 };
|
||||
}
|
||||
|
||||
uint64_t totalImport = 0, totalExport = 0;
|
||||
for(uint8_t i = 1; i <= prevDay; i++) {
|
||||
totalImport += ds->getDayImport(i);
|
||||
totalExport += ds->getDayExport(i);
|
||||
}
|
||||
uint8_t accuracy = 0;
|
||||
uint64_t importUpdate = totalImport, exportUpdate = totalExport;
|
||||
while(importUpdate > UINT32_MAX || exportUpdate > UINT32_MAX) {
|
||||
accuracy++;
|
||||
importUpdate = totalImport / pow(10, accuracy);
|
||||
exportUpdate = totalExport / pow(10, accuracy);
|
||||
}
|
||||
data.lastMonthImport = importUpdate;
|
||||
data.lastMonthExport = exportUpdate;
|
||||
data.lastMonthAccuracy = accuracy;
|
||||
|
||||
data.month = local.Month;
|
||||
currentThresholdIdx = 0;
|
||||
ret = true;
|
||||
@@ -132,7 +151,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
use += kwhi;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float cost = price * kwhi;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), cost / 100.0, eapi->getCurrency());
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), cost / 100.0, currency.c_str());
|
||||
costHour += cost;
|
||||
costDay += cost;
|
||||
}
|
||||
@@ -142,7 +161,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
produce += kwhe;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float income = price * kwhe;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), income / 100.0, eapi->getCurrency());
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), income / 100.0, currency.c_str());
|
||||
incomeHour += income;
|
||||
incomeDay += income;
|
||||
}
|
||||
@@ -160,6 +179,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
void EnergyAccounting::calcDayCost() {
|
||||
time_t now = time(nullptr);
|
||||
tmElements_t local, utc;
|
||||
if(tz == NULL) return;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(getPriceForHour(0) != ENTSOE_NO_VALUE) {
|
||||
@@ -186,10 +206,10 @@ float EnergyAccounting::getUseThisHour() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseToday() {
|
||||
if(tz == NULL) return 0.0;
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0.0;
|
||||
if(tz == NULL) return 0.0;
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < currentHour; i++) {
|
||||
@@ -201,22 +221,27 @@ float EnergyAccounting::getUseToday() {
|
||||
|
||||
float EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0.0;
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 0; i < currentDay; i++) {
|
||||
for(uint8_t i = 1; i < currentDay; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseLastMonth() {
|
||||
return (data.lastMonthImport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedThisHour() {
|
||||
return produce;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedToday() {
|
||||
if(tz == NULL) return 0.0;
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0.0;
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < currentHour; i++) {
|
||||
@@ -228,14 +253,17 @@ float EnergyAccounting::getProducedToday() {
|
||||
|
||||
float EnergyAccounting::getProducedThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0.0;
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 0; i < currentDay; i++) {
|
||||
for(uint8_t i = 1; i < currentDay; i++) {
|
||||
ret += ds->getDayExport(i) / 1000.0;
|
||||
}
|
||||
return ret + getProducedToday();
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedLastMonth() {
|
||||
return (data.lastMonthExport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostThisHour() {
|
||||
return costHour;
|
||||
@@ -246,15 +274,15 @@ float EnergyAccounting::getCostToday() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostYesterday() {
|
||||
return data.costYesterday / 10.0;
|
||||
return data.costYesterday / 100.0;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostThisMonth() {
|
||||
return data.costThisMonth + getCostToday();
|
||||
return (data.costThisMonth / 100.0) + getCostToday();
|
||||
}
|
||||
|
||||
uint16_t EnergyAccounting::getCostLastMonth() {
|
||||
return data.costLastMonth;
|
||||
float EnergyAccounting::getCostLastMonth() {
|
||||
return data.costLastMonth / 100.0;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeThisHour() {
|
||||
@@ -266,15 +294,15 @@ float EnergyAccounting::getIncomeToday() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeYesterday() {
|
||||
return data.incomeYesterday / 10.0;
|
||||
return data.incomeYesterday / 100.0;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeThisMonth() {
|
||||
return data.incomeThisMonth + getIncomeToday();
|
||||
return (data.incomeThisMonth / 100.0) + getIncomeToday();
|
||||
}
|
||||
|
||||
uint16_t EnergyAccounting::getIncomeLastMonth() {
|
||||
return data.incomeLastMonth;
|
||||
float EnergyAccounting::getIncomeLastMonth() {
|
||||
return data.incomeLastMonth / 100.0;
|
||||
}
|
||||
|
||||
uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
@@ -364,17 +392,35 @@ bool EnergyAccounting::load() {
|
||||
file.readBytes(buf, file.size());
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Data version %d\n"), buf[0]);
|
||||
if(buf[0] == 5) {
|
||||
if(buf[0] == 6) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
ret = true;
|
||||
} else if(buf[0] == 5) {
|
||||
EnergyAccountingData5* data = (EnergyAccountingData5*) buf;
|
||||
this->data = { 6, data->month,
|
||||
((uint32_t) data->costYesterday) * 10,
|
||||
((uint32_t) data->costThisMonth) * 100,
|
||||
((uint32_t) data->costLastMonth) * 100,
|
||||
((uint32_t) data->incomeYesterday) * 10,
|
||||
((uint32_t) data->incomeThisMonth) * 100,
|
||||
((uint32_t) data->incomeLastMonth) * 100,
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
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] == 4) {
|
||||
EnergyAccountingData4* data = (EnergyAccountingData4*) buf;
|
||||
this->data = { 5, data->month,
|
||||
data->costYesterday,
|
||||
data->costThisMonth,
|
||||
data->costLastMonth,
|
||||
((uint32_t) data->costYesterday) * 10,
|
||||
((uint32_t) data->costThisMonth) * 100,
|
||||
((uint32_t) data->costLastMonth) * 100,
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
@@ -385,8 +431,11 @@ bool EnergyAccounting::load() {
|
||||
} else if(buf[0] == 3) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
this->data = { 5, data->month,
|
||||
(uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100),
|
||||
data->costYesterday * 10,
|
||||
data->costThisMonth,
|
||||
data->costLastMonth,
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
@@ -398,6 +447,7 @@ bool EnergyAccounting::load() {
|
||||
data = { 5, 0,
|
||||
0, 0, 0, // Cost
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
@@ -405,11 +455,11 @@ bool EnergyAccounting::load() {
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
if(buf[0] == 2) {
|
||||
EnergyAccountingData1* data = (EnergyAccountingData1*) buf;
|
||||
EnergyAccountingData2* data = (EnergyAccountingData2*) buf;
|
||||
this->data.month = data->month;
|
||||
this->data.costYesterday = (uint16_t) (data->costYesterday / 10);
|
||||
this->data.costThisMonth = (uint16_t) (data->costThisMonth / 100);
|
||||
this->data.costLastMonth = (uint16_t) (data->costLastMonth / 100);
|
||||
this->data.costYesterday = data->costYesterday * 10;
|
||||
this->data.costThisMonth = data->costThisMonth;
|
||||
this->data.costLastMonth = data->costLastMonth;
|
||||
uint8_t b = 0;
|
||||
for(uint8_t i = sizeof(this->data); i < file.size(); i+=2) {
|
||||
this->data.peaks[b].day = b;
|
||||
@@ -418,15 +468,6 @@ bool EnergyAccounting::load() {
|
||||
if(b >= config->hours || b >= 5) break;
|
||||
}
|
||||
ret = true;
|
||||
} else if(buf[0] == 1) {
|
||||
EnergyAccountingData1* data = (EnergyAccountingData1*) buf;
|
||||
this->data.month = data->month;
|
||||
this->data.costYesterday = (uint16_t) (data->costYesterday / 10);
|
||||
this->data.costThisMonth = (uint16_t) (data->costThisMonth / 100);
|
||||
this->data.costLastMonth = (uint16_t) (data->costLastMonth / 100);
|
||||
this->data.peaks[0].day = 1;
|
||||
this->data.peaks[0].value = data->maxHour;
|
||||
ret = true;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(EnergyAccounting) Unknown version\n"));
|
||||
ret = false;
|
||||
@@ -499,8 +540,9 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setFixedPrice(float price) {
|
||||
void EnergyAccounting::setFixedPrice(float price, String currency) {
|
||||
this->fixedPrice = price;
|
||||
this->currency = currency;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getPriceForHour(uint8_t h) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "Uptime.h"
|
||||
#include "TimeLib.h"
|
||||
#include "DnbCurrParser.h"
|
||||
#include "version.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#include "GcmParser.h"
|
||||
|
||||
@@ -37,7 +37,7 @@ void EntsoeApi::setup(EntsoeConfig& config) {
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http.setReuse(false);
|
||||
http.setTimeout(60000);
|
||||
http.setUserAgent("ams2mqtt/" + String(VERSION));
|
||||
http.setUserAgent("ams2mqtt/" + String(FirmwareVersion::VersionString));
|
||||
http.useHTTP10(true);
|
||||
|
||||
#if defined(AMS2MQTT_PRICE_KEY)
|
||||
@@ -135,7 +135,7 @@ bool EntsoeApi::loop() {
|
||||
if(now < 10000) return false; // Grace period
|
||||
|
||||
time_t t = time(nullptr);
|
||||
if(t < BUILD_EPOCH) return false;
|
||||
if(t < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
#ifndef AMS2MQTT_PRICE_KEY
|
||||
if(strlen(getToken()) == 0) {
|
||||
@@ -272,14 +272,14 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to, time_t
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1"), from);
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), from);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EntsoeApi) Retrieving %s to NOK conversion\n"), from);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) url: %s\n"), buf);
|
||||
if(retrieve(buf, &p)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) got exchange rate %.4f\n"), p.getValue());
|
||||
currencyMultiplier = p.getValue();
|
||||
if(strncmp(to, "NOK", 3) != 0) {
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1"), to);
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), to);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EntsoeApi) Retrieving %s to NOK conversion\n"), to);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) url: %s\n"), buf);
|
||||
if(retrieve(buf, &p)) {
|
||||
|
||||
12
lib/FirmwareVersion/include/FirmwareVersion.h
Normal file
12
lib/FirmwareVersion/include/FirmwareVersion.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef _FIRMWARE_VERSION_h
|
||||
#define _FIRMWARE_VERSION_h
|
||||
|
||||
|
||||
class FirmwareVersion {
|
||||
public:
|
||||
static long BuildEpoch;
|
||||
static const char* VersionString;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
5
lib/FirmwareVersion/src/FirmwareVersion.cpp
Normal file
5
lib/FirmwareVersion/src/FirmwareVersion.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "FirmwareVersion.h"
|
||||
#include "generated_version.h"
|
||||
|
||||
long FirmwareVersion::BuildEpoch = BUILD_EPOCH;
|
||||
const char* FirmwareVersion::VersionString = VERSION_STRING;
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
typedef struct HomeAssistantSensor {
|
||||
struct HomeAssistantSensor {
|
||||
const char* name;
|
||||
const char* topic;
|
||||
const char* path;
|
||||
@@ -15,75 +15,75 @@ typedef struct HomeAssistantSensor {
|
||||
|
||||
const uint8_t List1SensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
|
||||
{"Active import", "/power", "P", "W", "power", "\"measurement\""}
|
||||
{"Active import", "/power", "P", "W", "power", "measurement"}
|
||||
};
|
||||
|
||||
const uint8_t List2SensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor List2Sensors[List2SensorCount] PROGMEM = {
|
||||
{"Reactive import", "/power", "Q", "var", "reactive_power", "\"measurement\""},
|
||||
{"Reactive export", "/power", "QO", "var", "reactive_power", "\"measurement\""},
|
||||
{"L1 current", "/power", "I1", "A", "current", "\"measurement\""},
|
||||
{"L2 current", "/power", "I2", "A", "current", "\"measurement\""},
|
||||
{"L3 current", "/power", "I3", "A", "current", "\"measurement\""},
|
||||
{"L1 voltage", "/power", "U1", "V", "voltage", "\"measurement\""},
|
||||
{"L2 voltage", "/power", "U2", "V", "voltage", "\"measurement\""},
|
||||
{"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""}
|
||||
{"Reactive import", "/power", "Q", "var", "reactive_power", "measurement"},
|
||||
{"Reactive export", "/power", "QO", "var", "reactive_power", "measurement"},
|
||||
{"L1 current", "/power", "I1", "A", "current", "measurement"},
|
||||
{"L2 current", "/power", "I2", "A", "current", "measurement"},
|
||||
{"L3 current", "/power", "I3", "A", "current", "measurement"},
|
||||
{"L1 voltage", "/power", "U1", "V", "voltage", "measurement"},
|
||||
{"L2 voltage", "/power", "U2", "V", "voltage", "measurement"},
|
||||
{"L3 voltage", "/power", "U3", "V", "voltage", "measurement"}
|
||||
};
|
||||
|
||||
const uint8_t List2ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = {
|
||||
{"Active export", "/power", "PO", "W", "power", "\"measurement\""}
|
||||
{"Active export", "/power", "PO", "W", "power", "measurement"}
|
||||
};
|
||||
|
||||
const uint8_t List3SensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor List3Sensors[List3SensorCount] PROGMEM = {
|
||||
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Accumulated reactive import","/energy", "tQI", "", "energy", "\"total_increasing\""},
|
||||
{"Accumulated reactive export","/energy", "tQO", "", "energy", "\"total_increasing\""}
|
||||
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "total_increasing"},
|
||||
{"Accumulated reactive import","/energy", "tQI", "kvarh","", "total_increasing"},
|
||||
{"Accumulated reactive export","/energy", "tQO", "kvarh","", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t List3ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = {
|
||||
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "\"total_increasing\""}
|
||||
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t List4SensorCount PROGMEM = 7;
|
||||
const HomeAssistantSensor List4Sensors[List4SensorCount] PROGMEM = {
|
||||
{"Power factor", "/power", "PF", "%", "power_factor", "\"measurement\""},
|
||||
{"L1 power factor", "/power", "PF1", "%", "power_factor", "\"measurement\""},
|
||||
{"L2 power factor", "/power", "PF2", "%", "power_factor", "\"measurement\""},
|
||||
{"L3 power factor", "/power", "PF3", "%", "power_factor", "\"measurement\""},
|
||||
{"L1 active import", "/power", "P1", "W", "power", "\"measurement\""},
|
||||
{"L2 active import", "/power", "P2", "W", "power", "\"measurement\""},
|
||||
{"L3 active import", "/power", "P3", "W", "power", "\"measurement\""}
|
||||
{"Power factor", "/power", "PF", "%", "power_factor", "measurement"},
|
||||
{"L1 power factor", "/power", "PF1", "%", "power_factor", "measurement"},
|
||||
{"L2 power factor", "/power", "PF2", "%", "power_factor", "measurement"},
|
||||
{"L3 power factor", "/power", "PF3", "%", "power_factor", "measurement"},
|
||||
{"L1 active import", "/power", "P1", "W", "power", "measurement"},
|
||||
{"L2 active import", "/power", "P2", "W", "power", "measurement"},
|
||||
{"L3 active import", "/power", "P3", "W", "power", "measurement"}
|
||||
};
|
||||
|
||||
const uint8_t List4ExportSensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
|
||||
{"L1 active export", "/power", "PO1", "W", "power", "\"measurement\""},
|
||||
{"L2 active export", "/power", "PO2", "W", "power", "\"measurement\""},
|
||||
{"L3 active export", "/power", "PO3", "W", "power", "\"measurement\""}
|
||||
{"L1 active export", "/power", "PO1", "W", "power", "measurement"},
|
||||
{"L2 active export", "/power", "PO2", "W", "power", "measurement"},
|
||||
{"L3 active export", "/power", "PO3", "W", "power", "measurement"}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeSensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = {
|
||||
{"Month max", "/realtime","max", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Month max", "/realtime","max", "kWh", "energy", "total_increasing"},
|
||||
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "total_increasing"},
|
||||
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "total_increasing"},
|
||||
{"Current hour cost", "/realtime","hour.cost", "", "monetary", ""},
|
||||
{"Current day used", "/realtime","day.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day used", "/realtime","day.use", "kWh", "energy", "total_increasing"},
|
||||
{"Current day cost", "/realtime","day.cost", "", "monetary", ""},
|
||||
{"Current month used", "/realtime","month.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month used", "/realtime","month.use", "kWh", "energy", "total_increasing"},
|
||||
{"Current month cost", "/realtime","month.cost", "", "monetary", ""}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeExportSensorCount PROGMEM = 6;
|
||||
const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGMEM = {
|
||||
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "total_increasing"},
|
||||
{"Current hour income", "/realtime","hour.income", "", "monetary", ""},
|
||||
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "total_increasing"},
|
||||
{"Current day income", "/realtime","day.income", "", "monetary", ""},
|
||||
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "total_increasing"},
|
||||
{"Current month income", "/realtime","month.income", "", "monetary", ""}
|
||||
};
|
||||
|
||||
@@ -102,11 +102,11 @@ const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices",
|
||||
|
||||
const uint8_t SystemSensorCount PROGMEM = 2;
|
||||
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
|
||||
{"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""},
|
||||
{"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""}
|
||||
{"Status", "/state", "rssi", "dBm", "signal_strength", "measurement"},
|
||||
{"Supply volt", "/state", "vcc", "V", "voltage", "measurement"}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", "°C", "temperature", "\"measurement\""};
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", "°C", "temperature", "measurement"};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"obj_id" : "%s_%s",
|
||||
"unit_of_meas" : "%s",
|
||||
"val_tpl" : "{{ value_json.%s | is_defined }}",
|
||||
"dev_cla" : "%s",
|
||||
"dev" : {
|
||||
"ids" : [ "%s" ],
|
||||
"name" : "%s",
|
||||
@@ -13,5 +12,5 @@
|
||||
"sw" : "%s",
|
||||
"mf" : "%s",
|
||||
"cu" : "%s"
|
||||
}%s %s
|
||||
}%s%s%s%s%s%s
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "HomeAssistantMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "version.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "json/ha1_json.h"
|
||||
#include "json/ha2_json.h"
|
||||
#include "json/ha3_json.h"
|
||||
@@ -329,7 +329,7 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, Energ
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
VERSION
|
||||
FirmwareVersion::VersionString
|
||||
);
|
||||
bool ret = mqtt->publish(topic + "/state", json);
|
||||
loop();
|
||||
@@ -350,15 +350,18 @@ void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor& sensor)
|
||||
deviceUid.c_str(), uid.c_str(),
|
||||
sensor.uom,
|
||||
sensor.path,
|
||||
sensor.devcl,
|
||||
deviceUid.c_str(),
|
||||
deviceName.c_str(),
|
||||
deviceModel.c_str(),
|
||||
VERSION,
|
||||
FirmwareVersion::VersionString,
|
||||
manufacturer.c_str(),
|
||||
deviceUrl.c_str(),
|
||||
strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : ""
|
||||
strlen_P(sensor.devcl) > 0 ? ",\"dev_cla\":\"" : "",
|
||||
strlen_P(sensor.devcl) > 0 ? (char *) FPSTR(sensor.devcl) : "",
|
||||
strlen_P(sensor.devcl) > 0 ? "\"" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? ",\"stat_cla\":\"" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : "",
|
||||
strlen_P(sensor.stacl) > 0 ? "\"" : ""
|
||||
);
|
||||
mqtt->publish(discoveryTopic + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
loop();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "JsonMqttHandler.h"
|
||||
#include "version.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "json/json1_json.h"
|
||||
@@ -341,7 +341,7 @@ bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounti
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
VERSION
|
||||
FirmwareVersion::VersionString
|
||||
);
|
||||
bool ret = mqtt->publish(topic, json);
|
||||
loop();
|
||||
|
||||
2
lib/SvelteUi/app/dist/index.css
vendored
2
lib/SvelteUi/app/dist/index.css
vendored
File diff suppressed because one or more lines are too long
22
lib/SvelteUi/app/dist/index.js
vendored
22
lib/SvelteUi/app/dist/index.js
vendored
File diff suppressed because one or more lines are too long
12
lib/SvelteUi/app/package-lock.json
generated
12
lib/SvelteUi/app/package-lock.json
generated
@@ -2714,9 +2714,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz",
|
||||
"integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==",
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
|
||||
"integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
@@ -4411,9 +4411,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz",
|
||||
"integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==",
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
|
||||
"integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
}
|
||||
|
||||
.cnt {
|
||||
@apply bg-white m-2 p-2 rounded shadow-lg
|
||||
@apply bg-white m-2 p-2 rounded shadow-lg;
|
||||
min-height: 268px;
|
||||
}
|
||||
|
||||
.gwf {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { fmtnum } from "./Helpers";
|
||||
|
||||
|
||||
export let sysinfo;
|
||||
export let data;
|
||||
export let currency;
|
||||
export let hasExport;
|
||||
@@ -9,7 +9,7 @@
|
||||
let hasCost = false;
|
||||
let cols = 3
|
||||
$: {
|
||||
hasCost = data && data.h && (data.h.c || data.d.c || data.m.c || data.h.i || data.d.i || data.m.i);
|
||||
hasCost = data && data.h && (data.h.c > 0.01 || data.d.c > 0.01 || data.m.c > 0.01 || data.h.i > 0.01 || data.d.i > 0.01 || data.m.i > 0.01);
|
||||
cols = hasCost ? 3 : 2;
|
||||
}
|
||||
</script>
|
||||
@@ -31,6 +31,9 @@
|
||||
<div>Month</div>
|
||||
<div class="text-right">{fmtnum(data.m.u)} kWh</div>
|
||||
{#if hasCost}<div class="text-right">{fmtnum(data.m.c)} {currency}</div>{/if}
|
||||
<div>Last mo.</div>
|
||||
<div class="text-right">{fmtnum(sysinfo.last_month.u)} kWh</div>
|
||||
{#if hasCost}<div class="text-right">{fmtnum(sysinfo.last_month.c)} {currency}</div>{/if}
|
||||
</div>
|
||||
<strong>Export</strong>
|
||||
<div class="grid grid-cols-{cols}">
|
||||
@@ -43,6 +46,9 @@
|
||||
<div>Month</div>
|
||||
<div class="text-right">{fmtnum(data.m.p)} kWh</div>
|
||||
{#if hasCost}<div class="text-right">{fmtnum(data.m.i)} {currency}</div>{/if}
|
||||
<div>Last mo.</div>
|
||||
<div class="text-right">{fmtnum(sysinfo.last_month.p)} kWh</div>
|
||||
{#if hasCost}<div class="text-right">{fmtnum(sysinfo.last_month.i)} {currency}</div>{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<strong>Consumption</strong>
|
||||
@@ -53,6 +59,8 @@
|
||||
<div class="text-right">{fmtnum(data.d.u,1)} kWh</div>
|
||||
<div>Month</div>
|
||||
<div class="text-right">{fmtnum(data.m.u)} kWh</div>
|
||||
<div>Last month</div>
|
||||
<div class="text-right">{fmtnum(sysinfo.last_month.u)} kWh</div>
|
||||
</div>
|
||||
{#if hasCost}
|
||||
<strong>Cost</strong>
|
||||
@@ -63,6 +71,8 @@
|
||||
<div class="text-right">{fmtnum(data.d.c,1)} {currency}</div>
|
||||
<div>Month</div>
|
||||
<div class="text-right">{fmtnum(data.m.c)} {currency}</div>
|
||||
<div>Last month</div>
|
||||
<div class="text-right">{fmtnum(sysinfo.last_month.c)} {currency}</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
function point(v) {
|
||||
return {
|
||||
label: fmtnum(v) +'A',
|
||||
label: fmtnum(v) + 'A',
|
||||
title: v.toFixed(1) + ' A',
|
||||
value: isNaN(v) ? 0 : v,
|
||||
color: ampcol(v ? (v)/(max)*100 : 0)
|
||||
};
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
let yScale;
|
||||
let heightAvailable;
|
||||
let labelOffset;
|
||||
let labelOffset2;
|
||||
|
||||
$: {
|
||||
heightAvailable = height-(config.title ? 20 : 0);
|
||||
let innerWidth = width - (config.padding.left + config.padding.right);
|
||||
barWidth = innerWidth / config.points.length;
|
||||
labelOffset = barWidth < 25 ? 28 : 17;
|
||||
labelOffset2 = barWidth < 25 ? 10 : 100;
|
||||
|
||||
let yPerUnit = (heightAvailable-config.padding.top-config.padding.bottom)/(config.y.max-config.y.min);
|
||||
|
||||
@@ -32,77 +34,96 @@
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="chart" bind:clientWidth={width} bind:clientHeight={height}>
|
||||
{#if config.title}
|
||||
<strong class="text-sm">{config.title}</strong>
|
||||
{#if config.x.ticks && config.points && heightAvailable}
|
||||
{#if config.title}
|
||||
<strong class="text-sm">{config.title}</strong>
|
||||
{/if}
|
||||
<svg height="{heightAvailable}">
|
||||
<!-- y axis -->
|
||||
<g class="axis y-axis">
|
||||
{#each config.y.ticks as tick}
|
||||
{#if !isNaN(yScale(tick.value))}
|
||||
<g class="tick tick-{tick.value} tick-{tick.color}" transform="translate(0, {yScale(tick.value)})">
|
||||
<line x2="100%"></line>
|
||||
<text y="-4" x={tick.align == 'right' ? '85%' : ''}>{tick.label}</text>
|
||||
</g>
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<!-- x axis -->
|
||||
<g class="axis x-axis">
|
||||
{#each config.x.ticks as point, i}
|
||||
{#if !isNaN(xScale(i))}
|
||||
<g class="tick" transform="translate({xScale(i)},{heightAvailable})">
|
||||
{#if barWidth > 20 || i%2 == 0}
|
||||
<text x="{barWidth/2}" y="-4">{point.label}</text>
|
||||
{/if}
|
||||
</g>
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<g class='bars'>
|
||||
{#each config.points as point, i}
|
||||
{#if !isNaN(xScale(i)) && !isNaN(yScale(point.value))}
|
||||
<g>
|
||||
{#if point.value !== undefined}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(point.value)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(Math.min(config.y.min, 0) + point.value)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(point.value) > yScale(0)-labelOffset ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+9})"
|
||||
|
||||
>{point.label}</text>
|
||||
{#if point.title}
|
||||
<title>{point.title}</title>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</g>
|
||||
<g>
|
||||
{#if point.value2 > 0.0001}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(0)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(config.y.min + point.value2)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(-point.value2) < yScale(0) + labelOffset2 ? yScale(0) - labelOffset+2 : yScale(-point.value2)-labelOffset}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{'middle'}"
|
||||
fill="{yScale(-point.value2) < yScale(0) + labelOffset2 ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(-point.value2) < yScale(0) + labelOffset2 ? yScale(0) - labelOffset+2 : yScale(-point.value2)-labelOffset})"
|
||||
|
||||
>{point.label2}</text>
|
||||
{#if point.title2}
|
||||
<title>{point.title2}</title>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</g>
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
</svg>
|
||||
{/if}
|
||||
<svg height="{heightAvailable}">
|
||||
<!-- y axis -->
|
||||
<g class="axis y-axis">
|
||||
{#each config.y.ticks as tick}
|
||||
<g class="tick tick-{tick.value} tick-{tick.color}" transform="translate(0, {yScale(tick.value)})">
|
||||
<line x2="100%"></line>
|
||||
<text y="-4" x={tick.align == 'right' ? '85%' : ''}>{tick.label}</text>
|
||||
</g>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<!-- x axis -->
|
||||
<g class="axis x-axis">
|
||||
{#each config.x.ticks as point, i}
|
||||
<g class="tick" transform="translate({xScale(i)},{heightAvailable})">
|
||||
{#if barWidth > 20 || i%2 == 0}
|
||||
<text x="{barWidth/2}" y="-4">{point.label}</text>
|
||||
{/if}
|
||||
</g>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<g class='bars'>
|
||||
{#each config.points as point, i}
|
||||
{#if point.value !== undefined}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(point.value)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(Math.min(config.y.min, 0) + point.value)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(point.value) > yScale(0)-labelOffset ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+9})"
|
||||
>{point.label}</text>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if point.value2 > 0.0001}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(0)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(config.y.min + point.value2)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(-point.value2) < yScale(0)+12 ? yScale(-point.value2) + 12 : yScale(-point.value2)-10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(-point.value2) < yScale(0)+12 ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value2 - config.y.min) > yScale(0)-12 ? yScale(point.value2 - config.y.min) - 12 : yScale(point.value2 - config.y.min)+9})"
|
||||
>{point.label2}</text>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
<script>
|
||||
import { zeropad, monthnames } from './Helpers.js';
|
||||
import { zeropad, monthnames, addHours } from './Helpers.js';
|
||||
|
||||
export let timestamp;
|
||||
export let fullTimeColor;
|
||||
export let offset;
|
||||
|
||||
let showFull;
|
||||
$:{
|
||||
showFull = Math.abs(new Date().getTime()-timestamp.getTime()) < 300000;
|
||||
if(!isNaN(offset))
|
||||
addHours(timestamp, offset);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if Math.abs(new Date().getTime()-timestamp.getTime()) < 300000 }
|
||||
{`${zeropad(timestamp.getDate())}. ${monthnames[timestamp.getMonth()]} ${zeropad(timestamp.getHours())}:${zeropad(timestamp.getMinutes())}`}
|
||||
{#if showFull }
|
||||
{`${zeropad(timestamp.getDate())}. ${monthnames[timestamp.getMonth()]} ${zeropad(timestamp.getUTCHours())}:${zeropad(timestamp.getMinutes())}`}
|
||||
{:else}
|
||||
<span class="{fullTimeColor}">{`${zeropad(timestamp.getDate())}.${zeropad(timestamp.getMonth()+1)}.${timestamp.getFullYear()} ${zeropad(timestamp.getHours())}:${zeropad(timestamp.getMinutes())}`}</span>
|
||||
<span class="{fullTimeColor}">{`${zeropad(timestamp.getDate())}.${zeropad(timestamp.getMonth()+1)}.${timestamp.getFullYear()} ${zeropad(timestamp.getUTCHours())}:${zeropad(timestamp.getMinutes())}`}</span>
|
||||
{/if}
|
||||
|
||||
@@ -353,19 +353,19 @@
|
||||
<div class="flex my-1">
|
||||
<div class="w-1/4">
|
||||
Watt<br/>
|
||||
<input name="mmw" bind:value={configuration.m.m.w} type="number" min="0.00" max="655.35" step="0.01" class="in-f tr w-full"/>
|
||||
<input name="mmw" bind:value={configuration.m.m.w} type="number" min="0.00" max="1000" step="0.001" class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
Volt<br/>
|
||||
<input name="mmv" bind:value={configuration.m.m.v} type="number" min="0.00" max="655.35" step="0.01" class="in-m tr w-full"/>
|
||||
<input name="mmv" bind:value={configuration.m.m.v} type="number" min="0.00" max="1000" step="0.001" class="in-m tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
Amp<br/>
|
||||
<input name="mma" bind:value={configuration.m.m.a} type="number" min="0.00" max="655.35" step="0.01" class="in-m tr w-full"/>
|
||||
<input name="mma" bind:value={configuration.m.m.a} type="number" min="0.00" max="1000" step="0.001" class="in-m tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
kWh<br/>
|
||||
<input name="mmc" bind:value={configuration.m.m.c} type="number" min="0.00" max="655.35" step="0.01" class="in-l tr w-full"/>
|
||||
<input name="mmc" bind:value={configuration.m.m.c} type="number" min="0.00" max="1000" step="0.001" class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.c, data.ea)}
|
||||
<div class="cnt">
|
||||
<AccountingData data={data.ea} currency={data.pc} hasExport={data.om > 0 || data.e > 0}/>
|
||||
<AccountingData sysinfo={sysinfo} data={data.ea} currency={data.pc} hasExport={data.om > 0 || data.e > 0}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.t, data.pr && (data.pr.startsWith("10YNO") || data.pr == '10Y1001A1001A48H'))}
|
||||
@@ -82,17 +82,17 @@
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.p, data.pe && !Number.isNaN(data.p))}
|
||||
<div class="cnt gwf">
|
||||
<PricePlot json={prices}/>
|
||||
<PricePlot json={prices} sysinfo={sysinfo}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.d, dayPlot)}
|
||||
<div class="cnt gwf">
|
||||
<DayPlot json={dayPlot} />
|
||||
<DayPlot json={dayPlot} sysinfo={sysinfo}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.m, monthPlot)}
|
||||
<div class="cnt gwf">
|
||||
<MonthPlot json={monthPlot} />
|
||||
<MonthPlot json={monthPlot} sysinfo={sysinfo}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.s, data.t && data.t != -127 && temperatures.c > 1)}
|
||||
|
||||
@@ -59,7 +59,7 @@ export const dataStore = readable(data, (set) => {
|
||||
}
|
||||
if(lastPrice != data.p && data.pe) {
|
||||
lastPrice = data.p;
|
||||
setTimeout(getPrices, 4000);
|
||||
setTimeout(getPrices, 1000);
|
||||
}
|
||||
if(sysinfo.upgrading) {
|
||||
window.location.reload();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
export let sysinfo;
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
@@ -15,6 +16,7 @@
|
||||
let points = [];
|
||||
let cur = addHours(new Date(), -24);
|
||||
let currentHour = new Date().getUTCHours();
|
||||
addHours(cur, sysinfo.clock_offset);
|
||||
for(i = currentHour; i<24; i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
@@ -22,12 +24,14 @@
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad(cur.getHours())
|
||||
label: zeropad(cur.getUTCHours())
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(1),
|
||||
title: imp.toFixed(2) + ' kWh',
|
||||
value: imp*10,
|
||||
label2: exp.toFixed(1),
|
||||
title2: exp.toFixed(2) + ' kWh',
|
||||
value2: exp*10,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
@@ -42,12 +46,14 @@
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad(cur.getHours())
|
||||
label: zeropad(cur.getUTCHours())
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(1),
|
||||
title: imp.toFixed(2) + ' kWh',
|
||||
value: imp*10,
|
||||
label2: exp.toFixed(1),
|
||||
title2: exp.toFixed(2) + ' kWh',
|
||||
value2: exp*10,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<a class="float-right" href='https://github.com/UtilitechAS/amsreader-firmware' target='_blank' rel="noreferrer" aria-label="GitHub"><img class="gh-logo" src={GitHubLogo} alt="GitHub repo"/></a>
|
||||
</div>
|
||||
<div class="flex-none my-auto px-2">
|
||||
<Clock timestamp={ data.c ? new Date(data.c * 1000) : new Date(0) } fullTimeColor="text-red-500" />
|
||||
<Clock timestamp={ data.c ? new Date(data.c * 1000) : new Date(0) } offset={sysinfo.clock_offset} fullTimeColor="text-red-500" />
|
||||
</div>
|
||||
{#if sysinfo.vndcfg && sysinfo.usrcfg}
|
||||
<div class="flex-none px-1 mt-1" title="Configuration">
|
||||
|
||||
@@ -116,7 +116,7 @@ export function hanError(err) {
|
||||
case -51: return "Authentication failed";
|
||||
case -52: return "Decryption failed";
|
||||
case -53: return "Encryption key invalid";
|
||||
case 90: return "No HAN data received last 30s";
|
||||
case 90: return "No HAN data received for at least 30s";
|
||||
case 91: return "Serial break";
|
||||
case 92: return "Serial buffer full";
|
||||
case 93: return "Serial FIFO overflow";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script>
|
||||
import { zeropad } from './Helpers.js';
|
||||
import { zeropad, addHours } from './Helpers.js';
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
export let sysinfo;
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
@@ -15,6 +16,8 @@
|
||||
let points = [];
|
||||
let cur = new Date();
|
||||
let lm = new Date();
|
||||
addHours(cur, sysinfo.clock_offset);
|
||||
addHours(lm, sysinfo.clock_offset);
|
||||
lm.setDate(0);
|
||||
|
||||
for(i = cur.getDate(); i<=lm.getDate(); i++) {
|
||||
@@ -27,9 +30,11 @@
|
||||
label: zeropad(i)
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(0),
|
||||
label: imp.toFixed(imp < 10 ? 1 : 0),
|
||||
title: imp.toFixed(2) + ' kWh',
|
||||
value: imp,
|
||||
label2: exp.toFixed(0),
|
||||
label2: exp.toFixed(exp < 10 ? 1 : 0),
|
||||
title2: exp.toFixed(2) + ' kWh',
|
||||
value2: exp,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
@@ -46,9 +51,11 @@
|
||||
label: zeropad(i)
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(0),
|
||||
label: imp.toFixed(imp < 10 ? 1 : 0),
|
||||
title: imp.toFixed(2) + ' kWh',
|
||||
value: imp,
|
||||
label2: exp.toFixed(0),
|
||||
label2: exp.toFixed(exp < 10 ? 1 : 0),
|
||||
title2: exp.toFixed(2) + ' kWh',
|
||||
value2: exp,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
export let sysinfo;
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
@@ -18,16 +19,19 @@
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
let cur = new Date();
|
||||
addHours(cur, sysinfo.clock_offset);
|
||||
for(i = hour; i<24; i++) {
|
||||
val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
xTicks.push({
|
||||
label: zeropad(cur.getHours())
|
||||
label: zeropad(cur.getUTCHours())
|
||||
});
|
||||
points.push({
|
||||
label: val > 0 ? val.toFixed(d) : '',
|
||||
value: val > 0 ? Math.abs(val*100) : 0,
|
||||
label: val >= 0 ? val.toFixed(d) : '',
|
||||
title: val >= 0 ? val.toFixed(2) + ' ' + json.currency : '',
|
||||
value: val >= 0 ? Math.abs(val*100) : 0,
|
||||
label2: val < 0 ? val.toFixed(d) : '',
|
||||
title2: val < 0 ? val.toFixed(2) + ' ' + json.currency : '',
|
||||
value2: val < 0 ? Math.abs(val*100) : 0,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
@@ -39,11 +43,11 @@
|
||||
val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
xTicks.push({
|
||||
label: zeropad(cur.getHours())
|
||||
label: zeropad(cur.getUTCHours())
|
||||
});
|
||||
points.push({
|
||||
label: val > 0 ? val.toFixed(d) : '',
|
||||
value: val > 0 ? Math.abs(val*100) : 0,
|
||||
label: val >= 0 ? val.toFixed(d) : '',
|
||||
value: val >= 0 ? Math.abs(val*100) : 0,
|
||||
label2: val < 0 ? val.toFixed(d) : '',
|
||||
value2: val < 0 ? Math.abs(val*100) : 0,
|
||||
color: '#7c3aed'
|
||||
@@ -53,12 +57,13 @@
|
||||
addHours(cur, 1);
|
||||
};
|
||||
|
||||
max = Math.ceil(max);
|
||||
min = Math.floor(min);
|
||||
let range = Math.max(max, Math.abs(min));
|
||||
|
||||
if(min < 0) {
|
||||
let yTickDistDown = min/4;
|
||||
for(i = 1; i < 5; i++) {
|
||||
min = Math.min((range/4)*-1, min);
|
||||
let yTicksNum = Math.ceil((Math.abs(min)/range) * 4);
|
||||
let yTickDistDown = min/yTicksNum;
|
||||
for(i = 1; i < yTicksNum+1; i++) {
|
||||
let val = (yTickDistDown*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
@@ -67,8 +72,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
let yTickDistUp = max/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
max = Math.max((range/4), max);
|
||||
let xTicksNum = Math.ceil((max/range) * 4);
|
||||
let yTickDistUp = max/xTicksNum;
|
||||
for(i = 0; i < xTicksNum+1; i++) {
|
||||
let val = (yTickDistUp*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
label: name.slice(-4)
|
||||
});
|
||||
points.push({
|
||||
label: val.toFixed(1),
|
||||
label: val.toFixed(1),
|
||||
value: val,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
function point(v) {
|
||||
return {
|
||||
label: fmtnum(v) + 'V',
|
||||
title: v.toFixed(1) + ' V',
|
||||
value: isNaN(v) ? 0 : v,
|
||||
color: voltcol(v ? v : 0)
|
||||
};
|
||||
|
||||
@@ -17,18 +17,18 @@ export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
server: {
|
||||
proxy: {
|
||||
"/data.json": "http://192.168.233.235",
|
||||
"/energyprice.json": "http://192.168.233.235",
|
||||
"/dayplot.json": "http://192.168.233.235",
|
||||
"/monthplot.json": "http://192.168.233.235",
|
||||
"/temperature.json": "http://192.168.233.235",
|
||||
"/sysinfo.json": "http://192.168.233.235",
|
||||
"/configuration.json": "http://192.168.233.235",
|
||||
"/tariff.json": "http://192.168.233.235",
|
||||
"/save": "http://192.168.233.235",
|
||||
"/reboot": "http://192.168.233.235",
|
||||
"/configfile": "http://192.168.233.235",
|
||||
"/upgrade": "http://192.168.233.235"
|
||||
"/data.json": "http://192.168.28.100",
|
||||
"/energyprice.json": "http://192.168.28.100",
|
||||
"/dayplot.json": "http://192.168.28.100",
|
||||
"/monthplot.json": "http://192.168.28.100",
|
||||
"/temperature.json": "http://192.168.28.100",
|
||||
"/sysinfo.json": "http://192.168.28.100",
|
||||
"/configuration.json": "http://192.168.28.100",
|
||||
"/tariff.json": "http://192.168.28.100",
|
||||
"/save": "http://192.168.28.100",
|
||||
"/reboot": "http://192.168.28.100",
|
||||
"/configfile": "http://192.168.28.100",
|
||||
"/upgrade": "http://192.168.28.100"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -44,5 +44,12 @@
|
||||
"e": %d,
|
||||
"f": "%s",
|
||||
"t": "%s"
|
||||
}
|
||||
},
|
||||
"last_month": {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"clock_offset": %d
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "AmsWebServer.h"
|
||||
#include "AmsWebHeaders.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "base64.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
@@ -31,8 +32,6 @@
|
||||
#include "html/conf_ui_json.h"
|
||||
#include "html/firmware_html.h"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_wifi.h>
|
||||
@@ -64,9 +63,9 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
this->ea = ea;
|
||||
|
||||
server.on(F("/"), HTTP_GET, std::bind(&AmsWebServer::indexHtml, this));
|
||||
snprintf_P(buf, 32, PSTR("/index-%s.js"), VERSION);
|
||||
snprintf_P(buf, 32, PSTR("/index-%s.js"), FirmwareVersion::VersionString);
|
||||
server.on(buf, HTTP_GET, std::bind(&AmsWebServer::indexJs, this));
|
||||
snprintf_P(buf, 32, PSTR("/index-%s.css"), VERSION);
|
||||
snprintf_P(buf, 32, PSTR("/index-%s.css"), FirmwareVersion::VersionString);
|
||||
server.on(buf, HTTP_GET, std::bind(&AmsWebServer::indexCss, this));
|
||||
|
||||
server.on(F("/configuration"), HTTP_GET, std::bind(&AmsWebServer::indexHtml, this));
|
||||
@@ -262,8 +261,10 @@ void AmsWebServer::sysinfoJson() {
|
||||
if(!meterId.isEmpty())
|
||||
meterId.replace(F("\\"), F("\\\\"));
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
int size = snprintf_P(buf, BufferSize, SYSINFO_JSON,
|
||||
VERSION,
|
||||
FirmwareVersion::VersionString,
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
"esp32s2",
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
@@ -323,7 +324,12 @@ void AmsWebServer::sysinfoJson() {
|
||||
upinfo.exitCode,
|
||||
upinfo.errorCode,
|
||||
upinfo.fromVersion,
|
||||
upinfo.toVersion
|
||||
upinfo.toVersion,
|
||||
ea->getUseLastMonth(),
|
||||
ea->getCostLastMonth(),
|
||||
ea->getProducedLastMonth(),
|
||||
ea->getIncomeLastMonth(),
|
||||
tz == NULL ? 0 : (tz->toLocal(now)-now)/3600
|
||||
);
|
||||
|
||||
stripNonAscii((uint8_t*) buf, size+1);
|
||||
@@ -770,7 +776,6 @@ void AmsWebServer::indexJs() {
|
||||
return;
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1MO);
|
||||
server.setContentLength(INDEX_JS_LEN);
|
||||
server.send_P(200, MIME_JS, INDEX_JS);
|
||||
}
|
||||
|
||||
@@ -824,7 +829,7 @@ void AmsWebServer::configurationJson() {
|
||||
|
||||
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
server.send_P(200, MIME_JSON, PSTR("{\"version\":\""));
|
||||
server.sendContent_P(VERSION);
|
||||
server.sendContent_P(FirmwareVersion::VersionString);
|
||||
server.sendContent_P(PSTR("\","));
|
||||
snprintf_P(buf, BufferSize, CONF_GENERAL_JSON,
|
||||
ntpConfig.timezone,
|
||||
@@ -1536,7 +1541,7 @@ void AmsWebServer::upgrade() {
|
||||
}
|
||||
|
||||
void AmsWebServer::upgradeFromUrl(String url, String nextVersion) {
|
||||
config->setUpgradeInformation(0xFF, 0xFF, VERSION, nextVersion.c_str());
|
||||
config->setUpgradeInformation(0xFF, 0xFF, FirmwareVersion::VersionString, nextVersion.c_str());
|
||||
|
||||
WiFiClient client;
|
||||
#if defined(ESP8266)
|
||||
@@ -1555,10 +1560,10 @@ void AmsWebServer::upgradeFromUrl(String url, String nextVersion) {
|
||||
|
||||
#if defined(ESP8266)
|
||||
ESP8266HTTPUpdate httpUpdate = ESP8266HTTPUpdate(60000);
|
||||
String currentVersion = VERSION;
|
||||
String currentVersion = FirmwareVersion::VersionString;
|
||||
#elif defined(ESP32)
|
||||
HTTPUpdate httpUpdate = HTTPUpdate(60000);
|
||||
String currentVersion = String(VERSION) + "-" + chipType;
|
||||
String currentVersion = String(FirmwareVersion::VersionString) + "-" + chipType;
|
||||
#endif
|
||||
|
||||
httpUpdate.rebootOnUpdate(false);
|
||||
@@ -1566,7 +1571,7 @@ void AmsWebServer::upgradeFromUrl(String url, String nextVersion) {
|
||||
HTTPUpdateResult ret = httpUpdate.update(client, url, currentVersion);
|
||||
int lastError = httpUpdate.getLastError();
|
||||
|
||||
config->setUpgradeInformation(ret, ret == HTTP_UPDATE_OK ? 0 : lastError, VERSION, nextVersion.c_str());
|
||||
config->setUpgradeInformation(ret, ret == HTTP_UPDATE_OK ? 0 : lastError, FirmwareVersion::VersionString, nextVersion.c_str());
|
||||
switch(ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
debugger->printf_P(PSTR("Update failed\n"));
|
||||
@@ -1604,7 +1609,7 @@ void AmsWebServer::firmwarePost() {
|
||||
if(rebootForUpgrade) {
|
||||
server.send(200);
|
||||
} else {
|
||||
config->setUpgradeInformation(0xFF, 0xFF, VERSION, "");
|
||||
config->setUpgradeInformation(0xFF, 0xFF, FirmwareVersion::VersionString, "");
|
||||
if(server.hasArg(F("url"))) {
|
||||
String url = server.arg(F("url"));
|
||||
if(!url.isEmpty() && (url.startsWith(F("http://")) || url.startsWith(F("https://")))) {
|
||||
@@ -1891,7 +1896,7 @@ void AmsWebServer::configFileDownload() {
|
||||
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
|
||||
server.send_P(200, MIME_PLAIN, PSTR("amsconfig\n"));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("version %s\n"), VERSION));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("version %s\n"), FirmwareVersion::VersionString));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("boardType %d\n"), sys.boardType));
|
||||
|
||||
if(includeWifi) {
|
||||
@@ -2019,6 +2024,7 @@ void AmsWebServer::configFileDownload() {
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("entsoeArea %s\n"), entsoe.area));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("entsoeCurrency %s\n"), entsoe.currency));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("entsoeMultiplier %.3f\n"), entsoe.multiplier / 1000.0));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("entsoeFixedPrice %.3f\n"), entsoe.fixedPrice / 1000.0));
|
||||
}
|
||||
|
||||
if(includeThresholds) {
|
||||
@@ -2187,15 +2193,15 @@ void AmsWebServer::configFileDownload() {
|
||||
EnergyAccountingConfig eac;
|
||||
config->getEnergyAccountingConfig(eac);
|
||||
EnergyAccountingData ead = ea->getData();
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("energyaccounting %d %d %.2f %d %d %.2f %d %d %d %.2f %d %.2f %d %.2f %d %.2f %d %.2f"),
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("energyaccounting %d %d %.2f %.2f %.2f %.2f %.2f %.2f %d %.2f %d %.2f %d %.2f %d %.2f %d %.2f %.2f %.2f"),
|
||||
ead.version,
|
||||
ead.month,
|
||||
ead.costYesterday / 10.0,
|
||||
ead.costThisMonth,
|
||||
ead.costLastMonth,
|
||||
ead.incomeYesterday / 10.0,
|
||||
ead.incomeThisMonth,
|
||||
ead.incomeLastMonth,
|
||||
ea->getCostYesterday(),
|
||||
ea->getCostThisMonth(),
|
||||
ea->getCostLastMonth(),
|
||||
ea->getIncomeYesterday(),
|
||||
ea->getIncomeThisMonth(),
|
||||
ea->getIncomeLastMonth(),
|
||||
ead.peaks[0].day,
|
||||
ead.peaks[0].value / 100.0,
|
||||
ead.peaks[1].day,
|
||||
@@ -2205,7 +2211,9 @@ void AmsWebServer::configFileDownload() {
|
||||
ead.peaks[3].day,
|
||||
ead.peaks[3].value / 100.0,
|
||||
ead.peaks[4].day,
|
||||
ead.peaks[4].value / 100.0
|
||||
ead.peaks[4].value / 100.0,
|
||||
ea->getUseLastMonth(),
|
||||
ea->getProducedLastMonth()
|
||||
));
|
||||
server.sendContent_P(PSTR("\n"));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
extra_configs = platformio-user.ini
|
||||
|
||||
[common]
|
||||
lib_deps = EEPROM, LittleFS, DNSServer, https://github.com/256dpi/arduino-mqtt.git, 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, AmsDecoder, EntsoePriceApi, EnergyAccounting, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, SvelteUi
|
||||
lib_deps = EEPROM, LittleFS, DNSServer, https://github.com/256dpi/arduino-mqtt.git, 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, FirmwareVersion, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, EntsoePriceApi, EnergyAccounting, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, SvelteUi
|
||||
lib_ignore = OneWire
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
import subprocess
|
||||
from time import time
|
||||
|
||||
FILENAME_VERSION_H = 'lib/AmsConfiguration/include/version.h'
|
||||
FILENAME_VERSION_H = 'lib/FirmwareVersion/src/generated_version.h'
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
@@ -15,9 +15,7 @@ if version == None:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
hf = """
|
||||
#ifndef VERSION
|
||||
#define VERSION "{}"
|
||||
#endif
|
||||
#define VERSION_STRING "{}"
|
||||
#define BUILD_EPOCH {}
|
||||
""".format(version, round(time()))
|
||||
with open(FILENAME_VERSION_H, 'w+') as f:
|
||||
|
||||
@@ -29,7 +29,7 @@ fi
|
||||
|
||||
if [ "$1" = "flash" ];then
|
||||
$esptool --chip esp32 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect \
|
||||
0x1000 bootloader_dio_40m.bin \
|
||||
0x1000 bootloader.bin \
|
||||
0x8000 partitions.bin \
|
||||
0xe000 boot_app0.bin \
|
||||
0x10000 firmware.bin
|
||||
|
||||
@@ -8,7 +8,6 @@ if [ ! -d $build_dir ];then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/$env/bin/bootloader_dio_40m.bin $build_dir
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin $build_dir
|
||||
chmod +x scripts/$env/flash.sh
|
||||
zip -j $env.zip $build_dir/*.bin scripts/$env/flash.sh
|
||||
|
||||
@@ -29,7 +29,7 @@ fi
|
||||
|
||||
if [ "$1" = "flash" ];then
|
||||
$esptool --chip esp32c3 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect \
|
||||
0x1000 bootloader_qio_80m.bin \
|
||||
0x0000 bootloader.bin \
|
||||
0x8000 partitions.bin \
|
||||
0xe000 boot_app0.bin \
|
||||
0x10000 firmware.bin
|
||||
|
||||
@@ -8,7 +8,6 @@ if [ ! -d $build_dir ];then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/$env/bin/bootloader_qio_80m.bin $build_dir
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin $build_dir
|
||||
chmod +x scripts/$env/flash.sh
|
||||
zip -j $env.zip $build_dir/*.bin scripts/$env/flash.sh
|
||||
|
||||
@@ -29,7 +29,7 @@ fi
|
||||
|
||||
if [ "$1" = "flash" ];then
|
||||
$esptool --chip esp32s2 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect \
|
||||
0x1000 bootloader_qio_40m.bin \
|
||||
0x1000 bootloader.bin \
|
||||
0x8000 partitions.bin \
|
||||
0xe000 boot_app0.bin \
|
||||
0x10000 firmware.bin
|
||||
|
||||
@@ -8,7 +8,6 @@ if [ ! -d $build_dir ];then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/$env/bin/bootloader_qio_40m.bin $build_dir
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin $build_dir
|
||||
chmod +x scripts/$env/flash.sh
|
||||
zip -j $env.zip $build_dir/*.bin scripts/$env/flash.sh
|
||||
|
||||
@@ -29,7 +29,7 @@ fi
|
||||
|
||||
if [ "$1" = "flash" ];then
|
||||
$esptool --chip esp32 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect \
|
||||
0x1000 bootloader_dio_40m.bin \
|
||||
0x1000 bootloader.bin \
|
||||
0x8000 partitions.bin \
|
||||
0xe000 boot_app0.bin \
|
||||
0x10000 firmware.bin
|
||||
|
||||
@@ -8,7 +8,6 @@ if [ ! -d $build_dir ];then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/$env/bin/bootloader_dio_40m.bin $build_dir
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin $build_dir
|
||||
chmod +x scripts/$env/flash.sh
|
||||
zip -j $env.zip $build_dir/*.bin scripts/$env/flash.sh
|
||||
|
||||
@@ -23,12 +23,15 @@
|
||||
* to Norwegian meters, but may also support data from electricity providers in other countries.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
ADC_MODE(ADC_VCC);
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#include <lwip/dns.h>
|
||||
#endif
|
||||
#define WDT_TIMEOUT 60
|
||||
|
||||
@@ -36,8 +39,7 @@ ADC_MODE(ADC_VCC);
|
||||
#include <driver/uart.h>
|
||||
#endif
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include "FirmwareVersion.h"
|
||||
#include "AmsToMqttBridge.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "AmsDataStorage.h"
|
||||
@@ -133,6 +135,54 @@ LLCParser *llcParser = NULL;
|
||||
DLMSParser *dlmsParser = NULL;
|
||||
DSMRParser *dsmrParser = NULL;
|
||||
|
||||
|
||||
void configFileParse();
|
||||
void swapWifiMode();
|
||||
void WiFi_connect();
|
||||
void WiFi_post_connect();
|
||||
void MQTT_connect();
|
||||
void handleNtpChange();
|
||||
void handleDataSuccess(AmsData* data);
|
||||
void handleTemperature(unsigned long now);
|
||||
void handleSystem(unsigned long now);
|
||||
void handleAutodetect(unsigned long now);
|
||||
void handleButton(unsigned long now);
|
||||
void handlePriceApi(unsigned long now);
|
||||
void handleClear(unsigned long now);
|
||||
void handleEnergyAccountingChanged();
|
||||
bool readHanPort();
|
||||
void setupHanPort(GpioConfig& gpioConfig, uint32_t baud, uint8_t parityOrdinal, bool invert);
|
||||
void rxerr(int err);
|
||||
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
|
||||
void errorBlink();
|
||||
void printHanReadError(int pos);
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
|
||||
|
||||
#if defined(ESP32)
|
||||
uint8_t dnsState = 0;
|
||||
ip_addr_t dns0;
|
||||
void WiFiEvent(WiFiEvent_t event) {
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
const ip_addr_t* dns = dns_getserver(0);
|
||||
memcpy(&dns0, dns, sizeof(dns0));
|
||||
|
||||
IPAddress res;
|
||||
int ret = WiFi.hostByName("hub.amsleser.no", res);
|
||||
if(ret == 0) {
|
||||
dnsState = 2;
|
||||
debugI_P(PSTR("No DNS, probably a closed network"));
|
||||
} else {
|
||||
debugI_P(PSTR("DNS is present and working, monitoring"));
|
||||
dnsState = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
@@ -186,7 +236,7 @@ void setup() {
|
||||
ws.setEntsoeApi(eapi);
|
||||
}
|
||||
ws.setPriceSettings(entsoe.area, entsoe.currency);
|
||||
ea.setFixedPrice(entsoe.fixedPrice / 1000.0);
|
||||
ea.setFixedPrice(entsoe.fixedPrice / 1000.0, entsoe.currency);
|
||||
bool shared = false;
|
||||
config.getMeterConfig(meterConfig);
|
||||
Serial.flush();
|
||||
@@ -309,7 +359,7 @@ void setup() {
|
||||
}
|
||||
flashed = Update.end(true);
|
||||
}
|
||||
config.setUpgradeInformation(flashed ? 2 : 0, 0xFF, VERSION, "");
|
||||
config.setUpgradeInformation(flashed ? 2 : 0, 0xFF, FirmwareVersion::VersionString, "");
|
||||
firmwareFile.close();
|
||||
} else {
|
||||
debugW_P(PSTR("AP button pressed, skipping firmware update and deleting firmware file."));
|
||||
@@ -337,15 +387,7 @@ void setup() {
|
||||
if(config.hasConfig()) {
|
||||
if(Debug.isActive(RemoteDebug::INFO)) config.print(&Debug);
|
||||
WiFi_connect();
|
||||
|
||||
NtpConfig ntp;
|
||||
if(config.getNtpConfig(ntp)) {
|
||||
tz = resolveTimezone(ntp.timezone);
|
||||
ws.setTimezone(tz);
|
||||
ds.setTimezone(tz);
|
||||
ea.setTimezone(tz);
|
||||
}
|
||||
|
||||
handleNtpChange();
|
||||
ds.load();
|
||||
} else {
|
||||
if(Debug.isActive(RemoteDebug::INFO)) {
|
||||
@@ -520,6 +562,9 @@ void loop() {
|
||||
debugW_P(PSTR("Used %dms to read HAN port (false)"), millis()-start);
|
||||
}
|
||||
}
|
||||
if(millis() - meterState.getLastUpdateMillis() > 1800000 && !ds.isHappy()) {
|
||||
handleClear(now);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
debugE_P(PSTR("Exception in readHanPort (%s)"), e.what());
|
||||
meterState.setLastError(METER_ERROR_EXCEPTION);
|
||||
@@ -550,6 +595,16 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void handleClear(unsigned long now) {
|
||||
tmElements_t tm;
|
||||
breakTime(time(nullptr), tm);
|
||||
if(tm.Minute == 0) {
|
||||
AmsData nullData;
|
||||
debugI_P(PSTR("Clearing data that have not been updated"));
|
||||
ds.update(&nullData);
|
||||
}
|
||||
}
|
||||
|
||||
void handleEnergyAccountingChanged() {
|
||||
EnergyAccountingConfig *eac = ea.getConfig();
|
||||
config.getEnergyAccountingConfig(*eac);
|
||||
@@ -594,6 +649,16 @@ void handleSystem(unsigned long now) {
|
||||
if(end - start > 1000) {
|
||||
debugW_P(PSTR("Used %dms to send system update to MQTT"), millis()-start);
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
if(dnsState == 1) {
|
||||
const ip_addr_t* dns = dns_getserver(0);
|
||||
if(memcmp(&dns0, dns, sizeof(dns0)) != 0) {
|
||||
dns_setserver(0, &dns0);
|
||||
debugI_P(PSTR("Had to reset DNS server"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,7 +720,7 @@ void handlePriceApi(unsigned long now) {
|
||||
}
|
||||
ws.setPriceSettings(entsoe.area, entsoe.currency);
|
||||
config.ackEntsoeChange();
|
||||
ea.setFixedPrice(entsoe.fixedPrice / 1000.0);
|
||||
ea.setFixedPrice(entsoe.fixedPrice / 1000.0, entsoe.currency);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,6 +995,7 @@ void swapWifiMode() {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(10);
|
||||
yield();
|
||||
|
||||
if (mode != WIFI_AP || !config.hasConfig()) {
|
||||
@@ -1126,15 +1192,15 @@ void handleDataSuccess(AmsData* data) {
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH && data->getListType() >= 3) {
|
||||
if(data->getMeterTimestamp() > BUILD_EPOCH) {
|
||||
if(now < FirmwareVersion::BuildEpoch && data->getListType() >= 3) {
|
||||
if(data->getMeterTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
debugI_P(PSTR("Using timestamp from meter"));
|
||||
now = data->getMeterTimestamp();
|
||||
} else if(data->getPackageTimestamp() > BUILD_EPOCH) {
|
||||
} else if(data->getPackageTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
debugI_P(PSTR("Using timestamp from meter (DLMS)"));
|
||||
now = data->getPackageTimestamp();
|
||||
}
|
||||
if(now > BUILD_EPOCH) {
|
||||
if(now > FirmwareVersion::BuildEpoch) {
|
||||
timeval tv { now, 0};
|
||||
settimeofday(&tv, nullptr);
|
||||
}
|
||||
@@ -1143,14 +1209,14 @@ void handleDataSuccess(AmsData* data) {
|
||||
meterState.apply(*data);
|
||||
|
||||
bool saveData = false;
|
||||
if(!ds.isHappy() && now > BUILD_EPOCH) {
|
||||
if(!ds.isHappy() && now > FirmwareVersion::BuildEpoch) { // Must use "isHappy()" in case day state gets reset and lastTimestamp is "now"
|
||||
debugD_P(PSTR("Its time to update data storage"));
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
if(tm.Minute == 0) {
|
||||
if(tm.Minute == 0 && data->getListType() >= 3) {
|
||||
debugV_P(PSTR(" using actual data"));
|
||||
saveData = ds.update(data);
|
||||
} else if(meterState.getListType() >= 3) {
|
||||
} else if(tm.Minute == 1 && meterState.getListType() >= 3) {
|
||||
debugV_P(PSTR(" using estimated data"));
|
||||
saveData = ds.update(&meterState);
|
||||
}
|
||||
@@ -1289,6 +1355,7 @@ void WiFi_connect() {
|
||||
if(strlen(wifi.hostname) > 0) {
|
||||
WiFi.setHostname(wifi.hostname);
|
||||
}
|
||||
WiFi.onEvent(WiFiEvent);
|
||||
#endif
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
@@ -1619,7 +1686,7 @@ void MQTT_connect() {
|
||||
|
||||
time_t epoch = time(nullptr);
|
||||
if(mqttConfig.ssl) {
|
||||
if(epoch < BUILD_EPOCH) {
|
||||
if(epoch < FirmwareVersion::BuildEpoch) {
|
||||
debugI_P(PSTR("NTP not ready for MQTT SSL"));
|
||||
return;
|
||||
}
|
||||
@@ -1967,6 +2034,9 @@ void configFileParse() {
|
||||
} else if(strncmp_P(buf, PSTR("entsoeMultiplier "), 17) == 0) {
|
||||
if(!lEntsoe) { config.getEntsoeConfig(entsoe); lEntsoe = true; };
|
||||
entsoe.multiplier = String(buf+17).toFloat() * 1000;
|
||||
} else if(strncmp_P(buf, PSTR("entsoeFixedPrice "), 17) == 0) {
|
||||
if(!lEntsoe) { config.getEntsoeConfig(entsoe); lEntsoe = true; };
|
||||
entsoe.fixedPrice = String(buf+17).toFloat() * 1000;
|
||||
} else if(strncmp_P(buf, PSTR("thresholds "), 11) == 0) {
|
||||
if(!lEac) { config.getEnergyAccountingConfig(eac); lEac = true; };
|
||||
int i = 0;
|
||||
@@ -2062,6 +2132,7 @@ void configFileParse() {
|
||||
EnergyAccountingData ead = { 0, 0,
|
||||
0, 0, 0, // Cost
|
||||
0, 0, 0, // Income
|
||||
0, 0, 0, // Last month import, export and accuracy
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
@@ -2069,6 +2140,7 @@ void configFileParse() {
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
uint8_t peak = 0;
|
||||
uint64_t totalImport = 0, totalExport = 0;
|
||||
char * pch = strtok (buf+17," ");
|
||||
while (pch != NULL) {
|
||||
if(ead.version < 5) {
|
||||
@@ -2085,13 +2157,13 @@ void configFileParse() {
|
||||
}
|
||||
} else if(i == 3) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.costYesterday = val * 10;
|
||||
ead.costYesterday = val * 100;
|
||||
} else if(i == 4) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.costThisMonth = val;
|
||||
ead.costThisMonth = val * 100;
|
||||
} else if(i == 5) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.costLastMonth = val;
|
||||
ead.costLastMonth = val * 100;
|
||||
} else if(i >= 6 && i < 18) {
|
||||
uint8_t hour = i-6;
|
||||
{
|
||||
@@ -2112,23 +2184,23 @@ void configFileParse() {
|
||||
ead.month = val;
|
||||
} else if(i == 2) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.costYesterday = val * 10;
|
||||
ead.costYesterday = val * 100;
|
||||
} else if(i == 3) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.costThisMonth = val;
|
||||
ead.costThisMonth = val * 100;
|
||||
} else if(i == 4) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.costLastMonth = val;
|
||||
ead.costLastMonth = val * 100;
|
||||
} else if(i == 5) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.incomeYesterday= val * 10;
|
||||
ead.incomeYesterday= val * 100;
|
||||
} else if(i == 6) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.incomeThisMonth = val;
|
||||
ead.incomeThisMonth = val * 100;
|
||||
} else if(i == 7) {
|
||||
float val = String(pch).toFloat();
|
||||
ead.incomeLastMonth = val;
|
||||
} else if(i >= 8 && i < 20) {
|
||||
ead.incomeLastMonth = val * 100;
|
||||
} else if(i >= 8 && i < 18) {
|
||||
uint8_t hour = i-8;
|
||||
{
|
||||
long val = String(pch).toInt();
|
||||
@@ -2141,12 +2213,28 @@ void configFileParse() {
|
||||
ead.peaks[peak].value = val * 100;
|
||||
}
|
||||
peak++;
|
||||
} else if(i == 18) {
|
||||
float val = String(pch).toFloat();
|
||||
totalImport = val * 1000;
|
||||
} else if(i == 19) {
|
||||
float val = String(pch).toFloat();
|
||||
totalExport = val * 1000;
|
||||
}
|
||||
}
|
||||
pch = strtok (NULL, " ");
|
||||
i++;
|
||||
}
|
||||
ead.version = 5;
|
||||
uint8_t accuracy = 0;
|
||||
uint64_t importUpdate = totalImport, exportUpdate = totalExport;
|
||||
while(importUpdate > UINT32_MAX || exportUpdate > UINT32_MAX) {
|
||||
accuracy++;
|
||||
importUpdate = totalImport / pow(10, accuracy);
|
||||
exportUpdate = totalExport / pow(10, accuracy);
|
||||
}
|
||||
ead.lastMonthImport = importUpdate;
|
||||
ead.lastMonthExport = exportUpdate;
|
||||
|
||||
ead.version = 6;
|
||||
ea.setData(ead);
|
||||
sEa = true;
|
||||
}
|
||||
@@ -376,19 +376,6 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(meterType == AmsTypeKaifa) {
|
||||
if(l1current != 0)
|
||||
l1current /= 1000;
|
||||
if(l2current != 0)
|
||||
l2current /= 1000;
|
||||
if(l3current != 0)
|
||||
l3current /= 1000;
|
||||
if(l1voltage != 0)
|
||||
l1voltage /= 10;
|
||||
if(l2voltage != 0)
|
||||
l2voltage /= 10;
|
||||
if(l3voltage != 0)
|
||||
l3voltage /= 10;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION "538de5e"
|
||||
#endif
|
||||
#define BUILD_EPOCH 1668532199
|
||||
Reference in New Issue
Block a user