Some serious restructuring to be able to swap between implementations

This commit is contained in:
Gunnar Skjold
2022-09-03 13:12:29 +02:00
parent 8b0d4185d3
commit 488c969858
77 changed files with 43 additions and 40 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,277 +0,0 @@
#ifndef _AMSCONFIGURATION_h
#define _AMSCONFIGURATION_h
#include <EEPROM.h>
#include "Arduino.h"
#define EEPROM_SIZE 1024*3
#define EEPROM_CHECK_SUM 95 // Used to check if config is stored. Change if structure changes
#define EEPROM_CONFIG_ADDRESS 0
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
#define CONFIG_SYSTEM_START 8
#define CONFIG_METER_START 32
#define CONFIG_GPIO_START 266
#define CONFIG_ENTSOE_START 290
#define CONFIG_WIFI_START 360
#define CONFIG_ENERGYACCOUNTING_START 576
#define CONFIG_WEB_START 648
#define CONFIG_DEBUG_START 824
#define CONFIG_DOMOTICZ_START 856
#define CONFIG_NTP_START 872
#define CONFIG_MQTT_START 1004
#define CONFIG_MQTT_START_86 224
#define CONFIG_METER_START_87 784
#define CONFIG_ENTSOE_START_90 286
#define CONFIG_WIFI_START_91 16
#define CONFIG_METER_START_93 224
struct SystemConfig {
uint8_t boardType;
}; // 1
struct WiFiConfig91 {
char ssid[32];
char psk[64];
char ip[15];
char gateway[15];
char subnet[15];
char dns1[15];
char dns2[15];
char hostname[32];
bool mdns;
}; // 204
struct WiFiConfig {
char ssid[32];
char psk[64];
char ip[16];
char gateway[16];
char subnet[16];
char dns1[16];
char dns2[16];
char hostname[32];
bool mdns;
uint8_t power;
}; // 210
struct MqttConfig86 {
char host[128];
uint16_t port;
char clientId[32];
char publishTopic[64];
char subscribeTopic[64];
char username[64];
char password[64];
uint8_t payloadFormat;
bool ssl;
}; // 420
struct MqttConfig {
char host[128];
uint16_t port;
char clientId[32];
char publishTopic[64];
char subscribeTopic[64];
char username[128];
char password[256];
uint8_t payloadFormat;
bool ssl;
}; // 676
struct WebConfig {
uint8_t security;
char username[64];
char password[64];
}; // 129
struct MeterConfig {
uint32_t baud;
uint8_t parity;
bool invert;
uint8_t distributionSystem;
uint8_t mainFuse;
uint8_t productionCapacity;
uint8_t encryptionKey[16];
uint8_t authenticationKey[16];
uint16_t wattageMultiplier;
uint16_t voltageMultiplier;
uint16_t amperageMultiplier;
uint16_t accumulatedMultiplier;
uint8_t source;
uint8_t parser;
}; // 50
struct MeterConfig87 {
uint8_t type;
uint8_t distributionSystem;
uint8_t mainFuse;
uint8_t productionCapacity;
uint8_t encryptionKey[16];
uint8_t authenticationKey[16];
bool substituteMissing;
}; // 37
struct DebugConfig {
bool telnet;
bool serial;
uint8_t level;
}; // 3
struct GpioConfig {
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 tempAnalogSensorPin;
uint8_t vccPin;
int16_t vccOffset;
uint16_t vccMultiplier;
uint8_t vccBootLimit;
uint16_t vccResistorGnd;
uint16_t vccResistorVcc;
}; // 20
struct DomoticzConfig {
uint16_t elidx;
uint16_t vl1idx;
uint16_t vl2idx;
uint16_t vl3idx;
uint16_t cl1idx;
}; // 10
struct NtpConfig {
bool enable;
bool dhcp;
int16_t offset;
int16_t summerOffset;
char server[64];
}; // 70
struct EntsoeConfig {
char token[37];
char area[17];
char currency[4];
uint32_t multiplier;
}; // 62
struct EnergyAccountingConfig {
uint8_t thresholds[10];
uint8_t hours;
}; // 11
struct TempSensorConfig {
uint8_t address[8];
char name[16];
bool common;
};
class AmsConfiguration {
public:
bool hasConfig();
int getConfigVersion();
bool save();
bool getSystemConfig(SystemConfig&);
bool setSystemConfig(SystemConfig&);
bool getWiFiConfig(WiFiConfig&);
bool setWiFiConfig(WiFiConfig&);
void clearWifi(WiFiConfig&);
void clearWifiIp(WiFiConfig&);
bool isWifiChanged();
void ackWifiChange();
bool getMqttConfig(MqttConfig&);
bool setMqttConfig(MqttConfig&);
void clearMqtt(MqttConfig&);
void setMqttChanged();
bool isMqttChanged();
void ackMqttChange();
bool getWebConfig(WebConfig&);
bool setWebConfig(WebConfig&);
void clearAuth(WebConfig&);
bool getMeterConfig(MeterConfig&);
bool setMeterConfig(MeterConfig&);
void clearMeter(MeterConfig&);
bool isMeterChanged();
void ackMeterChanged();
bool getDebugConfig(DebugConfig&);
bool setDebugConfig(DebugConfig&);
void clearDebug(DebugConfig&);
bool pinUsed(uint8_t, GpioConfig&);
bool getGpioConfig(GpioConfig&);
bool setGpioConfig(GpioConfig&);
void clearGpio(GpioConfig&);
void print(Print* debugger);
bool getDomoticzConfig(DomoticzConfig&);
bool setDomoticzConfig(DomoticzConfig&);
void clearDomo(DomoticzConfig&);
bool isDomoChanged();
void ackDomoChange();
bool getNtpConfig(NtpConfig&);
bool setNtpConfig(NtpConfig&);
void clearNtp(NtpConfig&);
bool isNtpChanged();
void ackNtpChange();
bool getEntsoeConfig(EntsoeConfig&);
bool setEntsoeConfig(EntsoeConfig&);
void clearEntsoe(EntsoeConfig&);
bool isEntsoeChanged();
void ackEntsoeChange();
bool getEnergyAccountingConfig(EnergyAccountingConfig&);
bool setEnergyAccountingConfig(EnergyAccountingConfig&);
void clearEnergyAccountingConfig(EnergyAccountingConfig&);
bool isEnergyAccountingChanged();
void ackEnergyAccountingChange();
void loadTempSensors();
void saveTempSensors();
uint8_t getTempSensorCount();
TempSensorConfig* getTempSensorConfig(uint8_t address[8]);
void updateTempSensorConfig(uint8_t address[8], const char name[32], bool common);
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
void clear();
protected:
private:
uint8_t configVersion = 0;
bool wifiChanged, mqttChanged, meterChanged = true, domoChanged, ntpChanged = true, entsoeChanged = false, energyAccountingChanged = true;
uint8_t tempSensorCount = 0;
TempSensorConfig** tempSensors = NULL;
bool relocateConfig86(); // 1.5.0
bool relocateConfig87(); // 1.5.4
bool relocateConfig90(); // 2.0.0
bool relocateConfig91(); // 2.0.2
bool relocateConfig92(); // 2.0.3
bool relocateConfig93(); // 2.1.0
bool relocateConfig94(); // 2.1.4
void saveToFs();
bool loadFromFs(uint8_t version);
void deleteFromFs(uint8_t version);
};
#endif

View File

