First implementation of energy accounting

This commit is contained in:
Gunnar Skjold
2022-01-21 17:37:23 +01:00
parent e4d4753181
commit a72f02a779
11 changed files with 336 additions and 14 deletions

View File

@@ -23,6 +23,7 @@ ADC_MODE(ADC_VCC);
#include "AmsToMqttBridge.h"
#include "AmsStorage.h"
#include "AmsDataStorage.h"
#include "EnergyAccounting.h"
#include <MQTT.h>
#include <DNSServer.h>
#include <lwip/apps/sntp.h>
@@ -80,6 +81,7 @@ AmsData meterState;
bool ntpEnabled = false;
AmsDataStorage ds(&Debug);
EnergyAccounting ea(&Debug);
uint8_t wifiReconnectCount = 0;
@@ -309,7 +311,8 @@ void setup() {
swapWifiMode();
}
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds);
ea.setup(&ds, eapi);
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds, &ea);
#if defined(ESP32)
esp_task_wdt_init(WDT_TIMEOUT, true);
@@ -872,6 +875,10 @@ bool readHanPort() {
debugI("Saving day plot");
ds.save();
}
if(ea.update(&data)) {
debugI("Saving energy accounting");
ea.save();
}
}
delay(1);
return true;

182
src/EnergyAccounting.cpp Normal file
View File

@@ -0,0 +1,182 @@
#include "EnergyAccounting.h"
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger) {
this->debugger = debugger;
}
void EnergyAccounting::setup(AmsDataStorage *ds, EntsoeApi *eapi) {
this->ds = ds;
this->eapi = eapi;
}
bool EnergyAccounting::update(AmsData* amsData) {
time_t now = time(nullptr);
if(now < EPOCH_2021_01_01) return false;
bool ret = false;
tmElements_t tm;
breakTime(now, tm);
if(!init) {
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;
}
}
}
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;
}
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);
}
}
}
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;
}
}
use = 0;
costHour = 0;
currentHour = tm.Hour;
if(tm.Day != currentDay) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New day %d\n", tm.Day);
data.costYesterday = costDay * 100;
data.costThisMonth += costDay * 100;
costDay = 0;
currentDay = tm.Day;
ret = true;
}
if(tm.Month != data.month) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New month %d\n", tm.Month);
data.costLastMonth = data.costThisMonth;
data.costThisMonth = 0;
data.maxHour = 0;
data.month = tm.Month;
currentThresholdIdx = 0;
ret = true;
}
}
unsigned long ms = this->lastUpdateMillis > amsData->getLastUpdateMillis() ? 0 : amsData->getLastUpdateMillis() - this->lastUpdateMillis;
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);
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());
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);
return ret;
}
double EnergyAccounting::getUseThisHour() {
return use;
}
double EnergyAccounting::getCostThisHour() {
return costHour;
}
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;
}
return ret + getUseThisHour();
}
double EnergyAccounting::getCostToday() {
return costDay;
}
double EnergyAccounting::getCostYesterday() {
return data.costYesterday / 100.0;
}
double EnergyAccounting::getUseThisMonth() {
time_t now = time(nullptr);
if(now < EPOCH_2021_01_01) return 0;
tmElements_t tm;
breakTime(now, tm);
float ret = 0;
for(int i = 0; i < tm.Day; i++) {
ret += ds->getDay(i) / 1000.0;
}
return ret + getUseToday();
}
double EnergyAccounting::getCostThisMonth() {
return (data.costThisMonth / 100.0) + getCostToday();
}
double EnergyAccounting::getCostLastMonth() {
return data.costLastMonth / 100.0;
}
float EnergyAccounting::getCurrentThreshold() {
return thresholds[currentThresholdIdx] / 10.0;
}
float EnergyAccounting::getMonthMax() {
return data.maxHour / 100.0;
}
bool EnergyAccounting::load() {
return false; // TODO
}
bool EnergyAccounting::save() {
return false; // TODO
}

