mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-02-11 18:55:37 +00:00
First implementation of energy accounting
This commit is contained in:
@@ -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
182
src/EnergyAccounting.cpp
Normal 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
51
src/EnergyAccounting.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user