@@ -1,186 +0,0 @@
#include "AmsData.h"
AmsData::AmsData() {}
void AmsData::apply(AmsData& other) {
if(other.getListType() < 3) {
unsigned long ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastUpdateMillis;
if(ms > 0) {
if(other.getActiveImportPower() > 0) {
float add = other.getActiveImportPower() * (((float) ms) / 3600000.0);
activeImportCounter += add / 1000.0;
//Serial.printf("%dW, %dms, %.6fkWh added\n", other.getActiveImportPower(), ms, add);
}
if(other.getListType() > 1) {
ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastList2;
if(other.getActiveExportPower() > 0) {
float add = other.getActiveExportPower() * (((float) ms) / 3600000.0);
activeExportCounter += add / 1000.0;
}
if(other.getReactiveImportPower() > 0) {
float add = other.getReactiveImportPower() * (((float) ms) / 3600000.0);
reactiveImportCounter += add / 1000.0;
}
if(other.getReactiveExportPower() > 0) {
float add = other.getReactiveExportPower() * (((float) ms) / 3600000.0);
reactiveExportCounter += add / 1000.0;
}
}
counterEstimated = true;
}
}
this->lastUpdateMillis = other.getLastUpdateMillis();
if(other.getListType() > 1) {
this->lastList2 = this->lastUpdateMillis;
}
this->packageTimestamp = other.getPackageTimestamp();
if(other.getListType() > this->listType)
this->listType = other.getListType();
switch(other.getListType()) {
case 4:
this->powerFactor = other.getPowerFactor();
this->l1PowerFactor = other.getL1PowerFactor();
this->l2PowerFactor = other.getL2PowerFactor();
this->l3PowerFactor = other.getL3PowerFactor();
case 3:
this->meterTimestamp = other.getMeterTimestamp();
this->activeImportCounter = other.getActiveImportCounter();
this->activeExportCounter = other.getActiveExportCounter();
this->reactiveImportCounter = other.getReactiveImportCounter();
this->reactiveExportCounter = other.getReactiveExportCounter();
this->counterEstimated = false;
case 2:
this->listId = other.getListId();
this->meterId = other.getMeterId();
this->meterType = other.getMeterType();
this->meterModel = other.getMeterModel();
this->reactiveImportPower = other.getReactiveImportPower();
this->activeExportPower = other.getActiveExportPower();
this->reactiveExportPower = other.getReactiveExportPower();
this->l1current = other.getL1Current();
this->l2current = other.getL2Current();
this->l3current = other.getL3Current();
this->l1voltage = other.getL1Voltage();
this->l2voltage = other.getL2Voltage();
this->l3voltage = other.getL3Voltage();
this->threePhase = other.isThreePhase();
this->twoPhase = other.isTwoPhase();
case 1:
this->activeImportPower = other.getActiveImportPower();
}
}
unsigned long AmsData::getLastUpdateMillis() {
return this->lastUpdateMillis;
}
time_t AmsData::getPackageTimestamp() {
return this->packageTimestamp;
}
uint8_t AmsData::getListType() {
return this->listType;
}
String AmsData::getListId() {
return this->listId;
}
String AmsData::getMeterId() {
return this->meterId;
}
uint8_t AmsData::getMeterType() {
return this->meterType;
}
String AmsData::getMeterModel() {
return this->meterModel;
}
time_t AmsData::getMeterTimestamp() {
return this->meterTimestamp;
}
uint16_t AmsData::getActiveImportPower() {
return this->activeImportPower;
}
uint16_t AmsData::getReactiveImportPower() {
return this->reactiveImportPower;
}
uint16_t AmsData::getActiveExportPower() {
return this->activeExportPower;
}
uint16_t AmsData::getReactiveExportPower() {
return this->reactiveExportPower;
}
float AmsData::getL1Voltage() {
return this->l1voltage;
}
float AmsData::getL2Voltage() {
return this->l2voltage;
}
float AmsData::getL3Voltage() {
return this->l3voltage;
}
float AmsData::getL1Current() {
return this->l1current;
}
float AmsData::getL2Current() {
return this->l2current;
}
float AmsData::getL3Current() {
return this->l3current;
}
float AmsData::getPowerFactor() {
return this->powerFactor;
}
float AmsData::getL1PowerFactor() {
return this->l1PowerFactor;
}
float AmsData::getL2PowerFactor() {
return this->l2PowerFactor;
}
float AmsData::getL3PowerFactor() {
return this->l3PowerFactor;
}
double AmsData::getActiveImportCounter() {
return this->activeImportCounter;
}
double AmsData::getReactiveImportCounter() {
return this->reactiveImportCounter;
}
double AmsData::getActiveExportCounter() {
return this->activeExportCounter;
}
double AmsData::getReactiveExportCounter() {
return this->reactiveExportCounter;
}
bool AmsData::isThreePhase() {
return this->threePhase;
}
bool AmsData::isTwoPhase() {
return this->twoPhase;
}

View File

@@ -1,78 +0,0 @@
#ifndef _AMSDATA_H
#define _AMSDATA_H
#include "Arduino.h"
#include <Timezone.h>
enum AmsType {
AmsTypeAutodetect = 0x00,
AmsTypeAidon = 0x01,
AmsTypeKaifa = 0x02,
AmsTypeKamstrup = 0x03,
AmsTypeIskra = 0x08,
AmsTypeLandis = 0x09,
AmsTypeSagemcom = 0x0A,
AmsTypeCustom = 0x88,
AmsTypeUnknown = 0xFF
};
class AmsData {
public:
AmsData();
void apply(AmsData& other);
unsigned long getLastUpdateMillis();
time_t getPackageTimestamp();
uint8_t getListType();
String getListId();
String getMeterId();
uint8_t getMeterType();
String getMeterModel();
time_t getMeterTimestamp();
uint16_t getActiveImportPower();
uint16_t getReactiveImportPower();
uint16_t getActiveExportPower();
uint16_t getReactiveExportPower();
float getL1Voltage();
float getL2Voltage();
float getL3Voltage();
float getL1Current();
float getL2Current();
float getL3Current();
float getPowerFactor();
float getL1PowerFactor();
float getL2PowerFactor();
float getL3PowerFactor();
double getActiveImportCounter();
double getReactiveImportCounter();
double getActiveExportCounter();
double getReactiveExportCounter();
bool isThreePhase();
bool isTwoPhase();
protected:
unsigned long lastUpdateMillis = 0;
unsigned long lastList2 = 0;
uint8_t listType = 0, meterType = AmsTypeUnknown;
time_t packageTimestamp = 0;
String listId, meterId, meterModel;
time_t meterTimestamp = 0;
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
bool threePhase = false, twoPhase = false, counterEstimated = false;
};
#endif

View File