51
src/EnergyAccounting.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef _ENERGYACCOUNTING_H
#define _ENERGYACCOUNTING_H
#include "Arduino.h"
#include "AmsData.h"
#include "AmsDataStorage.h"
#include "entsoe/EntsoeApi.h"
struct EnergyAccountingData {
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, EntsoeApi *eapi);
bool update(AmsData* amsData);
bool save();
double getUseThisHour();
double getCostThisHour();
double getUseToday();
double getCostToday();
double getCostYesterday();
double getUseThisMonth();
double getCostThisMonth();
double getCostLastMonth();
float getMonthMax();
float getCurrentThreshold();
private:
RemoteDebug* debugger = NULL;
unsigned long lastUpdateMillis = 0;
bool init = false, initPrice = false;
AmsDataStorage *ds = NULL;
EntsoeApi *eapi = NULL;
uint8_t thresholds[5] = {50, 100, 150, 200, 250};
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
double use, costHour, costDay;
EnergyAccountingData data;
bool load();
};
#endif

View File

@@ -38,12 +38,12 @@ char* EntsoeApi::getCurrency() {
return this->config->currency;
}
float EntsoeApi::getValueForHour(uint8_t hour) {
float EntsoeApi::getValueForHour(int8_t hour) {
time_t cur = time(nullptr);
return getValueForHour(cur, hour);
}
float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
tmElements_t tm;
if(tz != NULL)
cur = tz->toLocal(cur);

View File

@@ -26,8 +26,8 @@ public:
char* getToken();
char* getCurrency();
float getValueForHour(uint8_t);
float getValueForHour(time_t, uint8_t);
float getValueForHour(int8_t);
float getValueForHour(time_t, int8_t);
private:
RemoteDebug* debugger;

View File

@@ -122,7 +122,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
int count = hw->getTempSensorCount();
if(count == 0)
if(count < 2)
return false;
int size = 32 + (count * 26);

View File

@@ -1,6 +1,5 @@
#include "AmsWebServer.h"
#include "version.h"
#include "AmsStorage.h"
#include "hexutils.h"
#include "AmsData.h"
@@ -56,12 +55,13 @@ AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw) {
this->hw = hw;
}
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds) {
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds, EnergyAccounting* ea) {
this->config = config;
this->gpioConfig = gpioConfig;
this->meterConfig = meterConfig;
this->meterState = meterState;
this->ds = ds;
this->ea = ea;
char jsuri[32];
snprintf(jsuri, 32, "/application-%s.js", VERSION);
@@ -757,7 +757,7 @@ void AmsWebServer::dataJson() {
if(eapi != NULL && strlen(eapi->getToken()) > 0)
price = eapi->getValueForHour(0);
char json[384];
char json[512];
snprintf_P(json, sizeof(json), DATA_JSON,
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
meterConfig->productionCapacity,
@@ -793,7 +793,15 @@ void AmsWebServer::dataJson() {
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(),
time(nullptr),
meterState->getMeterType(),
meterConfig->distributionSystem
meterConfig->distributionSystem,
ea->getMonthMax(),
ea->getCurrentThreshold(),
ea->getUseThisHour(),
ea->getCostThisHour(),
ea->getUseToday(),
ea->getCostToday(),
ea->getUseThisMonth(),
ea->getCostThisMonth()
);
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
@@ -1040,7 +1048,7 @@ void AmsWebServer::handleSetup() {
break;
case 200: // ESP32
gpioConfig->hanPin = 16;
gpioConfig->apPin = 0;
gpioConfig->apPin = 4;
gpioConfig->ledPin = 2;
gpioConfig->ledInverted = false;
break;

View File

@@ -8,7 +8,9 @@
#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"
@@ -30,7 +32,7 @@
class AmsWebServer {
public:
AmsWebServer(RemoteDebug* Debug, HwTools* hw);
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage*);
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*);
void loop();
void setMqtt(MQTTClient* mqtt);
void setTimezone(Timezone* tz);
@@ -50,6 +52,7 @@ private:
WebConfig webConfig;
AmsData* meterState;
AmsDataStorage* ds;
EnergyAccounting* ea = NULL;
MQTTClient* mqtt = NULL;
bool uploading = false;
File file;