mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-18 17:26:46 +00:00
Energy accounting
This commit is contained in:
parent
8c69f9a738
commit
77ce5d8e90
@ -492,6 +492,63 @@ void AmsConfiguration::ackEntsoeChange() {
|
||||
entsoeChanged = false;
|
||||
}
|
||||
|
||||
|
||||
bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START, config);
|
||||
EEPROM.end();
|
||||
if(config.thresholds[9] != 255) {
|
||||
clearEnergyAccountingConfig(config);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) {
|
||||
EnergyAccountingConfig existing;
|
||||
if(getEnergyAccountingConfig(existing)) {
|
||||
for(int i = 0; i < 9; i++) {
|
||||
if(existing.thresholds[i] != config.thresholds[i]) {
|
||||
energyAccountingChanged = true;
|
||||
}
|
||||
}
|
||||
config.thresholds[9] = 255;
|
||||
} else {
|
||||
energyAccountingChanged = true;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearEnergyAccountingConfig(EnergyAccountingConfig& config) {
|
||||
config.thresholds[0] = 5;
|
||||
config.thresholds[1] = 10;
|
||||
config.thresholds[2] = 15;
|
||||
config.thresholds[3] = 20;
|
||||
config.thresholds[4] = 25;
|
||||
config.thresholds[5] = 50;
|
||||
config.thresholds[6] = 75;
|
||||
config.thresholds[7] = 100;
|
||||
config.thresholds[8] = 150;
|
||||
config.thresholds[9] = 255;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isEnergyAccountingChanged() {
|
||||
return energyAccountingChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackEnergyAccountingChange() {
|
||||
energyAccountingChanged = false;
|
||||
}
|
||||
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
MeterConfig meter;
|
||||
@ -522,6 +579,10 @@ void AmsConfiguration::clear() {
|
||||
clearEntsoe(entsoe);
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
|
||||
EnergyAccountingConfig eac;
|
||||
clearEnergyAccountingConfig(eac);
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, -1);
|
||||
EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@ -534,16 +595,6 @@ bool AmsConfiguration::hasConfig() {
|
||||
EEPROM.end();
|
||||
}
|
||||
switch(configVersion) {
|
||||
case 83:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(loadConfig83(EEPROM_CONFIG_ADDRESS+1)) {
|
||||
configVersion = EEPROM_CHECK_SUM;
|
||||
return true;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 86:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig86()) {
|
||||
@ -627,116 +678,6 @@ void AmsConfiguration::saveTempSensors() {
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::loadConfig83(int address) {
|
||||
ConfigObject83 c;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(address, c);
|
||||
|
||||
EntsoeConfig entsoe {"", "", "", 1000};
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
|
||||
NtpConfig ntp {
|
||||
c.ntpEnable,
|
||||
c.ntpDhcp,
|
||||
c.ntpOffset,
|
||||
c.ntpSummerOffset
|
||||
};
|
||||
strcpy(ntp.server, c.ntpServer);
|
||||
EEPROM.put(CONFIG_NTP_START, ntp);
|
||||
|
||||
DomoticzConfig domo {
|
||||
c.domoELIDX,
|
||||
c.domoVL1IDX,
|
||||
c.domoVL2IDX,
|
||||
c.domoVL3IDX,
|
||||
c.domoCL1IDX
|
||||
};
|
||||
EEPROM.put(CONFIG_DOMOTICZ_START, domo);
|
||||
|
||||
GpioConfig gpio {
|
||||
c.hanPin,
|
||||
c.apPin,
|
||||
c.ledPin,
|
||||
c.ledInverted,
|
||||
c.ledPinRed,
|
||||
c.ledPinGreen,
|
||||
c.ledPinBlue,
|
||||
c.ledRgbInverted,
|
||||
c.tempSensorPin,
|
||||
c.tempAnalogSensorPin,
|
||||
c.vccPin,
|
||||
c.vccOffset,
|
||||
c.vccMultiplier,
|
||||
c.vccBootLimit,
|
||||
0,
|
||||
0
|
||||
};
|
||||
EEPROM.put(CONFIG_GPIO_START, gpio);
|
||||
|
||||
DebugConfig debug {
|
||||
c.debugTelnet,
|
||||
c.debugSerial,
|
||||
c.debugLevel
|
||||
};
|
||||
EEPROM.put(CONFIG_DEBUG_START, debug);
|
||||
|
||||
MeterConfig meter {
|
||||
2400,
|
||||
c.meterType == 3 || c.meterType == 4 ? 3 : 11,
|
||||
false,
|
||||
c.distributionSystem,
|
||||
c.mainFuse,
|
||||
c.productionCapacity,
|
||||
{0},
|
||||
{0}
|
||||
};
|
||||
memcpy(meter.encryptionKey, c.meterEncryptionKey, 16);
|
||||
memcpy(meter.authenticationKey, c.meterAuthenticationKey, 16);
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
WebConfig web {
|
||||
c.authSecurity
|
||||
};
|
||||
strcpy(web.username, c.authUser);
|
||||
strcpy(web.password, c.authPassword);
|
||||
EEPROM.put(CONFIG_WEB_START, web);
|
||||
|
||||
MqttConfig mqtt;
|
||||
strcpy(mqtt.host, c.mqttHost);
|
||||
mqtt.port = c.mqttPort;
|
||||
strcpy(mqtt.clientId, c.mqttClientId);
|
||||
strcpy(mqtt.publishTopic, c.mqttPublishTopic);
|
||||
strcpy(mqtt.subscribeTopic, c.mqttSubscribeTopic);
|
||||
strcpy(mqtt.username, c.mqttUser);
|
||||
strcpy(mqtt.password, c.mqttPassword);
|
||||
mqtt.payloadFormat = c.mqttPayloadFormat;
|
||||
mqtt.ssl = c.mqttSsl;
|
||||
EEPROM.put(CONFIG_MQTT_START, mqtt);
|
||||
|
||||
WiFiConfig wifi;
|
||||
strcpy(wifi.ssid, c.wifiSsid);
|
||||
strcpy(wifi.psk, c.wifiPassword);
|
||||
strcpy(wifi.ip, c.wifiIp);
|
||||
strcpy(wifi.gateway, c.wifiGw);
|
||||
strcpy(wifi.subnet, c.wifiSubnet);
|
||||
strcpy(wifi.dns1, c.wifiDns1);
|
||||
strcpy(wifi.dns2, c.wifiDns2);
|
||||
strcpy(wifi.hostname, c.wifiHostname);
|
||||
wifi.mdns = c.mDnsEnable;
|
||||
EEPROM.put(CONFIG_WIFI_START, wifi);
|
||||
|
||||
SystemConfig sys {
|
||||
c.boardType
|
||||
};
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig86() {
|
||||
MqttConfig86 mqtt86;
|
||||
MqttConfig mqtt;
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_ENTSOE_START 290
|
||||
#define CONFIG_WIFI_START 360
|
||||
#define CONFIG_ENERGYACCOUNTING_START 520
|
||||
#define CONFIG_WEB_START 648
|
||||
#define CONFIG_DEBUG_START 824
|
||||
#define CONFIG_DOMOTICZ_START 856
|
||||
@ -152,71 +153,9 @@ struct EntsoeConfig {
|
||||
uint32_t multiplier;
|
||||
}; // 62
|
||||
|
||||
struct ConfigObject83 {
|
||||
uint8_t boardType;
|
||||
char wifiSsid[32];
|
||||
char wifiPassword[64];
|
||||
char wifiIp[15];
|
||||
char wifiGw[15];
|
||||
char wifiSubnet[15];
|
||||
char wifiDns1[15];
|
||||
char wifiDns2[15];
|
||||
char wifiHostname[32];
|
||||
char mqttHost[128];
|
||||
uint16_t mqttPort;
|
||||
char mqttClientId[32];
|
||||
char mqttPublishTopic[64];
|
||||
char mqttSubscribeTopic[64];
|
||||
char mqttUser[64];
|
||||
char mqttPassword[64];
|
||||
uint8_t mqttPayloadFormat;
|
||||
bool mqttSsl;
|
||||
uint8_t authSecurity;
|
||||
char authUser[64];
|
||||
char authPassword[64];
|
||||
|
||||
uint8_t meterType;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_t productionCapacity;
|
||||
uint8_t meterEncryptionKey[16];
|
||||
uint8_t meterAuthenticationKey[16];
|
||||
bool substituteMissing;
|
||||
bool sendUnknown;
|
||||
|
||||
bool debugTelnet;
|
||||
bool debugSerial;
|
||||
uint8_t debugLevel;
|
||||
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
bool ledInverted;
|
||||
uint8_t ledPinRed;
|
||||
uint8_t ledPinGreen;
|
||||
uint8_t ledPinBlue;
|
||||
bool ledRgbInverted;
|
||||
uint8_t tempSensorPin;
|
||||
uint8_t vccPin;
|
||||
int16_t vccOffset;
|
||||
uint16_t vccMultiplier;
|
||||
uint8_t vccBootLimit;
|
||||
|
||||
uint16_t domoELIDX;
|
||||
uint16_t domoVL1IDX;
|
||||
uint16_t domoVL2IDX;
|
||||
uint16_t domoVL3IDX;
|
||||
uint16_t domoCL1IDX;
|
||||
|
||||
bool mDnsEnable;
|
||||
bool ntpEnable;
|
||||
bool ntpDhcp;
|
||||
int16_t ntpOffset;
|
||||
int16_t ntpSummerOffset;
|
||||
char ntpServer[64];
|
||||
|
||||
uint8_t tempAnalogSensorPin;
|
||||
};
|
||||
struct EnergyAccountingConfig {
|
||||
uint8_t thresholds[10];
|
||||
}; // 10
|
||||
|
||||
struct TempSensorConfig {
|
||||
uint8_t address[8];
|
||||
@ -288,6 +227,12 @@ public:
|
||||
bool isEntsoeChanged();
|
||||
void ackEntsoeChange();
|
||||
|
||||
bool getEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
bool setEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
void clearEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
bool isEnergyAccountingChanged();
|
||||
void ackEnergyAccountingChange();
|
||||
|
||||
void loadTempSensors();
|
||||
void saveTempSensors();
|
||||
uint8_t getTempSensorCount();
|
||||
@ -303,14 +248,13 @@ protected:
|
||||
private:
|
||||
uint8_t configVersion = 0;
|
||||
|
||||
bool wifiChanged, mqttChanged, meterChanged = true, domoChanged, ntpChanged = true, entsoeChanged = false;
|
||||
bool wifiChanged, mqttChanged, meterChanged = true, domoChanged, ntpChanged = true, entsoeChanged = false, energyAccountingChanged = true;
|
||||
|
||||
uint8_t tempSensorCount = 0;
|
||||
TempSensorConfig** tempSensors = NULL;
|
||||
|
||||
bool loadConfig83(int address);
|
||||
bool relocateConfig86();
|
||||
bool relocateConfig87();
|
||||
bool relocateConfig86(); // 1.5.0
|
||||
bool relocateConfig87(); // 1.5.4
|
||||
bool relocateConfig90(); // 2.0.0
|
||||
bool relocateConfig91(); // 2.0.2
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
#include "AmsDataStorage.h"
|
||||
#include <lwip/apps/sntp.h>
|
||||
#include "EEPROM.h"
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
|
||||
@ -16,8 +15,10 @@ void AmsDataStorage::setTimezone(Timezone* tz) {
|
||||
|
||||
bool AmsDataStorage::update(AmsData* data) {
|
||||
time_t now = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Time is: %d\n", now);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Time is: %d\n", now);
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Timezone is missing\n");
|
||||
return false;
|
||||
}
|
||||
if(now < EPOCH_2021_01_01) {
|
||||
if(data->getMeterTimestamp() > 0) {
|
||||
@ -38,22 +39,26 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if(now-day.lastMeterReadTime < 3595) {
|
||||
if(now-day.lastMeterReadTime < 3500) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) It is only %d seconds since last update, ignoring\n", (now-day.lastMeterReadTime));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
tmElements_t tm, last;
|
||||
breakTime(now, tm);
|
||||
tmElements_t utc, ltz, utcYesterday, ltzYesterDay;
|
||||
breakTime(now, utc);
|
||||
breakTime(tz->toLocal(now), ltz);
|
||||
breakTime(now-3600, utcYesterday);
|
||||
breakTime(tz->toLocal(now-3600), ltzYesterDay);
|
||||
|
||||
if(day.lastMeterReadTime > EPOCH_2021_01_01) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last day update: %d\n", day.lastMeterReadTime);
|
||||
}
|
||||
tmElements_t last;
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
for(int i = last.Hour; i < tm.Hour; i++) {
|
||||
for(int i = last.Hour; i < utc.Hour; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Clearing hour: %d\n", i);
|
||||
}
|
||||
@ -65,15 +70,9 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last month update: %d\n", month.lastMeterReadTime);
|
||||
}
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
} else {
|
||||
breakTime(now, tm);
|
||||
breakTime(month.lastMeterReadTime, last);
|
||||
}
|
||||
|
||||
for(int i = last.Day; i < tm.Day; i++) {
|
||||
tmElements_t last;
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
for(int i = last.Day; i < ltz.Day; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Clearing day: %d\n", i);
|
||||
}
|
||||
@ -88,10 +87,11 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
day.activeImport = data->getActiveImportCounter() * 1000;
|
||||
day.activeExport = data->getActiveExportCounter() * 1000;
|
||||
day.lastMeterReadTime = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(data->getListType() != 3) return false;
|
||||
else if(tm.Minute > 5) return false;
|
||||
else if(ltz.Minute > 1) return false;
|
||||
|
||||
// Update day plot
|
||||
if(day.activeImport == 0 || now - day.lastMeterReadTime > 86400) {
|
||||
@ -105,13 +105,10 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
setHour(i, 0);
|
||||
}
|
||||
} else if(now - day.lastMeterReadTime < 4000) {
|
||||
breakTime(now - 3600, tm);
|
||||
int16_t val = (((data->getActiveImportCounter() * 1000) - day.activeImport) - ((data->getActiveExportCounter() * 1000) - day.activeExport));
|
||||
setHour(tm.Hour, val);
|
||||
setHour(utcYesterday.Hour, val);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for hour %d: %d\n", tm.Hour, val);
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(AmsDataStorage) Usage for hour %d: %d\n", ltzYesterDay.Hour, val);
|
||||
|
||||
day.activeImport = data->getActiveImportCounter() * 1000;
|
||||
day.activeExport = data->getActiveExportCounter() * 1000;
|
||||
@ -127,10 +124,10 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
debugger->printf("(AmsDataStorage) Since last day update, minutes: %.1f, import: %d (%.2f/min), export: %d (%.2f/min)\n", mins, im, ipm, ex, epm);
|
||||
}
|
||||
|
||||
breakTime(day.lastMeterReadTime, tm);
|
||||
day.lastMeterReadTime = day.lastMeterReadTime - (tm.Minute * 60) - tm.Second;
|
||||
breakTime(now, tm);
|
||||
time_t stopAt = now - (tm.Minute * 60) - tm.Second;
|
||||
tmElements_t last;
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
day.lastMeterReadTime = day.lastMeterReadTime - (last.Minute * 60) - last.Second;
|
||||
time_t stopAt = now - (utc.Minute * 60) - utc.Second;
|
||||
while(day.lastMeterReadTime < stopAt) {
|
||||
time_t cur = min(day.lastMeterReadTime + 3600, stopAt);
|
||||
uint8_t minutes = round((cur - day.lastMeterReadTime) / 60.0);
|
||||
@ -151,12 +148,6 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
|
||||
// Update month plot
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
} else {
|
||||
breakTime(now, tm);
|
||||
}
|
||||
|
||||
if(month.lastMeterReadTime > now) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf("(AmsDataStorage) Invalid future timestamp for month plot, resetting\n");
|
||||
@ -164,9 +155,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
month.activeImport = data->getActiveImportCounter() * 1000;
|
||||
month.activeExport = data->getActiveExportCounter() * 1000;
|
||||
month.lastMeterReadTime = now;
|
||||
}
|
||||
|
||||
if(tm.Hour == 0 && now - month.lastMeterReadTime > 86300) {
|
||||
} else if(ltz.Hour == 0) {
|
||||
if(month.activeImport == 0 || now - month.lastMeterReadTime > 2678400) {
|
||||
month.activeImport = data->getActiveImportCounter() * 1000;
|
||||
month.activeExport = data->getActiveExportCounter() * 1000;
|
||||
@ -177,17 +166,14 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
for(int i = 1; i<=31; i++) {
|
||||
setDay(i, 0);
|
||||
}
|
||||
} else if(now - month.lastMeterReadTime < 87000) {
|
||||
} else if(now - month.lastMeterReadTime < 86500 && now - month.lastMeterReadTime > 86300) {
|
||||
int32_t val = (month.activeImport == 0 ? 0 : ((data->getActiveImportCounter() * 1000) - month.activeImport) - ((data->getActiveExportCounter() * 1000) - month.activeExport));
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for day %d: %d\n", tm.Day, val);
|
||||
debugger->printf("(AmsDataStorage) Usage for day %d: %d\n", ltzYesterDay.Day, val);
|
||||
}
|
||||
|
||||
time_t yesterday = now - 3600;
|
||||
breakTime(yesterday, tm);
|
||||
setDay(tm.Day, val);
|
||||
|
||||
setDay(ltzYesterDay.Day, val);
|
||||
month.activeImport = data->getActiveImportCounter() * 1000;
|
||||
month.activeExport = data->getActiveExportCounter() * 1000;
|
||||
month.lastMeterReadTime = now;
|
||||
@ -206,32 +192,19 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
|
||||
// Make sure last month read is at midnight
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), tm);
|
||||
} else {
|
||||
breakTime(month.lastMeterReadTime, tm);
|
||||
}
|
||||
month.lastMeterReadTime = month.lastMeterReadTime - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second;
|
||||
tmElements_t last;
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
month.lastMeterReadTime = month.lastMeterReadTime - (last.Hour * 3600) - (last.Minute * 60) - last.Second;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lu\n", month.lastMeterReadTime);
|
||||
}
|
||||
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
} else {
|
||||
breakTime(now, tm);
|
||||
}
|
||||
time_t stopAt = now - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second;
|
||||
time_t stopAt = now - (ltz.Hour * 3600) - (ltz.Minute * 60) - ltz.Second;
|
||||
while(month.lastMeterReadTime < stopAt) {
|
||||
time_t cur = min(month.lastMeterReadTime + 86400, stopAt);
|
||||
uint8_t hours = round((cur - month.lastMeterReadTime) / 3600.0);
|
||||
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
} else {
|
||||
breakTime(month.lastMeterReadTime, last);
|
||||
}
|
||||
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
float val = ((iph * hours) - (eph * hours));
|
||||
setDay(last.Day, val);
|
||||
|
||||
|
||||
@ -9,5 +9,6 @@
|
||||
|
||||
#define FILE_DAYPLOT "/dayplot.bin"
|
||||
#define FILE_MONTHPLOT "/monthplot.bin"
|
||||
#define FILE_ENERGYACCOUNTING "/energyaccounting.bin"
|
||||
|
||||
#endif
|
||||
|
||||
@ -303,6 +303,7 @@ void setup() {
|
||||
tz = new Timezone(dst, std);
|
||||
ws.setTimezone(tz);
|
||||
ds.setTimezone(tz);
|
||||
ea.setTimezone(tz);
|
||||
}
|
||||
|
||||
ds.load();
|
||||
@ -313,7 +314,13 @@ void setup() {
|
||||
swapWifiMode();
|
||||
}
|
||||
|
||||
ea.setup(&ds, eapi);
|
||||
EnergyAccountingConfig *eac = new EnergyAccountingConfig();
|
||||
if(!config.getEnergyAccountingConfig(*eac)) {
|
||||
config.clearEnergyAccountingConfig(*eac);
|
||||
config.setEnergyAccountingConfig(*eac);
|
||||
config.ackEnergyAccountingChange();
|
||||
}
|
||||
ea.setup(&ds, eapi, eac);
|
||||
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds, &ea);
|
||||
|
||||
#if defined(ESP32)
|
||||
@ -420,6 +427,8 @@ void loop() {
|
||||
TimeChangeRule dst = {"DST", Last, Sun, Mar, 2, (ntp.offset + ntp.summerOffset) / 6};
|
||||
tz = new Timezone(dst, std);
|
||||
ws.setTimezone(tz);
|
||||
ds.setTimezone(tz);
|
||||
ea.setTimezone(tz);
|
||||
}
|
||||
|
||||
config.ackNtpChange();
|
||||
@ -491,6 +500,13 @@ void loop() {
|
||||
hc = NULL;
|
||||
}
|
||||
|
||||
if(config.isEnergyAccountingChanged()) {
|
||||
EnergyAccountingConfig *eac = ea.getConfig();
|
||||
config.getEnergyAccountingConfig(*eac);
|
||||
ea.setup(&ds, eapi, eac);
|
||||
config.ackEnergyAccountingChange();
|
||||
}
|
||||
|
||||
if(readHanPort() || now - meterState.getLastUpdateMillis() > 30000) {
|
||||
if(now - lastTemperatureRead > 15000) {
|
||||
unsigned long start = millis();
|
||||
@ -842,7 +858,7 @@ bool readHanPort() {
|
||||
if(!hw.ledBlink(LED_GREEN, 1))
|
||||
hw.ledBlink(LED_INTERNAL, 1);
|
||||
if(mqttEnabled && mqttHandler != NULL && mqtt != NULL) {
|
||||
if(mqttHandler->publish(&data, &meterState)) {
|
||||
if(mqttHandler->publish(&data, &meterState, &ea)) {
|
||||
if(data.getListType() == 3 && eapi != NULL) {
|
||||
mqttHandler->publishPrices(eapi);
|
||||
}
|
||||
|
||||
@ -1,94 +1,83 @@
|
||||
#include "EnergyAccounting.h"
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
|
||||
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger) {
|
||||
data.version = 1;
|
||||
this->debugger = debugger;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setup(AmsDataStorage *ds, EntsoeApi *eapi) {
|
||||
void EnergyAccounting::setup(AmsDataStorage *ds, EntsoeApi *eapi, EnergyAccountingConfig *config) {
|
||||
this->ds = ds;
|
||||
this->eapi = eapi;
|
||||
this->config = config;
|
||||
this->currentThresholdIdx = 0;
|
||||
}
|
||||
|
||||
EnergyAccountingConfig* EnergyAccounting::getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::update(AmsData* amsData) {
|
||||
time_t now = time(nullptr);
|
||||
if(now < EPOCH_2021_01_01) return false;
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Timezone is missing\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
tmElements_t local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(!init) {
|
||||
currentHour = local.Hour;
|
||||
currentDay = local.Day;
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Initializing data at %lu\n", now);
|
||||
if(!load()) {
|
||||
data = { 1, tm.Month, 0, 0, 0, 0 };
|
||||
currentHour = tm.Hour;
|
||||
currentDay = tm.Day;
|
||||
|
||||
for(int i = 0; i < tm.Hour; i++) {
|
||||
int16_t val = ds->getHour(i) / 10.0;
|
||||
if(val > data.maxHour) {
|
||||
data.maxHour = val;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data");
|
||||
data = { 1, local.Month, 0, 0, 0, 0 };
|
||||
if(calcDayUse()) ret = true;
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
if(!initPrice && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Initializing prices at %lu\n", now);
|
||||
for(int i = 0; i < tm.Hour; i++) {
|
||||
float price = eapi->getValueForHour(i-tm.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
int16_t wh = ds->getHour(i);
|
||||
double kwh = wh / 1000.0;
|
||||
costDay += price * kwh;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" Hour: %d, wh: %d, kwh; %.2f, price: %.2f, costDay: %.4f\n", i, wh, kwh, price, costDay);
|
||||
}
|
||||
initPrice = true;
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
if(amsData->getListType() >= 3 && tm.Hour != currentHour) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New hour %d\n", tm.Hour);
|
||||
if(tm.Hour > 0) {
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
costDay = 0;
|
||||
for(int i = 0; i < tm.Hour; i++) {
|
||||
float price = eapi->getValueForHour(i-tm.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
int16_t wh = ds->getHour(i);
|
||||
costDay += price * (wh / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(amsData->getListType() >= 3 && local.Hour != currentHour) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New local hour %d\n", local.Hour);
|
||||
|
||||
for(int i = 0; i < tm.Hour; i++) {
|
||||
int16_t val = ds->getHour(i) / 10.0;
|
||||
if(val > data.maxHour) {
|
||||
data.maxHour = val;
|
||||
ret = true;
|
||||
}
|
||||
if(calcDayUse()) ret = true;
|
||||
if(local.Hour > 0) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
use = 0;
|
||||
costHour = 0;
|
||||
currentHour = tm.Hour;
|
||||
currentHour = local.Hour;
|
||||
|
||||
if(tm.Day != currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New day %d\n", tm.Day);
|
||||
if(local.Day != currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New day %d\n", local.Day);
|
||||
data.costYesterday = costDay * 100;
|
||||
data.costThisMonth += costDay * 100;
|
||||
costDay = 0;
|
||||
currentDay = tm.Day;
|
||||
currentDay = local.Day;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if(tm.Month != data.month) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New month %d\n", tm.Month);
|
||||
if(local.Month != data.month) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New month %d\n", local.Month);
|
||||
data.costLastMonth = data.costThisMonth;
|
||||
data.costThisMonth = 0;
|
||||
data.maxHour = 0;
|
||||
data.month = tm.Month;
|
||||
data.month = local.Month;
|
||||
currentThresholdIdx = 0;
|
||||
ret = true;
|
||||
}
|
||||
@ -98,25 +87,62 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
float kwh = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
lastUpdateMillis = amsData->getLastUpdateMillis();
|
||||
if(kwh > 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Adding %.4f kWh\n", kwh);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh\n", kwh);
|
||||
use += kwh;
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
float price = eapi->getValueForHour(0);
|
||||
float cost = price * kwh;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency());
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency());
|
||||
costHour += cost;
|
||||
costDay += cost;
|
||||
}
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) calculating threshold, currently at %d\n", currentThresholdIdx);
|
||||
while(getMonthMax() > thresholds[currentThresholdIdx] / 10.0 && currentThresholdIdx < 5) currentThresholdIdx++;
|
||||
while(use > thresholds[currentThresholdIdx] / 10.0 && currentThresholdIdx < 5) currentThresholdIdx++;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) new threshold %d\n", currentThresholdIdx);
|
||||
if(config != NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) calculating threshold, currently at %d\n", currentThresholdIdx);
|
||||
while(getMonthMax() > config->thresholds[currentThresholdIdx] && currentThresholdIdx < 10) currentThresholdIdx++;
|
||||
while(use > config->thresholds[currentThresholdIdx] && currentThresholdIdx < 10) currentThresholdIdx++;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) new threshold %d\n", currentThresholdIdx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::calcDayUse() {
|
||||
time_t now = time(nullptr);
|
||||
tmElements_t local, utc;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
bool ret = false;
|
||||
for(int i = 0; i < local.Hour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
int16_t val = ds->getHour(utc.Hour) / 10.0;
|
||||
if(val > data.maxHour) {
|
||||
data.maxHour = val;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EnergyAccounting::calcDayCost() {
|
||||
time_t now = time(nullptr);
|
||||
tmElements_t local, utc;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(!initPrice) costDay = 0;
|
||||
for(int i = 0; i < local.Hour; i++) {
|
||||
float price = eapi->getValueForHour(i - local.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
int16_t wh = ds->getHour(utc.Hour);
|
||||
costDay += price * (wh / 1000.0);
|
||||
}
|
||||
initPrice = true;
|
||||
}
|
||||
}
|
||||
|
||||
double EnergyAccounting::getUseThisHour() {
|
||||
return use;
|
||||
}
|
||||
@ -129,10 +155,11 @@ double EnergyAccounting::getUseToday() {
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < EPOCH_2021_01_01) return 0;
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
for(int i = 0; i < tm.Hour; i++) {
|
||||
ret += ds->getHour(i) / 1000.0;
|
||||
tmElements_t local, utc;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(int i = 0; i < local.Hour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHour(utc.Hour) / 1000.0;
|
||||
}
|
||||
return ret + getUseThisHour();
|
||||
}
|
||||
@ -149,7 +176,10 @@ double EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < EPOCH_2021_01_01) return 0;
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
if(tz != NULL)
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
else
|
||||
breakTime(now, tm);
|
||||
float ret = 0;
|
||||
for(int i = 0; i < tm.Day; i++) {
|
||||
ret += ds->getDay(i) / 1000.0;
|
||||
@ -165,8 +195,10 @@ double EnergyAccounting::getCostLastMonth() {
|
||||
return data.costLastMonth / 100.0;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCurrentThreshold() {
|
||||
return thresholds[currentThresholdIdx] / 10.0;
|
||||
uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
if(config == NULL)
|
||||
return 0;
|
||||
return config->thresholds[currentThresholdIdx];
|
||||
}
|
||||
|
||||
float EnergyAccounting::getMonthMax() {
|
||||
@ -174,9 +206,51 @@ float EnergyAccounting::getMonthMax() {
|
||||
}
|
||||
|
||||
bool EnergyAccounting::load() {
|
||||
return false; // TODO
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf("(EnergyAccounting) Unable to load LittleFS\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
if(LittleFS.exists(FILE_ENERGYACCOUNTING)) {
|
||||
File file = LittleFS.open(FILE_ENERGYACCOUNTING, "r");
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
file.close();
|
||||
|
||||
if(data->version == 1) {
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
ret = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
LittleFS.end();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::save() {
|
||||
return false; // TODO
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf("(EnergyAccounting) Unable to load LittleFS\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
{
|
||||
File file = LittleFS.open(FILE_ENERGYACCOUNTING, "w");
|
||||
char buf[sizeof(data)];
|
||||
memcpy(buf, &data, sizeof(data));
|
||||
for(int i = 0; i < sizeof(data); i++) {
|
||||
file.write(buf[i]);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
LittleFS.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -18,7 +18,9 @@ struct EnergyAccountingData {
|
||||
class EnergyAccounting {
|
||||
public:
|
||||
EnergyAccounting(RemoteDebug*);
|
||||
void setup(AmsDataStorage *ds, EntsoeApi *eapi);
|
||||
void setup(AmsDataStorage *ds, EntsoeApi *eapi, EnergyAccountingConfig *config);
|
||||
void setTimezone(Timezone*);
|
||||
EnergyAccountingConfig* getConfig();
|
||||
bool update(AmsData* amsData);
|
||||
bool save();
|
||||
|
||||
@ -32,7 +34,7 @@ public:
|
||||
double getCostLastMonth();
|
||||
|
||||
float getMonthMax();
|
||||
float getCurrentThreshold();
|
||||
uint8_t getCurrentThreshold();
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger = NULL;
|
||||
@ -40,12 +42,15 @@ private:
|
||||
bool init = false, initPrice = false;
|
||||
AmsDataStorage *ds = NULL;
|
||||
EntsoeApi *eapi = NULL;
|
||||
uint8_t thresholds[5] = {50, 100, 150, 200, 250};
|
||||
EnergyAccountingConfig *config = NULL;
|
||||
Timezone *tz = NULL;
|
||||
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
|
||||
double use, costHour, costDay;
|
||||
EnergyAccountingData data;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
bool load();
|
||||
bool calcDayUse();
|
||||
void calcDayCost();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <MQTT.h>
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
|
||||
@ -14,7 +15,7 @@ public:
|
||||
this->mqtt = mqtt;
|
||||
};
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState);
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
virtual bool publishPrices(EntsoeApi* eapi);
|
||||
virtual bool publishSystem(HwTools*);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#include "DomoticzMqttHandler.h"
|
||||
#include "web/root/domoticz_json.h"
|
||||
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
bool ret = false;
|
||||
if (config.elidx > 0) {
|
||||
if(data->getActiveImportCounter() > 1.0) {
|
||||
|
||||
@ -9,7 +9,7 @@ public:
|
||||
DomoticzMqttHandler(MQTTClient* mqtt, DomoticzConfig config) : AmsMqttHandler(mqtt) {
|
||||
this->config = config;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
#include "web/root/jsonsys_json.h"
|
||||
#include "web/root/jsonprices_json.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
@ -22,7 +22,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
data->getActiveImportPower()
|
||||
data->getActiveImportPower(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCurrentThreshold()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
} else if(data->getListType() == 2) {
|
||||
@ -47,7 +49,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage()
|
||||
data->getL3Voltage(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCurrentThreshold()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
} else if(data->getListType() == 3) {
|
||||
@ -78,7 +82,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp()
|
||||
data->getMeterTimestamp(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCurrentThreshold()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
} else {
|
||||
@ -112,7 +118,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp()
|
||||
data->getMeterTimestamp(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCurrentThreshold()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ public:
|
||||
this->topic = String(topic);
|
||||
this->hw = hw;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState) {
|
||||
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
@ -71,6 +71,8 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState) {
|
||||
mqtt->publish(topic + "/meter/import/active", String(data->getActiveImportPower()));
|
||||
}
|
||||
}
|
||||
mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
|
||||
mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ public:
|
||||
this->topic = String(topic);
|
||||
this->full = full;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
#include "root/dayplot_json.h"
|
||||
#include "root/monthplot_json.h"
|
||||
#include "root/energyprice_json.h"
|
||||
#include "root/energyaccounting_html.h"
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
@ -79,6 +80,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
server.on("/web", HTTP_GET, std::bind(&AmsWebServer::configWebHtml, this));
|
||||
server.on("/domoticz",HTTP_GET, std::bind(&AmsWebServer::configDomoticzHtml, this));
|
||||
server.on("/entsoe",HTTP_GET, std::bind(&AmsWebServer::configEntsoeHtml, this));
|
||||
server.on("/accounting",HTTP_GET, std::bind(&AmsWebServer::configEnergyAccountingHtml, this));
|
||||
server.on("/boot.css", HTTP_GET, std::bind(&AmsWebServer::bootCss, this));
|
||||
server.on("/github.svg", HTTP_GET, std::bind(&AmsWebServer::githubSvg, this));
|
||||
server.on("/data.json", HTTP_GET, std::bind(&AmsWebServer::dataJson, this));
|
||||
@ -647,6 +649,25 @@ void AmsWebServer::configEntsoeHtml() {
|
||||
}
|
||||
}
|
||||
|
||||
void AmsWebServer::configEnergyAccountingHtml() {
|
||||
printD("Serving /accounting.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
EnergyAccountingConfig* config = ea->getConfig();
|
||||
|
||||
String html = String((const __FlashStringHelper*) ENERGYACCOUNTING_HTML);
|
||||
for(int i = 0; i < 9; i++) {
|
||||
html.replace("{t" + String(i) + "}", String(config->thresholds[i]));
|
||||
}
|
||||
|
||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
server.send_P(200, "text/html", HEAD_HTML);
|
||||
server.sendContent(html);
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
}
|
||||
|
||||
void AmsWebServer::configWebHtml() {
|
||||
printD("Serving /web.html over http...");
|
||||
|
||||
@ -1286,6 +1307,21 @@ void AmsWebServer::handleSave() {
|
||||
config->setEntsoeConfig(entsoe);
|
||||
}
|
||||
|
||||
if(server.hasArg("cc") && server.arg("cc") == "true") {
|
||||
printD("Received energy accounting config");
|
||||
EnergyAccountingConfig eac;
|
||||
eac.thresholds[0] = server.arg("t0").toInt();
|
||||
eac.thresholds[1] = server.arg("t1").toInt();
|
||||
eac.thresholds[2] = server.arg("t2").toInt();
|
||||
eac.thresholds[3] = server.arg("t3").toInt();
|
||||
eac.thresholds[4] = server.arg("t4").toInt();
|
||||
eac.thresholds[5] = server.arg("t5").toInt();
|
||||
eac.thresholds[6] = server.arg("t6").toInt();
|
||||
eac.thresholds[7] = server.arg("t7").toInt();
|
||||
eac.thresholds[8] = server.arg("t8").toInt();
|
||||
config->setEnergyAccountingConfig(eac);
|
||||
}
|
||||
|
||||
printI("Saving configuration now...");
|
||||
|
||||
//if (debugger->isActive(RemoteDebug::DEBUG)) config->print(debugger);
|
||||
|
||||
@ -81,6 +81,7 @@ private:
|
||||
void configNtpHtml();
|
||||
void configGpioHtml();
|
||||
void configDebugHtml();
|
||||
void configEnergyAccountingHtml();
|
||||
void bootCss();
|
||||
void githubSvg();
|
||||
void dataJson();
|
||||
|
||||
@ -755,13 +755,16 @@ var fetch = function() {
|
||||
if(json.ea) {
|
||||
$('#each').html(json.ea.h.u.toFixed(2));
|
||||
$('#eachc').html(json.ea.h.c.toFixed(2));
|
||||
$('#eacd').html(json.ea.d.u.toFixed(2));
|
||||
$('#eacdc').html(json.ea.d.c.toFixed(2));
|
||||
$('#eacm').html(json.ea.m.u.toFixed(2));
|
||||
$('#eacmc').html(json.ea.m.c.toFixed(2));
|
||||
$('#eax').html(json.ea.x.toFixed(2));
|
||||
$('#eat').html(json.ea.t.toFixed(2));
|
||||
$('#eacd').html(json.ea.d.u.toFixed(1));
|
||||
$('#eacdc').html(json.ea.d.c.toFixed(1));
|
||||
$('#eacm').html(json.ea.m.u.toFixed(0));
|
||||
$('#eacmc').html(json.ea.m.c.toFixed(0));
|
||||
$('#eax').html(json.ea.x.toFixed(1));
|
||||
$('#eat').html(json.ea.t.toFixed(0));
|
||||
$('.cr').html(currency);
|
||||
if(currency) {
|
||||
$('.sp').show();
|
||||
}
|
||||
}
|
||||
|
||||
if(json.me) {
|
||||
|
||||
116
web/energyaccounting.html
Normal file
116
web/energyaccounting.html
Normal file
@ -0,0 +1,116 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="cc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Tariff thresholds</h6>
|
||||
<div class="row">
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">1</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t0" type="number" min="5" max="255" step="1" value="{t0}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">2</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t1" type="number" min="5" max="255" step="1" value="{t1}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">3</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t2" type="number" min="5" max="255" step="1" value="{t2}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">4</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t3" type="number" min="5" max="255" step="1" value="{t3}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">5</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t4" type="number" min="5" max="255" step="1" value="{t4}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">6</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t5" type="number" min="5" max="255" step="1" value="{t5}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">7</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t6" type="number" min="5" max="255" step="1" value="{t6}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">8</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t7" type="number" min="5" max="255" step="1" value="{t7}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">9</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t8" type="number" min="5" max="255" step="1" value="{t8}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -62,6 +62,7 @@
|
||||
<a class="dropdown-item" href="/mqtt">MQTT</a>
|
||||
<a class="dropdown-item" href="/web">Web</a>
|
||||
<a class="dropdown-item" href="/ntp">NTP</a>
|
||||
<a class="dropdown-item" href="/accounting">Thresholds</a>
|
||||
<a class="dropdown-item" href="/entsoe">ENTSO-E API</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="https://github.com/gskjold/AmsToMqttBridge/wiki" target="_blank">Documentation</a>
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
<a class="dropdown-item" href="/mqtt">MQTT</a>
|
||||
<a class="dropdown-item" href="/web">Web</a>
|
||||
<a class="dropdown-item" href="/ntp">NTP</a>
|
||||
<a class="dropdown-item" href="/accounting">Thresholds</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="https://github.com/gskjold/AmsToMqttBridge/wiki" target="_blank">Documentation</a>
|
||||
</div>
|
||||
|
||||
@ -113,14 +113,14 @@
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow pt-3 pb-3" style="font-size: 14px;">
|
||||
<strong class="mr-3 ml-3">Current use and cost</strong><br/>
|
||||
<strong class="mr-3 ml-3">Real time calculation</strong><br/>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Hour</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="each"></span> kWh
|
||||
<span class="sp text-nowrap">(<span id="eachc"></span> <span class="cr"></span>)</span>
|
||||
<span class="sp text-nowrap" style="display: none;">(<span id="eachc"></span> <span class="cr"></span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -129,7 +129,7 @@
|
||||
<div>Day</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacd"></span> kWh
|
||||
<span class="sp text-nowrap">(<span id="eacdc"></span> <span class="cr"></span>)</span>
|
||||
<span class="sp text-nowrap" style="display: none;">(<span id="eacdc"></span> <span class="cr"></span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -138,14 +138,16 @@
|
||||
<div>Month</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacm"></span> kWh
|
||||
<span class="sp text-nowrap">(<span id="eacmc"></span> <span class="cr"></span>)</span>
|
||||
<span class="sp text-nowrap" style="display: none;">(<span id="eacmc"></span> <span class="cr"></span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="row mr-3 ml-3">
|
||||
<div class="col-3">Max</div>
|
||||
<div class="col-9 text-right"><span id="eax"></span> / <span id="eat"></span> kWh</div>
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Max</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eax"></span> / <span id="eat"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -8,5 +8,9 @@
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"P" : %d
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"t" : %d
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,5 +20,9 @@
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"t" : %d
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,5 +25,9 @@
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : %lu
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"t" : %d
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,5 +29,9 @@
|
||||
"tQI" : %.2f,
|
||||
"tQO" : %.2f,
|
||||
"rtc" : %lu
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"t" : %d
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user