@@ -1,429 +0,0 @@
#include "AmsDataStorage.h"
#include <lwip/apps/sntp.h>
#include "LittleFS.h"
#include "AmsStorage.h"
#include "version.h"
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
day.version = 4;
month.version = 5;
this->debugger = debugger;
}
void AmsDataStorage::setTimezone(Timezone* tz) {
this->tz = tz;
}
bool AmsDataStorage::update(AmsData* data) {
if(isHappy()) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Data is up to date\n");
return false;
}
time_t now = time(nullptr);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Time is: %lld\n", (int64_t) now);
if(tz == NULL) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Timezone is missing\n");
return false;
}
if(now < BUILD_EPOCH) {
if(data->getMeterTimestamp() > BUILD_EPOCH) {
now = data->getMeterTimestamp();
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Using meter timestamp, which is: %lld\n", (int64_t) now);
}
} else if(data->getPackageTimestamp() > BUILD_EPOCH) {
now = data->getPackageTimestamp();
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Using package timestamp, which is: %lld\n", (int64_t) now);
}
}
}
if(now < BUILD_EPOCH) {
if(debugger->isActive(RemoteDebug::VERBOSE)) {
debugger->printf("(AmsDataStorage) Invalid time: %lld\n", (int64_t) now);
}
return false;
}
tmElements_t utc, ltz, utcYesterday, ltzYesterDay;
breakTime(now, utc);
breakTime(tz->toLocal(now), ltz);
breakTime(now-3600, utcYesterday);
breakTime(tz->toLocal(now-3600), ltzYesterDay);
// Clear hours between last update and now
if(day.lastMeterReadTime > now) {
if(debugger->isActive(RemoteDebug::WARNING)) {
debugger->printf("(AmsDataStorage) Invalid future timestamp for day plot, resetting\n");
}
day.activeImport = data->getActiveImportCounter() * 1000;
day.activeExport = data->getActiveExportCounter() * 1000;
day.lastMeterReadTime = now;
return true;
} else {
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Last day update: %lld\n", (int64_t) day.lastMeterReadTime);
}
tmElements_t last;
breakTime(day.lastMeterReadTime, last);
for(int i = last.Hour; i < utc.Hour; i++) {
if(debugger->isActive(RemoteDebug::VERBOSE)) {
debugger->printf("(AmsDataStorage) Clearing hour: %d\n", i);
}
setHourImport(i, 0);
setHourExport(i, 0);
}
}
// Clear days between last update and now
if(month.lastMeterReadTime > now) {
if(debugger->isActive(RemoteDebug::WARNING)) {
debugger->printf("(AmsDataStorage) Invalid future timestamp for month plot, resetting\n");
}
month.activeImport = data->getActiveImportCounter() * 1000;
month.activeExport = data->getActiveExportCounter() * 1000;
month.lastMeterReadTime = now;
} else {
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Last month update: %lld\n", (int64_t) month.lastMeterReadTime);
}
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);
}
setDayImport(i, 0);
setDayExport(i, 0);
}
}
if(data->getListType() < 3) {
debugger->printf("(AmsDataStorage) Not enough data in list type: %d\n", data->getListType());
return false;
}
bool ret = false;
// Update day plot
if(!isDayHappy()) {
if(day.activeImport == 0 || now - day.lastMeterReadTime > 86400) {
day.activeImport = data->getActiveImportCounter() * 1000;
day.activeExport = data->getActiveExportCounter() * 1000;
day.lastMeterReadTime = now;
if(debugger->isActive(RemoteDebug::WARNING)) {
debugger->printf("(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 = (data->getActiveImportCounter() * 1000) - day.activeImport;
uint32_t exp = (data->getActiveExportCounter() * 1000) - day.activeExport;
setHourImport(utcYesterday.Hour, imp);
setHourExport(utcYesterday.Hour, exp);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(AmsDataStorage) Usage for hour %d: %d - %d\n", ltzYesterDay.Hour, imp, exp);
day.activeImport = data->getActiveImportCounter() * 1000;
day.activeExport = data->getActiveExportCounter() * 1000;
day.lastMeterReadTime = now;
} else {
float mins = (now - day.lastMeterReadTime) / 60.0;
uint32_t im = (data->getActiveImportCounter() * 1000) - day.activeImport;
uint32_t ex = (data->getActiveExportCounter() * 1000) - day.activeExport;
float ipm = im / mins;
float epm = ex / mins;
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Since last day update, minutes: %.1f, import: %d (%.2f/min), export: %d (%.2f/min)\n", mins, im, ipm, ex, epm);
}
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);
if(minutes < 1) break;
breakTime(day.lastMeterReadTime, last);
float imp = (ipm * minutes);
float exp = (epm * minutes);
setHourImport(last.Hour, imp);
setHourExport(last.Hour, exp);
if(debugger->isActive(RemoteDebug::INFO)) {
debugger->printf("(AmsDataStorage) Estimated usage for hour %u: %.1f - %.1f (%lld)\n", last.Hour, imp, exp, (int64_t) cur);
}
day.activeImport += imp;
day.activeExport += exp;
day.lastMeterReadTime = cur;
}
}
ret = true;
}
// Update month plot
if(ltz.Hour == 0 && !isMonthHappy()) {
if(month.activeImport == 0 || now - month.lastMeterReadTime > 2678400) {
month.activeImport = data->getActiveImportCounter() * 1000;
month.activeExport = data->getActiveExportCounter() * 1000;
month.lastMeterReadTime = now;
if(debugger->isActive(RemoteDebug::WARNING)) {
debugger->printf("(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 < 86500 && now - month.lastMeterReadTime > 86300) {
int32_t imp = (data->getActiveImportCounter() * 1000) - month.activeImport;
int32_t exp = (data->getActiveExportCounter() * 1000) - month.activeExport;
if(debugger->isActive(RemoteDebug::INFO)) {
debugger->printf("(AmsDataStorage) Usage for day %d: %d - %d\n", ltzYesterDay.Day, imp, exp);
}
setDayImport(ltzYesterDay.Day, imp);
setDayExport(ltzYesterDay.Day, exp);
month.activeImport = data->getActiveImportCounter() * 1000;
month.activeExport = data->getActiveExportCounter() * 1000;
month.lastMeterReadTime = now;
} else {
// Make sure last month read is at midnight
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: %lld\n", (int64_t) month.lastMeterReadTime);
}
float hrs = (now - month.lastMeterReadTime) / 3600.0;
uint32_t im = (data->getActiveImportCounter() * 1000) - month.activeImport;
uint32_t ex = (data->getActiveExportCounter() * 1000) - month.activeExport;
float iph = im / hrs;
float eph = ex / hrs;
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Since last month update, hours: %.1f, import: %d (%.2f/hr), export: %d (%.2f/hr)\n", hrs, im, iph, ex, eph);
}
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);
breakTime(tz->toLocal(month.lastMeterReadTime), last);
float imp = (iph * hours);
float exp = (eph * hours);
setDayImport(last.Day, imp);
setDayExport(last.Day, exp);
if(debugger->isActive(RemoteDebug::INFO)) {
debugger->printf("(AmsDataStorage) Estimated usage for day %u: %.1f - %.1f (%lld)\n", last.Day, imp, exp, (int64_t) cur);
}
month.activeImport += imp;
month.activeExport += exp;
month.lastMeterReadTime = cur;
}
}
ret = true;
}
return ret;
}
void AmsDataStorage::setHourImport(uint8_t hour, int32_t val) {
if(hour < 0 || hour > 24) return;
day.hImport[hour] = val / 10;
}
int32_t AmsDataStorage::getHourImport(uint8_t hour) {
if(hour < 0 || hour > 24) return 0;
return day.hImport[hour] * 10;
}
void AmsDataStorage::setHourExport(uint8_t hour, int32_t val) {
if(hour < 0 || hour > 24) return;
day.hExport[hour] = val / 10;
}
int32_t AmsDataStorage::getHourExport(uint8_t hour) {
if(hour < 0 || hour > 24) return 0;
return day.hExport[hour] * 10;
}
void AmsDataStorage::setDayImport(uint8_t day, int32_t val) {
if(day < 1 || day > 31) return;
month.dImport[day-1] = val / 10;
}
int32_t AmsDataStorage::getDayImport(uint8_t day) {
if(day < 1 || day > 31) return 0;
return (month.dImport[day-1] * 10);
}
void AmsDataStorage::setDayExport(uint8_t day, int32_t val) {
if(day < 1 || day > 31) return;
month.dExport[day-1] = val / 10;
}
int32_t AmsDataStorage::getDayExport(uint8_t day) {
if(day < 1 || day > 31) return 0;
return (month.dExport[day-1] * 10);
}
bool AmsDataStorage::load() {
if(!LittleFS.begin()) {
if(debugger->isActive(RemoteDebug::ERROR)) {
debugger->printf("(AmsDataStorage) Unable to load LittleFS\n");
}
return false;
}
bool ret = false;
if(LittleFS.exists(FILE_DAYPLOT)) {
File file = LittleFS.open(FILE_DAYPLOT, "r");
char buf[file.size()];
file.readBytes(buf, file.size());
DayDataPoints* day = (DayDataPoints*) buf;
file.close();
ret = setDayData(*day);
}
if(LittleFS.exists(FILE_MONTHPLOT)) {
File file = LittleFS.open(FILE_MONTHPLOT, "r");
char buf[file.size()];
file.readBytes(buf, file.size());
MonthDataPoints* month = (MonthDataPoints*) buf;
file.close();
ret = ret && setMonthData(*month);
}
LittleFS.end();
return ret;
}
bool AmsDataStorage::save() {
if(!LittleFS.begin()) {
if(debugger->isActive(RemoteDebug::ERROR)) {
debugger->printf("(AmsDataStorage) Unable to load LittleFS\n");
}
return false;
}
{
File file = LittleFS.open(FILE_DAYPLOT, "w");
char buf[sizeof(day)];
memcpy(buf, &day, sizeof(day));
for(unsigned long i = 0; i < sizeof(day); i++) {
file.write(buf[i]);
}
file.close();
}
{
File file = LittleFS.open(FILE_MONTHPLOT, "w");
char buf[sizeof(month)];
memcpy(buf, &month, sizeof(month));
for(unsigned long i = 0; i < sizeof(month); i++) {
file.write(buf[i]);
}
file.close();
}
LittleFS.end();
return true;
}
DayDataPoints AmsDataStorage::getDayData() {
return day;
}
MonthDataPoints AmsDataStorage::getMonthData() {
return month;
}
bool AmsDataStorage::setDayData(DayDataPoints& day) {
if(day.version == 4) {
this->day = day;
return true;
} else if(day.version == 3) {
this->day = day;
for(uint8_t i = 0; i < 24; i++) this->day.hExport[i] = 0;
this->day.version = 4;
return true;
}
return false;
}
bool AmsDataStorage::setMonthData(MonthDataPoints& month) {
if(month.version == 5) {
this->month = month;
return true;
} else if(month.version == 4) {
this->month = month;
for(uint8_t i = 0; i < 31; i++) this->month.dExport[i] = 0;
this->month.version = 5;
return true;
}
return false;
}
bool AmsDataStorage::isHappy() {
return isDayHappy() && isMonthHappy();
}
bool AmsDataStorage::isDayHappy() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return false;
tmElements_t tm, last;
if(now < day.lastMeterReadTime) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld < %lld\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
return false;
}
if(now-day.lastMeterReadTime > 3600) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld - %lld > 3600\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
return false;
}
breakTime(tz->toLocal(now), tm);
breakTime(tz->toLocal(day.lastMeterReadTime), last);
if(tm.Hour > last.Hour) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %d > %d\n", tm.Hour, last.Hour);
return false;
}
return true;
}
bool AmsDataStorage::isMonthHappy() {
if(tz == NULL) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Timezone is missing\n");
return false;
}
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return false;
tmElements_t tm, last;
if(now < month.lastMeterReadTime) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld < %lld\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
return false;
}
if(now-month.lastMeterReadTime > 86400) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld - %lld > 3600\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
return false;
}
breakTime(tz->toLocal(now), tm);
breakTime(tz->toLocal(month.lastMeterReadTime), last);
if(tm.Day > last.Day) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %d > %d\n", tm.Day, last.Day);
return false;
}
return true;
}

View File

@@ -1,68 +0,0 @@
#ifndef _AMSDATASTORAGE_H
#define _AMSDATASTORAGE_H
#include "Arduino.h"
#include "AmsData.h"
#include "RemoteDebug.h"
#include "Timezone.h"
struct DayDataPoints {
uint8_t version;
int16_t hImport[24];
time_t lastMeterReadTime;
uint32_t activeImport;
uint32_t activeExport;
int16_t hExport[24];
}; // 112 bytes
struct MonthDataPoints {
uint8_t version;
int16_t dImport[31];
time_t lastMeterReadTime;
uint32_t activeImport;
uint32_t activeExport;
int16_t dExport[31];
}; // 141 bytes
class AmsDataStorage {
public:
AmsDataStorage(RemoteDebug*);
void setTimezone(Timezone*);
bool update(AmsData*);
int32_t getHourImport(uint8_t);
int32_t getHourExport(uint8_t);
int32_t getDayImport(uint8_t);
int32_t getDayExport(uint8_t);
bool load();
bool save();
DayDataPoints getDayData();
bool setDayData(DayDataPoints&);
MonthDataPoints getMonthData();
bool setMonthData(MonthDataPoints&);
bool isHappy();
bool isDayHappy();
bool isMonthHappy();
private:
Timezone* tz;
DayDataPoints day = {
0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
MonthDataPoints month = {
0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
RemoteDebug* debugger;
void setHourImport(uint8_t, int32_t);
void setHourExport(uint8_t, int32_t);
void setDayImport(uint8_t, int32_t);
void setDayExport(uint8_t, int32_t);
};
#endif

View File

@@ -1,16 +0,0 @@
#ifndef _AMSSTORAGE_H
#define _AMSSTORAGE_H
#define FILE_FIRMWARE "/firmware.bin"
#define FILE_MQTT_CA "/mqtt-ca.pem"
#define FILE_MQTT_CERT "/mqtt-cert.pem"
#define FILE_MQTT_KEY "/mqtt-key.pem"
#define FILE_DAYPLOT "/dayplot.bin"
#define FILE_MONTHPLOT "/monthplot.bin"
#define FILE_ENERGYACCOUNTING "/energyaccounting.bin"
#define FILE_CFG "/configfile.cfg"
#endif

View File

@@ -49,9 +49,9 @@ ADC_MODE(ADC_VCC);
#include "hexutils.h"
#include "HwTools.h"
#include "entsoe/EntsoeApi.h"
#include "EntsoeApi.h"
#include "web/AmsWebServer.h"
#include "AmsWebServer.h"
#include "AmsConfiguration.h"
#include "mqtt/AmsMqttHandler.h"

View File

@@ -1,430 +0,0 @@
#include "EnergyAccounting.h"
#include "LittleFS.h"
#include "AmsStorage.h"
#include "version.h"
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger) {
data.version = 1;
this->debugger = debugger;
}
void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config) {
this->ds = ds;
this->config = config;
this->currentThresholdIdx = 0;
}
void EnergyAccounting::setEapi(EntsoeApi *eapi) {
this->eapi = eapi;
}
EnergyAccountingConfig* EnergyAccounting::getConfig() {
return config;
}
void EnergyAccounting::setTimezone(Timezone* tz) {
this->tz = tz;
}
bool EnergyAccounting::update(AmsData* amsData) {
if(config == NULL) return false;
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return false;
if(tz == NULL) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Timezone is missing\n");
return false;
}
bool ret = false;
tmElements_t local;
breakTime(tz->toLocal(now), local);
if(!init) {
currentHour = local.Hour;
currentDay = local.Day;
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now);
if(!load()) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n");
data = { 4, local.Month,
0, 0, 0,
0, 0, // Peak 1
0, 0, // Peak 2
0, 0, // Peak 3
0, 0, // Peak 4
0, 0 // Peak 5
};
} else if(debugger->isActive(RemoteDebug::DEBUG)) {
for(uint8_t i = 0; i < 5; i++) {
debugger->printf("(EnergyAccounting) Peak hour from day %d: %d\n", data.peaks[i].day, data.peaks[i].value*10);
}
debugger->printf("(EnergyAccounting) Loaded cost yesterday: %d, this month: %d, last month: %d\n", data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth);
}
init = true;
}
if(!initPrice && eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lld\n", (int64_t) now);
calcDayCost();
}
if(local.Hour != currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New local hour %d\n", local.Hour);
tmElements_t oneHrAgo;
breakTime(now-3600, oneHrAgo);
uint16_t val = ds->getHourImport(oneHrAgo.Hour) / 10;
ret |= updateMax(val, local.Day);
if(local.Hour > 0) {
calcDayCost();
}
use = 0;
produce = 0;
costHour = 0;
currentHour = local.Hour;
if(local.Day != currentDay) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New day %d\n", local.Day);
data.costYesterday = costDay * 10;
data.costThisMonth += costDay;
costDay = 0;
currentDay = local.Day;
ret = true;
}
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;
for(uint8_t i = 0; i < 5; i++) {
data.peaks[i] = { 0, 0 };
}
data.month = local.Month;
currentThresholdIdx = 0;
ret = true;
}
}
unsigned long ms = this->lastUpdateMillis > amsData->getLastUpdateMillis() ? 0 : amsData->getLastUpdateMillis() - this->lastUpdateMillis;
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
lastUpdateMillis = amsData->getLastUpdateMillis();
if(kwhi > 0) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh import\n", kwhi);
use += kwhi;
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
float price = eapi->getValueForHour(0);
float cost = price * kwhi;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency());
costHour += cost;
costDay += cost;
}
}
if(kwhe > 0) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh export\n", kwhe);
produce += kwhe;
}
if(config != NULL) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) calculating threshold, currently at %d\n", currentThresholdIdx);
while(getMonthMax() > config->thresholds[currentThresholdIdx] && currentThresholdIdx < 10) currentThresholdIdx++;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) new threshold %d\n", currentThresholdIdx);
}
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 < currentHour; i++) {
float price = eapi->getValueForHour(i - currentHour);
if(price == ENTSOE_NO_VALUE) break;
breakTime(now - ((currentHour - i) * 3600), utc);
int16_t wh = ds->getHourImport(utc.Hour);
costDay += price * (wh / 1000.0);
}
initPrice = true;
}
}
double EnergyAccounting::getUseThisHour() {
return use;
}
double EnergyAccounting::getUseToday() {
float ret = 0.0;
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
tmElements_t utc;
for(int i = 0; i < currentHour; i++) {
breakTime(now - ((currentHour - i) * 3600), utc);
ret += ds->getHourImport(utc.Hour) / 1000.0;
}
return ret + getUseThisHour();
}
double EnergyAccounting::getUseThisMonth() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
float ret = 0;
for(int i = 0; i < currentDay; i++) {
ret += ds->getDayImport(i) / 1000.0;
}
return ret + getUseToday();
}
double EnergyAccounting::getProducedThisHour() {
return produce;
}
double EnergyAccounting::getProducedToday() {
float ret = 0.0;
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
tmElements_t utc;
for(int i = 0; i < currentHour; i++) {
breakTime(now - ((currentHour - i) * 3600), utc);
ret += ds->getHourExport(utc.Hour) / 1000.0;
}
return ret + getProducedThisHour();
}
double EnergyAccounting::getProducedThisMonth() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
float ret = 0;
for(int i = 0; i < currentDay; i++) {
ret += ds->getDayExport(i) / 1000.0;
}
return ret + getProducedToday();
}
double EnergyAccounting::getCostThisHour() {
return costHour;
}
double EnergyAccounting::getCostToday() {
return costDay;
}
double EnergyAccounting::getCostYesterday() {
return data.costYesterday / 10.0;
}
double EnergyAccounting::getCostThisMonth() {
return data.costThisMonth + getCostToday();
}
uint16_t EnergyAccounting::getCostLastMonth() {
return data.costLastMonth;
}
uint8_t EnergyAccounting::getCurrentThreshold() {
if(config == NULL)
return 0;
return config->thresholds[currentThresholdIdx];
}
float EnergyAccounting::getMonthMax() {
uint8_t count = 0;
uint32_t maxHour = 0.0;
bool included[5] = { false, false, false, false, false };
while(count < config->hours) {
uint8_t maxIdx = 0;
uint16_t maxVal = 0;
for(uint8_t i = 0; i < 5; i++) {
if(included[i]) continue;
if(data.peaks[i].value > maxVal) {
maxVal = data.peaks[i].value;
maxIdx = i;
}
}
included[maxIdx] = true;
count++;
}
for(uint8_t i = 0; i < 5; i++) {
if(!included[i]) continue;
if(data.peaks[i].day > 0) {
maxHour += data.peaks[i].value;
}
}
return maxHour > 0 ? maxHour / count / 100.0 : 0.0;
}
float EnergyAccounting::getPeak(uint8_t num) {
if(num < 1 || num > 5) return 0.0;
uint8_t count = 0;
bool included[5] = { false, false, false, false, false };
while(count < config->hours) {
uint8_t maxIdx = 0;
uint16_t maxVal = 0;
for(uint8_t i = 0; i < 5; i++) {
if(included[i]) continue;
if(data.peaks[i].value > maxVal) {
maxVal = data.peaks[i].value;
maxIdx = i;
}
}
included[maxIdx] = true;
count++;
}
uint8_t pos = 0;
for(uint8_t i = 0; i < 5; i++) {
if(!included[i]) continue;
pos++;
if(pos == num) {
return data.peaks[i].value / 100.0;
}
}
return 0.0;
}
bool EnergyAccounting::load() {
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());
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Data version %d\n", buf[0]);
if(buf[0] == 4) {
EnergyAccountingData* data = (EnergyAccountingData*) buf;
memcpy(&this->data, data, sizeof(this->data));
ret = true;
} else if(buf[0] == 3) {
EnergyAccountingData* data = (EnergyAccountingData*) buf;
this->data = { 4, data->month,
(uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100),
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 {
data = { 4, 0,
0, 0, 0,
0, 0, // Peak 1
0, 0, // Peak 2
0, 0, // Peak 3
0, 0, // Peak 4
0, 0 // Peak 5
};
if(buf[0] == 2) {
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);
uint8_t b = 0;
for(uint8_t i = sizeof(this->data); i < file.size(); i+=2) {
this->data.peaks[b].day = b;
memcpy(&this->data.peaks[b].value, buf+i, 2);
b++;
if(b >= config->hours) 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("(EnergyAccounting) Unknown version\n");
ret = false;
}
}
file.close();
} else {
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf("(EnergyAccounting) File not found\n");
}
LittleFS.end();
return ret;
}
bool EnergyAccounting::save() {
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(uint8_t i = 0; i < sizeof(buf); i++) {
file.write(buf[i]);
}
file.close();
}
LittleFS.end();
return true;
}
EnergyAccountingData EnergyAccounting::getData() {
return this->data;
}
void EnergyAccounting::setData(EnergyAccountingData& data) {
this->data = data;
}
bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
for(uint8_t i = 0; i < 5; i++) {
if(data.peaks[i].day == day || data.peaks[i].day == 0) {
if(val > data.peaks[i].value) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Adding new max %d for day %d which is larger than %d\n", val*10, day, data.peaks[i].value*10);
data.peaks[i].day = day;
data.peaks[i].value = val;
return true;
}
return false;
}
}
uint16_t test = 0;
uint8_t idx = 255;
for(uint8_t i = 0; i < 5; i++) {
if(val > data.peaks[i].value) {
if(test < data.peaks[i].value) {
test = data.peaks[i].value;
idx = i;
}
}
}
if(idx < 5) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Adding new max %d for day %d\n", val*10, day);
data.peaks[idx].value = val;
data.peaks[idx].day = day;
return true;
}
return false;
}

View File

@@ -1,82 +0,0 @@
#ifndef _ENERGYACCOUNTING_H
#define _ENERGYACCOUNTING_H
#include "Arduino.h"
#include "AmsData.h"
#include "AmsDataStorage.h"
#include "entsoe/EntsoeApi.h"
struct EnergyAccountingPeak {
uint8_t day;
uint16_t value;
};
struct EnergyAccountingData {
uint8_t version;
uint8_t month;
uint16_t costYesterday;
uint16_t costThisMonth;
uint16_t costLastMonth;
EnergyAccountingPeak peaks[5];
};
struct EnergyAccountingData1 {
uint8_t version;
uint8_t month;
uint16_t maxHour;
uint16_t costYesterday;
uint16_t costThisMonth;
uint16_t costLastMonth;
};
class EnergyAccounting {
public:
EnergyAccounting(RemoteDebug*);
void setup(AmsDataStorage *ds, EnergyAccountingConfig *config);
void setEapi(EntsoeApi *eapi);
void setTimezone(Timezone*);
EnergyAccountingConfig* getConfig();
bool update(AmsData* amsData);
bool load();
bool save();
double getUseThisHour();
double getUseToday();
double getUseThisMonth();
double getProducedThisHour();
double getProducedToday();
double getProducedThisMonth();
double getCostThisHour();
double getCostToday();
double getCostYesterday();
double getCostThisMonth();
uint16_t getCostLastMonth();
float getMonthMax();
uint8_t getCurrentThreshold();
float getPeak(uint8_t);
EnergyAccountingData getData();
void setData(EnergyAccountingData&);
private:
RemoteDebug* debugger = NULL;
unsigned long lastUpdateMillis = 0;
bool init = false, initPrice = false;
AmsDataStorage *ds = NULL;
EntsoeApi *eapi = NULL;
EnergyAccountingConfig *config = NULL;
Timezone *tz = NULL;
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
double use, costHour, costDay;
double produce;
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
void calcDayCost();
bool updateMax(uint16_t val, uint8_t day);
};
#endif

View File

@@ -1,432 +0,0 @@
#include "HwTools.h"
void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
this->config = config;
this->amsConf = amsConf;
this->tempSensorInit = false;
if(sensorApi != NULL)
delete sensorApi;
if(oneWire != NULL)
delete oneWire;
if(config->tempSensorPin > 0 && config->tempSensorPin < 40) {
pinMode(config->tempSensorPin, INPUT);
} else {
config->tempSensorPin = 0xFF;
}
#if defined(CONFIG_IDF_TARGET_ESP32S2)
analogReadResolution(13);
analogRange = 8192;
analogSetAttenuation(ADC_11db);
#elif defined(ESP32)
analogReadResolution(12);
analogRange = 4096;
analogSetAttenuation(ADC_6db);
#endif
if(config->vccPin > 0 && config->vccPin < 40) {
#if defined(CONFIG_IDF_TARGET_ESP32S2)
getAdcChannel(config->vccPin, voltAdc);
if(voltAdc.unit != 0xFF) {
if(voltAdc.unit == ADC_UNIT_1) {
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_13, 1100, voltAdcChar);
adc1_config_channel_atten((adc1_channel_t) voltAdc.channel, ADC_ATTEN_DB_11);
} else if(voltAdc.unit == ADC_UNIT_2) {
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_13, 1100, voltAdcChar);
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_11);
}
}
#elif defined(ESP32)
getAdcChannel(config->vccPin, voltAdc);
if(voltAdc.unit != 0xFF) {
if(voltAdc.unit == ADC_UNIT_1) {
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar);
adc1_config_channel_atten((adc1_channel_t) voltAdc.channel, ADC_ATTEN_DB_6);
} else if(voltAdc.unit == ADC_UNIT_2) {
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar);
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_6);
}
}
#else
pinMode(config->vccPin, INPUT);
#endif
} else {
voltAdc.unit = 0xFF;
voltAdc.channel = 0xFF;
config->vccPin = 0xFF;
}
if(config->tempAnalogSensorPin > 0 && config->tempAnalogSensorPin < 40) {
pinMode(config->tempAnalogSensorPin, INPUT);
} else {
config->tempAnalogSensorPin = 0xFF;
}
if(config->ledPin > 0 && config->ledPin < 40) {
pinMode(config->ledPin, OUTPUT);
ledOff(LED_INTERNAL);
} else {
config->ledPin = 0xFF;
}
if(config->ledPinRed > 0 && config->ledPinRed < 40) {
pinMode(config->ledPinRed, OUTPUT);
ledOff(LED_RED);
} else {
config->ledPinRed = 0xFF;
}
if(config->ledPinGreen > 0 && config->ledPinGreen < 40) {
pinMode(config->ledPinGreen, OUTPUT);
ledOff(LED_GREEN);
} else {
config->ledPinGreen = 0xFF;
}
if(config->ledPinBlue > 0 && config->ledPinBlue < 40) {
pinMode(config->ledPinBlue, OUTPUT);
ledOff(LED_BLUE);
} else {
config->ledPinBlue = 0xFF;
}
}
void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
config.unit = 0xFF;
config.channel = 0xFF;
#if defined(ESP32)
switch(pin) {
case ADC1_CHANNEL_0_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_0;
break;
case ADC1_CHANNEL_1_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_1;
break;
case ADC1_CHANNEL_2_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_2;
break;
case ADC1_CHANNEL_3_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_3;
break;
case ADC1_CHANNEL_4_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_4;
break;
case ADC1_CHANNEL_5_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_5;
break;
case ADC1_CHANNEL_6_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_6;
break;
case ADC1_CHANNEL_7_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_7;
break;
#if defined(CONFIG_IDF_TARGET_ESP32S2)
case ADC1_CHANNEL_8_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_8;
break;
case ADC1_CHANNEL_9_GPIO_NUM:
config.unit = ADC_UNIT_1;
config.channel = ADC1_CHANNEL_9;
break;
#endif
case ADC2_CHANNEL_0_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_0;
break;
case ADC2_CHANNEL_1_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_1;
break;
case ADC2_CHANNEL_2_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_2;
break;
case ADC2_CHANNEL_3_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_3;
break;
case ADC2_CHANNEL_4_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_4;
break;
case ADC2_CHANNEL_5_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_5;
break;
case ADC2_CHANNEL_6_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_6;
break;
case ADC2_CHANNEL_7_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_7;
break;
case ADC2_CHANNEL_8_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_8;
break;
case ADC2_CHANNEL_9_GPIO_NUM:
config.unit = ADC_UNIT_2;
config.channel = ADC2_CHANNEL_9;
break;
}
#endif
}
double HwTools::getVcc() {
double volts = 0.0;
if(config->vccPin != 0xFF) {
#if defined(ESP32)
if(voltAdc.unit != 0xFF) {
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
if(voltAdc.unit == ADC_UNIT_1) {
x += adc1_get_raw((adc1_channel_t) voltAdc.channel);
} else if(voltAdc.unit == ADC_UNIT_2) {
int v = 0;
#if defined(CONFIG_IDF_TARGET_ESP32S2)
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_13, &v);
#elif defined(CONFIG_IDF_TARGET_ESP32)
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_12, &v);
#endif
x += v;
}
}
x = x / 10;
uint32_t voltage = esp_adc_cal_raw_to_voltage(x, voltAdcChar);
volts = voltage / 1000.0;
} else {
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
x += analogRead(config->vccPin);
}
volts = (x * 3.3) / 10.0 / analogRange;
}
#else
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
x += analogRead(config->vccPin);
}
volts = (x * 3.3) / 10.0 / analogRange;
#endif
} else {
#if defined(ESP8266)
volts = ESP.getVcc() / 1024.0;
#endif
}
if(volts == 0.0) return 0.0;
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
volts *= ((double) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
}
float vccOffset = config->vccOffset / 100.0;
float vccMultiplier = config->vccMultiplier / 1000.0;
return vccOffset + (volts > 0.0 ? volts * vccMultiplier : 0.0);
}
uint8_t HwTools::getTempSensorCount() {
return sensorCount;
}
TempSensorData* HwTools::getTempSensorData(uint8_t i) {
if(i < sensorCount) {
return tempSensors[i];
}
return NULL;
}
bool HwTools::updateTemperatures() {
if(config->tempSensorPin != 0xFF) {
if(!tempSensorInit) {
oneWire = new OneWire(config->tempSensorPin);
sensorApi = new DallasTemperature(this->oneWire);
sensorApi->begin();
delay(100);
tempSensorInit = true;
DeviceAddress addr;
sensorApi->requestTemperatures();
int c = sensorApi->getDeviceCount();
if(this->tempSensors != NULL) {
delete this->tempSensors;
}
this->tempSensors = new TempSensorData*[c];
for(int i = 0; i < c; i++) {
bool found = false;
sensorApi->getAddress(addr, i);
float t = sensorApi->getTempC(addr);
for(int x = 0; x < sensorCount; x++) {
TempSensorData *data = tempSensors[x];
if(isSensorAddressEqual(data->address, addr)) {
found = true;
data->lastRead = t;
if(t > -85) {
data->changed = data->lastValidRead != t;
data->lastValidRead = t;
}
}
}
if(!found) {
TempSensorData *data = new TempSensorData();
memcpy(data->address, addr, 8);
data->lastRead = t;
if(t > -85) {
data->changed = data->lastValidRead != t;
data->lastValidRead = t;
}
tempSensors[sensorCount++] = data;
}
delay(10);
}
} else {
if(sensorCount > 0) {
sensorApi->requestTemperatures();
for(int x = 0; x < sensorCount; x++) {
TempSensorData *data = tempSensors[x];
float t = sensorApi->getTempC(data->address);
data->lastRead = t;
if(t > -85) {
data->changed = data->lastValidRead != t;
data->lastValidRead = t;
}
}
}
}
return true;
}
return false;
}
bool HwTools::isSensorAddressEqual(uint8_t a[8], uint8_t b[8]) {
for(int i = 0; i < 8; i++) {
if(a[i] != b[i]) return false;
}
return true;
}
double HwTools::getTemperature() {
uint8_t c = 0;
double ret = 0;
double analogTemp = getTemperatureAnalog();
if(analogTemp != DEVICE_DISCONNECTED_C) {
ret += analogTemp;
c++;
}
for(int x = 0; x < sensorCount; x++) {
TempSensorData data = *tempSensors[x];
TempSensorConfig* conf = amsConf->getTempSensorConfig(data.address);
if((conf == NULL || conf->common) && data.lastValidRead > -85) {
ret += data.lastValidRead;
c++;
}
}
return c == 0 ? DEVICE_DISCONNECTED_C : ret/c;
}
double HwTools::getTemperatureAnalog() {
if(config->tempAnalogSensorPin != 0xFF) {
float adcCalibrationFactor = 1.06587;
int volts = ((double) analogRead(config->tempAnalogSensorPin) / analogRange) * 3.3;
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
}
return DEVICE_DISCONNECTED_C;
}
int HwTools::getWifiRssi() {
int rssi = WiFi.RSSI();
return isnan(rssi) ? -100.0 : rssi;
}
bool HwTools::ledOn(uint8_t color) {
if(color == LED_INTERNAL) {
return writeLedPin(color, config->ledInverted ? LOW : HIGH);
} else {
return writeLedPin(color, config->ledRgbInverted ? LOW : HIGH);
}
}
bool HwTools::ledOff(uint8_t color) {
if(color == LED_INTERNAL) {
return writeLedPin(color, config->ledInverted ? HIGH : LOW);
} else {
return writeLedPin(color, config->ledRgbInverted ? HIGH : LOW);
}
}
bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
for(int i = 0; i < blink; i++) {
if(!ledOn(color)) return false;
delay(50);
ledOff(color);
if(i != blink)
delay(50);
}
return true;
}
bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
switch(color) {
case LED_INTERNAL: {
if(config->ledPin != 0xFF) {
digitalWrite(config->ledPin, state);
return true;
} else {
return false;
}
break;
}
case LED_RED: {
if(config->ledPinRed != 0xFF) {
digitalWrite(config->ledPinRed, state);
return true;
} else {
return false;
}
break;
}
case LED_GREEN: {
if(config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinGreen, state);
return true;
} else {
return false;
}
break;
}
case LED_BLUE: {
if(config->ledPinBlue != 0xFF) {
digitalWrite(config->ledPinBlue, state);
return true;
} else {
return false;
}
break;
}
case LED_YELLOW: {
if(config->ledPinRed != 0xFF && config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinRed, state);
digitalWrite(config->ledPinGreen, state);
return true;
} else {
return false;
}
break;
}
}
return false;
}

View File

@@ -1,72 +0,0 @@
#ifndef _HWTOOLS_H
#define _HWTOOLS_H
#include "Arduino.h"
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>
#include <soc/adc_channel.h>
#endif
#include <DallasTemperature.h>
#include <OneWire.h>
#include "AmsConfiguration.h"
#define LED_INTERNAL 0
#define LED_RED 1
#define LED_GREEN 2
#define LED_BLUE 3
#define LED_YELLOW 4
struct TempSensorData {
uint8_t address[8];
float lastRead;
float lastValidRead;
bool changed;
};
struct AdcConfig {
uint8_t unit;
uint8_t channel;
};
class HwTools {
public:
void setup(GpioConfig*, AmsConfiguration*);
double getVcc();
uint8_t getTempSensorCount();
TempSensorData* getTempSensorData(uint8_t);
bool updateTemperatures();
double getTemperature();
double getTemperatureAnalog();
double getTemperature(uint8_t address[8]);
int getWifiRssi();
bool ledOn(uint8_t color);
bool ledOff(uint8_t color);
bool ledBlink(uint8_t color, uint8_t blink);
HwTools() {};
private:
uint16_t analogRange = 1024;
AdcConfig voltAdc, tempAdc;
#if defined(ESP32)
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
#endif
GpioConfig* config;
AmsConfiguration* amsConf;
bool tempSensorInit;
OneWire *oneWire = NULL;
DallasTemperature *sensorApi = NULL;
uint8_t sensorCount = 0;
TempSensorData** tempSensors = NULL;
bool writeLedPin(uint8_t color, uint8_t state);
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
void getAdcChannel(uint8_t pin, AdcConfig&);
};
#endif

View File

@@ -1,8 +0,0 @@
#include "Uptime.h"
uint64_t millis64() {
uint32_t new_low32 = millis();
if (new_low32 < _uptime_last_value) _uptime_rollovers++;
_uptime_last_value = new_low32;
return (uint64_t) _uptime_rollovers << 32 | _uptime_last_value;
}

View File

@@ -1,10 +0,0 @@
#ifndef _UPTIME_H
#define _UPTIME_H
#include "Arduino.h"
static uint32_t _uptime_last_value = 0;
static uint32_t _uptime_rollovers = 0;
uint64_t millis64();
#endif

View File

@@ -1,77 +0,0 @@
#include "DnbCurrParser.h"
#include "Arduino.h"
float DnbCurrParser::getValue() {
return value;
}
int DnbCurrParser::available() {
return 0;
}
int DnbCurrParser::read() {
return 0;
}
int DnbCurrParser::peek() {
return 0;
}
void DnbCurrParser::flush() {
}
size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
for(int i = 0; i < size; i++) {
write(buffer[i]);
}
return size;
}
size_t DnbCurrParser::write(uint8_t byte) {
if(pos >= 128) pos = 0;
if(pos == 0) {
if(byte == '<') {
buf[pos++] = byte;
}
} else if(byte == '>') {
buf[pos++] = byte;
buf[pos++] = '\0';
if(strncmp(buf, "<Series", 7) == 0) {
for(int i = 0; i < pos; i++) {
if(strncmp(buf+i, "UNIT_MULT=\"", 11) == 0) {
pos = i + 11;
break;
}
}
for(int i = 0; i < 16; i++) {
uint8_t b = buf[pos+i];
if(b == '"') {
buf[pos+i] = '\0';
break;
}
}
scale = String(buf+pos).toInt();
} else if(strncmp(buf, "<Obs", 4) == 0) {
for(int i = 0; i < pos; i++) {
if(strncmp(buf+i, "OBS_VALUE=\"", 11) == 0) {
pos = i + 11;
break;
}
}
for(int i = 0; i < 16; i++) {
uint8_t b = buf[pos+i];
if(b == '"') {
buf[pos+i] = '\0';
break;
}
}
value = String(buf+pos).toFloat() / pow(10, scale);
}
pos = 0;
} else {
buf[pos++] = byte;
}
return 1;
}

View File

@@ -1,26 +0,0 @@
#ifndef _DNBCURRPARSER_H
#define _DNBCURRPARSER_H
#include "Stream.h"
class DnbCurrParser: public Stream {
public:
float getValue();
int available();
int read();
int peek();
void flush();
size_t write(const uint8_t *buffer, size_t size);
size_t write(uint8_t);
private:
uint8_t scale = 0;
float value = 1.0;
char buf[128];
uint8_t pos = 0;
uint8_t mode = 0;
};
#endif

View File

@@ -1,104 +0,0 @@
#include "EntsoeA44Parser.h"
#include "HardwareSerial.h"
EntsoeA44Parser::EntsoeA44Parser() {
for(int i = 0; i < 24; i++) points[i] = ENTSOE_NO_VALUE;
}
char* EntsoeA44Parser::getCurrency() {
return currency;
}
char* EntsoeA44Parser::getMeasurementUnit() {
return measurementUnit;
}
float EntsoeA44Parser::getPoint(uint8_t position) {
if(position >= 24) return ENTSOE_NO_VALUE;
return points[position];
}
int EntsoeA44Parser::available() {
return 0;
}
int EntsoeA44Parser::read() {
return 0;
}
int EntsoeA44Parser::peek() {
return 0;
}
void EntsoeA44Parser::flush() {
}
size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
for(int i = 0; i < size; i++) {
write(buffer[i]);
}
return size;
}
size_t EntsoeA44Parser::write(uint8_t byte) {
if(pos >= 64) pos = 0;
if(docPos == DOCPOS_CURRENCY) {
buf[pos++] = byte;
if(pos == 3) {
buf[pos++] = '\0';
memcpy(currency, buf, pos);
docPos = DOCPOS_SEEK;
pos = 0;
}
} else if(docPos == DOCPOS_MEASUREMENTUNIT) {
buf[pos++] = byte;
if(pos == 3) {
buf[pos++] = '\0';
memcpy(measurementUnit, buf, pos);
docPos = DOCPOS_SEEK;
pos = 0;
}
} else if(docPos == DOCPOS_POSITION) {
if(byte == '<') {
buf[pos] = '\0';
pointNum = String(buf).toInt() - 1;
docPos = DOCPOS_SEEK;
pos = 0;
} else {
buf[pos++] = byte;
}
} else if(docPos == DOCPOS_AMOUNT) {
if(byte == '<') {
buf[pos] = '\0';
points[pointNum] = String(buf).toFloat();
docPos = DOCPOS_SEEK;
pos = 0;
} else {
buf[pos++] = byte;
}
} else {
if(pos == 0) {
if(byte == '<') {
buf[pos++] = byte;
}
} else if(byte == '>') {
buf[pos++] = byte;
buf[pos] = '\0';
if(strcmp(buf, "<currency_Unit.name>") == 0) {
docPos = DOCPOS_CURRENCY;
} else if(strcmp(buf, "<price_Measure_Unit.name>") == 0) {
docPos = DOCPOS_MEASUREMENTUNIT;
} else if(strcmp(buf, "<position>") == 0) {
docPos = DOCPOS_POSITION;
pointNum = 0xFF;
} else if(strcmp(buf, "<price.amount>") == 0) {
docPos = DOCPOS_AMOUNT;
}
pos = 0;
} else {
buf[pos++] = byte;
}
}
return 1;
}

View File

@@ -1,40 +0,0 @@
#ifndef _ENTSOEA44PARSER_H
#define _ENTSOEA44PARSER_H
#include "Stream.h"
#define DOCPOS_SEEK 0
#define DOCPOS_CURRENCY 1
#define DOCPOS_MEASUREMENTUNIT 2
#define DOCPOS_POSITION 3
#define DOCPOS_AMOUNT 4
#define ENTSOE_NO_VALUE -127
class EntsoeA44Parser: public Stream {
public:
EntsoeA44Parser();
char* getCurrency();
char* getMeasurementUnit();
float getPoint(uint8_t position);
int available();
int read();
int peek();
void flush();
size_t write(const uint8_t *buffer, size_t size);
size_t write(uint8_t);
private:
char currency[4];
char measurementUnit[4];
float points[24];
char buf[64];
uint8_t pos = 0;
uint8_t docPos = 0;
uint8_t pointNum = 0;
};
#endif

View File

@@ -1,297 +0,0 @@
#include "EntsoeApi.h"
#include <EEPROM.h>
#include "Uptime.h"
#include "TimeLib.h"
#include "DnbCurrParser.h"
#include "version.h"
#if defined(ESP32)
#include <esp_task_wdt.h>
#endif
EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
this->buf = (char*) malloc(BufferSize);
debugger = Debug;
// Entso-E uses CET/CEST
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
tz = new Timezone(CEST, CET);
tomorrowFetchMillis = 36000000 + (random(1800) * 1000); // Random between 13:30 and 14:00
}
void EntsoeApi::setup(EntsoeConfig& config) {
if(this->config == NULL) {
this->config = new EntsoeConfig();
}
memcpy(this->config, &config, sizeof(config));
lastTodayFetch = lastTomorrowFetch = lastCurrencyFetch = 0;
if(today != NULL) delete today;
if(tomorrow != NULL) delete tomorrow;
today = tomorrow = NULL;
}
char* EntsoeApi::getToken() {
return this->config->token;
}
char* EntsoeApi::getCurrency() {
return this->config->currency;
}
float EntsoeApi::getValueForHour(int8_t hour) {
time_t cur = time(nullptr);
return getValueForHour(cur, hour);
}
float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
tmElements_t tm;
if(tz != NULL)
cur = tz->toLocal(cur);
breakTime(cur, tm);
int pos = tm.Hour + hour;
if(pos >= 48)
return ENTSOE_NO_VALUE;
double value = ENTSOE_NO_VALUE;
double multiplier = config->multiplier / 1000.0;
if(pos > 23) {
if(tomorrow == NULL)
return ENTSOE_NO_VALUE;
value = tomorrow->getPoint(pos-24);
if(value != ENTSOE_NO_VALUE && strcmp(tomorrow->getMeasurementUnit(), "MWH") == 0) {
multiplier *= 0.001;
} else {
return ENTSOE_NO_VALUE;
}
float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency);
if(mult == 0) return ENTSOE_NO_VALUE;
multiplier *= mult;
} else if(pos >= 0) {
if(today == NULL)
return ENTSOE_NO_VALUE;
value = today->getPoint(pos);
if(value != ENTSOE_NO_VALUE && strcmp(today->getMeasurementUnit(), "MWH") == 0) {
multiplier *= 0.001;
} else {
return ENTSOE_NO_VALUE;
}
float mult = getCurrencyMultiplier(today->getCurrency(), config->currency);
if(mult == 0) return ENTSOE_NO_VALUE;
multiplier *= mult;
}
return value * multiplier;
}
bool EntsoeApi::loop() {
if(strlen(getToken()) == 0)
return false;
uint64_t now = millis64();
if(now < 10000) return false; // Grace period
time_t t = time(nullptr);
if(t < BUILD_EPOCH) return false;
bool ret = false;
tmElements_t tm;
breakTime(tz->toLocal(t), tm);
if(currentHour != tm.Hour) {
currentHour = tm.Hour;
ret = today != NULL; // Only trigger MQTT publish if we have todays prices.
}
if(midnightMillis == 0) {
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
midnightMillis = now + (SECS_PER_DAY * 1000) - curDayMillis;
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %lu\n", midnightMillis);
currentDay = tm.Day;
return false;
} else if(now > midnightMillis && currentDay != tm.Day) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
if(today != NULL) delete today;
if(tomorrow != NULL) {
today = tomorrow;
tomorrow = NULL;
}
currentDay = tm.Day;
midnightMillis = 0; // Force new midnight millis calculation
return true;
} else {
breakTime(t, tm); // Break UTC to find UTC midnight
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
lastTodayFetch = now;
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // UTC midnight
time_t e2 = e1 + SECS_PER_DAY;
tmElements_t d1, d2;
breakTime(tz->toUTC(e1), d1); // To get day and hour for CET/CEST at UTC midnight
breakTime(tz->toUTC(e2), d2);
snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
"https://transparency.entsoe.eu/api", getToken(),
d1.Year+1970, d1.Month, d1.Day, d1.Hour, 00,
d2.Year+1970, d2.Month, d2.Day, d2.Hour, 00,
config->area, config->area);
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for today\n");
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(buf, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
today = a44;
return true;
} else if(a44 != NULL) {
delete a44;
today = NULL;
return false;
}
}
// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
// fetch 1 hr after that (with some random delay) and retry every 15 minutes
if(tomorrow == NULL
&& midnightMillis - now < tomorrowFetchMillis
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)
) {
lastTomorrowFetch = now;
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second + (SECS_PER_DAY);
time_t e2 = e1 + SECS_PER_DAY;
tmElements_t d1, d2;
breakTime(tz->toUTC(e1), d1);
breakTime(tz->toUTC(e2), d2);
snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
"https://transparency.entsoe.eu/api", getToken(),
d1.Year+1970, d1.Month, d1.Day, d1.Hour, 00,
d2.Year+1970, d2.Month, d2.Day, d2.Hour, 00,
config->area, config->area);
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for tomorrow\n");
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(buf, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
tomorrow = a44;
return true;
} else if(a44 != NULL) {
delete a44;
tomorrow = NULL;
return false;
}
}
}
return ret;
}
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
HTTPClient https;
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
https.setReuse(false);
https.setTimeout(50000);
https.setUserAgent("ams2mqtt/" + String(VERSION));
#if defined(ESP32)
if(https.begin(url)) {
printD("Connection established");
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
int status = https.GET();
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
if(status == HTTP_CODE_OK) {
printD("Receiving data");
https.writeToStream(doc);
https.end();
return true;
} else {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Communication error, returned status: %d\n", status);
printE(https.errorToString(status));
printD(https.getString());
https.end();
return false;
}
} else {
return false;
}
#endif
return false;
}
float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
if(strcmp(from, to) == 0)
return 1.00;
uint64_t now = millis64();
if(now > lastCurrencyFetch && (lastCurrencyFetch == 0 || (now - lastCurrencyFetch) > 60000)) {
lastCurrencyFetch = now;
DnbCurrParser p;
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
snprintf(buf, BufferSize, "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", from);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", from);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
if(retrieve(buf, &p)) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
currencyMultiplier = p.getValue();
if(strncmp(to, "NOK", 3) != 0) {
snprintf(buf, BufferSize, "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", to);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", to);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
if(retrieve(buf, &p)) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
currencyMultiplier /= p.getValue();
} else {
return 0;
}
}
} else {
return 0;
}
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Resulting currency multiplier: %.4f\n", currencyMultiplier);
lastCurrencyFetch = midnightMillis;
}
return currencyMultiplier;
}
void EntsoeApi::printD(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}
void EntsoeApi::printE(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}

View File

@@ -1,57 +0,0 @@
#ifndef _ENTSOEAPI_H
#define _ENTSOEAPI_H
#include "TimeLib.h"
#include "Timezone.h"
#include "RemoteDebug.h"
#include "EntsoeA44Parser.h"
#include "AmsConfiguration.h"
#if defined(ESP8266)
#include <ESP8266HTTPClient.h>
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
#include <HTTPClient.h>
#else
#warning "Unsupported board type"
#endif
#define SSL_BUF_SIZE 512
class EntsoeApi {
public:
EntsoeApi(RemoteDebug*);
void setup(EntsoeConfig&);
bool loop();
char* getToken();
char* getCurrency();
float getValueForHour(int8_t);
float getValueForHour(time_t, int8_t);
private:
RemoteDebug* debugger;
EntsoeConfig* config = NULL;
uint8_t currentDay = 0, currentHour = 0;
uint32_t tomorrowFetchMillis = 36000000; // Number of ms before midnight. Default fetch 10hrs before midnight (14:00 CE(S)T)
uint64_t midnightMillis = 0;
uint64_t lastTodayFetch = 0;
uint64_t lastTomorrowFetch = 0;
uint64_t lastCurrencyFetch = 0;
EntsoeA44Parser* today = NULL;
EntsoeA44Parser* tomorrow = NULL;
Timezone* tz = NULL;
static const uint16_t BufferSize = 256;
char* buf;
float currencyMultiplier = 0;
bool retrieve(const char* url, Stream* doc);
float getCurrencyMultiplier(const char* from, const char* to);
void printD(String fmt, ...);
void printE(String fmt, ...);
};
#endif

View File

@@ -1,23 +0,0 @@
#include "hexutils.h"
String toHex(uint8_t* in) {
return toHex(in, sizeof(in)*2);
}
String toHex(uint8_t* in, uint16_t size) {
String hex;
for(int i = 0; i < size; i++) {
if(in[i] < 0x10) {
hex += '0';
}
hex += String(in[i], HEX);
}
hex.toUpperCase();
return hex;
}
void fromHex(uint8_t *out, String in, uint16_t size) {
for(int i = 0; i < size*2; i += 2) {
out[i/2] = strtol(in.substring(i, i+2).c_str(), 0, 16);
}
}

View File

@@ -1,11 +0,0 @@
#ifndef _HEXUTILS_H
#define _HEXUTILS_H
#include <stdint.h>
#include "Arduino.h"
String toHex(uint8_t* in);
String toHex(uint8_t* in, uint16_t size);
void fromHex(uint8_t *out, String in, uint16_t size);
#endif

View File

@@ -7,7 +7,7 @@
#include "AmsConfiguration.h"
#include "EnergyAccounting.h"
#include "HwTools.h"
#include "entsoe/EntsoeApi.h"
#include "EntsoeApi.h"
class AmsMqttHandler {
public:

View File

@@ -1,5 +1,5 @@
#include "DomoticzMqttHandler.h"
#include "web/root/domoticz_json.h"
#include "root/domoticz_json.h"
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
bool ret = false;

View File

@@ -3,12 +3,12 @@
#include "hexutils.h"
#include "Uptime.h"
#include "version.h"
#include "web/root/ha1_json.h"
#include "web/root/ha2_json.h"
#include "web/root/ha3_json.h"
#include "web/root/jsonsys_json.h"
#include "web/root/jsonprices_json.h"
#include "web/root/hadiscover_json.h"
#include "root/ha1_json.h"
#include "root/ha2_json.h"
#include "root/ha3_json.h"
#include "root/jsonsys_json.h"
#include "root/jsonprices_json.h"
#include "root/hadiscover_json.h"
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected())

View File

@@ -2,12 +2,12 @@
#include "version.h"
#include "hexutils.h"
#include "Uptime.h"
#include "web/root/json1_json.h"
#include "web/root/json2_json.h"
#include "web/root/json3_json.h"
#include "web/root/json4_json.h"
#include "web/root/jsonsys_json.h"
#include "web/root/jsonprices_json.h"
#include "root/json1_json.h"
#include "root/json2_json.h"
#include "root/json3_json.h"
#include "root/json4_json.h"
#include "root/jsonsys_json.h"
#include "root/jsonprices_json.h"
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected())

View File

@@ -1,14 +0,0 @@
static const char HEADER_CACHE_CONTROL[] PROGMEM = "Cache-Control";
static const char HEADER_PRAGMA[] PROGMEM = "Pragma";
static const char HEADER_EXPIRES[] PROGMEM = "Expires";
static const char HEADER_AUTHENTICATE[] PROGMEM = "WWW-Authenticate";
static const char CACHE_CONTROL_NO_CACHE[] PROGMEM = "no-cache, no-store, must-revalidate";
static const char CACHE_1HR[] PROGMEM = "public, max-age=3600";
static const char PRAGMA_NO_CACHE[] PROGMEM = "no-cache";
static const char EXPIRES_OFF[] PROGMEM = "-1";
static const char AUTHENTICATE_BASIC[] PROGMEM = "Basic realm=\"Secure Area\"";
static const char MIME_PLAIN[] PROGMEM = "text/plain";
static const char MIME_HTML[] PROGMEM = "text/html";
static const char MIME_JSON[] PROGMEM = "application/json";

File diff suppressed because it is too large Load Diff

View File

@@ -1,140 +0,0 @@
#ifndef _AMSWEBSERVER_h
#define _AMSWEBSERVER_h
#define BOOTSTRAP_URL "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css"
#include "Arduino.h"
#include <MQTT.h>
#include "AmsConfiguration.h"
#include "HwTools.h"
#include "AmsData.h"
#include "AmsStorage.h"
#include "AmsDataStorage.h"
#include "EnergyAccounting.h"
#include "Uptime.h"
#include "RemoteDebug.h"
#include "entsoe/EntsoeApi.h"
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#else
#warning "Unsupported board type"
#endif
#include "LittleFS.h"
class AmsWebServer {
public:
AmsWebServer(uint8_t* buf, RemoteDebug* Debug, HwTools* hw);
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*);
void loop();
void setMqtt(MQTTClient* mqtt);
void setTimezone(Timezone* tz);
void setMqttEnabled(bool);
void setEntsoeApi(EntsoeApi* eapi);
private:
RemoteDebug* debugger;
bool mqttEnabled = false;
int maxPwr = 0;
HwTools* hw;
Timezone* tz;
EntsoeApi* eapi = NULL;
AmsConfiguration* config;
GpioConfig* gpioConfig;
MeterConfig* meterConfig;
WebConfig webConfig;
AmsData* meterState;
AmsDataStorage* ds;
EnergyAccounting* ea = NULL;
MQTTClient* mqtt = NULL;
bool uploading = false;
File file;
bool performRestart = false;
bool performUpgrade = false;
bool rebootForUpgrade = false;
static const uint16_t BufferSize = 2048;
char* buf;
#if defined(ESP8266)
ESP8266WebServer server;
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
WebServer server;
#endif
bool checkSecurity(byte level);
void indexHtml();
void applicationJs();
void temperature();
void temperaturePost();
void temperatureJson();
void configMeterHtml();
void configMeterAdvancedHtml();
void configWifiHtml();
void configMqttHtml();
void configWebHtml();
void configDomoticzHtml();
void configEntsoeHtml();
void configNtpHtml();
void configGpioHtml();
void configDebugHtml();
void configThresholdsHtml();
void bootCss();
void githubSvg();
void dataJson();
void dayplotJson();
void monthplotJson();
void energyPriceJson();
void configFileHtml();
void configFileDownload();
void configFileUpload();
void handleSetup();
void handleSave();
String getSerialSelectOptions(int selected);
void firmwareHtml();
void firmwareUpload();
void firmwareDownload();
void restartHtml();
void restartPost();
void restartWaitHtml();
void isAliveCheck();
void uploadHtml(const char* label, const char* action, const char* menu);
void deleteHtml(const char* label, const char* action, const char* menu);
HTTPUpload& uploadFile(const char* path);
void deleteFile(const char* path);
void uploadPost();
void mqttCa();
void mqttCaUpload();
void mqttCaDelete();
void mqttCert();
void mqttCertUpload();
void mqttCertDelete();
void mqttKey();
void mqttKeyUpload();
void mqttKeyDelete();
void factoryResetHtml();
void factoryResetPost();
void notFound();
void printD(String fmt, ...);
void printI(String fmt, ...);
void printW(String fmt, ...);
void printE(String fmt, ...);
};
#endif