WIP more changes for 15min prices

This commit is contained in:
Gunnar Skjold
2025-06-05 15:28:15 +02:00
parent b9594568ee
commit b592532c1d
13 changed files with 323 additions and 185 deletions

View File

@@ -18,6 +18,24 @@ struct EnergyAccountingPeak {
};
struct EnergyAccountingData {
uint8_t version;
uint8_t month;
int32_t costToday;
int32_t costYesterday;
int32_t costThisMonth;
int32_t costLastMonth;
int32_t incomeToday;
int32_t incomeYesterday;
int32_t incomeThisMonth;
int32_t incomeLastMonth;
uint32_t lastMonthImport;
uint32_t lastMonthExport;
uint8_t lastMonthAccuracy;
EnergyAccountingPeak peaks[5];
time_t lastUpdated;
};
struct EnergyAccountingData6 {
uint8_t version;
uint8_t month;
int32_t costYesterday;
@@ -44,24 +62,6 @@ struct EnergyAccountingData5 {
EnergyAccountingPeak peaks[5];
};
struct EnergyAccountingData4 {
uint8_t version;
uint8_t month;
uint16_t costYesterday;
uint16_t costThisMonth;
uint16_t costLastMonth;
EnergyAccountingPeak peaks[5];
};
struct EnergyAccountingData2 {
uint8_t version;
uint8_t month;
uint16_t maxHour;
uint16_t costYesterday;
uint16_t costThisMonth;
uint16_t costLastMonth;
};
struct EnergyAccountingRealtimeData {
uint8_t magic;
uint8_t currentHour;
@@ -124,7 +124,6 @@ public:
void setData(EnergyAccountingData&);
void setCurrency(String currency);
float getPriceForHour(uint8_t d, uint8_t h);
private:
#if defined(AMS_REMOTE_DEBUG)
@@ -137,7 +136,7 @@ private:
PriceService *ps = NULL;
EnergyAccountingConfig *config = NULL;
Timezone *tz = NULL;
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
EnergyAccountingRealtimeData* realtimeData = NULL;
String currency = "";

View File

@@ -30,7 +30,7 @@ EnergyAccounting::EnergyAccounting(Stream* Stream, EnergyAccountingRealtimeData*
rtd->lastImportUpdateMillis = 0;
rtd->lastExportUpdateMillis = 0;
}
this->realtimeData = rtd;
realtimeData = rtd;
}
void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config) {
@@ -67,31 +67,31 @@ bool EnergyAccounting::update(AmsData* amsData) {
breakTime(tz->toLocal(now), local);
if(!init) {
this->realtimeData->lastImportUpdateMillis = 0;
this->realtimeData->lastExportUpdateMillis = 0;
this->realtimeData->currentHour = local.Hour;
this->realtimeData->currentDay = local.Day;
realtimeData->lastImportUpdateMillis = 0;
realtimeData->lastExportUpdateMillis = 0;
realtimeData->currentHour = local.Hour;
realtimeData->currentDay = local.Day;
if(!load()) {
data = { 6, local.Month,
0, 0, 0, // Cost
0, 0, 0, // Income
data = { 7, local.Month,
0, 0, 0, 0, // Cost
0, 0, 0, 0, // Income
0, 0, 0, // Last month import, export and accuracy
0, 0, // Peak 1
0, 0, // Peak 2
0, 0, // Peak 3
0, 0, // Peak 4
0, 0 // Peak 5
0, 0, // Peak 5
0 // Last updated
};
}
init = true;
}
float importPrice = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
if(!initPrice && importPrice != PRICE_NO_VALUE) {
if(!initPrice && ps != NULL && ps->hasPrice()) {
calcDayCost();
}
if(local.Hour != this->realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
if(local.Hour != realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
tmElements_t oneHrAgo, oneHrAgoLocal;
breakTime(now-3600, oneHrAgo);
uint16_t val = round(ds->getHourImport(oneHrAgo.Hour) / 10.0);
@@ -99,27 +99,24 @@ bool EnergyAccounting::update(AmsData* amsData) {
breakTime(tz->toLocal(now-3600), oneHrAgoLocal);
ret |= updateMax(val, oneHrAgoLocal.Day);
this->realtimeData->currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
if(local.Hour > 0) {
calcDayCost();
}
realtimeData->currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
this->realtimeData->use = 0;
this->realtimeData->produce = 0;
this->realtimeData->costHour = 0;
this->realtimeData->incomeHour = 0;
realtimeData->use = 0;
realtimeData->produce = 0;
realtimeData->costHour = 0;
realtimeData->incomeHour = 0;
uint8_t prevDay = this->realtimeData->currentDay;
if(local.Day != this->realtimeData->currentDay) {
data.costYesterday = this->realtimeData->costDay * 100;
data.costThisMonth += this->realtimeData->costDay * 100;
this->realtimeData->costDay = 0;
uint8_t prevDay = realtimeData->currentDay;
if(local.Day != realtimeData->currentDay) {
data.costYesterday = realtimeData->costDay * 100;
data.costThisMonth += realtimeData->costDay * 100;
realtimeData->costDay = 0;
data.incomeYesterday = this->realtimeData->incomeDay * 100;
data.incomeThisMonth += this->realtimeData->incomeDay * 100;
this->realtimeData->incomeDay = 0;
data.incomeYesterday = realtimeData->incomeDay * 100;
data.incomeThisMonth += realtimeData->incomeDay * 100;
realtimeData->incomeDay = 0;
this->realtimeData->currentDay = local.Day;
realtimeData->currentDay = local.Day;
ret = true;
}
@@ -149,42 +146,49 @@ bool EnergyAccounting::update(AmsData* amsData) {
data.lastMonthAccuracy = accuracy;
data.month = local.Month;
this->realtimeData->currentThresholdIdx = 0;
realtimeData->currentThresholdIdx = 0;
ret = true;
}
if(ret) {
data.costToday = realtimeData->costDay * 100;
data.incomeToday = realtimeData->incomeDay * 100;
data.lastUpdated = now;
}
}
if(this->realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastImportUpdateMillis;
if(realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
unsigned long ms = amsData->getLastUpdateMillis() - realtimeData->lastImportUpdateMillis;
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
if(kwhi > 0) {
this->realtimeData->use += kwhi;
realtimeData->use += kwhi;
float importPrice = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_IMPORT);
if(importPrice != PRICE_NO_VALUE) {
float cost = importPrice * kwhi;
this->realtimeData->costHour += cost;
this->realtimeData->costDay += cost;
realtimeData->costHour += cost;
realtimeData->costDay += cost;
}
}
this->realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
}
if(amsData->getListType() > 1 && this->realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastExportUpdateMillis;
if(amsData->getListType() > 1 && realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
unsigned long ms = amsData->getLastUpdateMillis() - realtimeData->lastExportUpdateMillis;
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
if(kwhe > 0) {
this->realtimeData->produce += kwhe;
float exportPrice = getPriceForHour(PRICE_DIRECTION_EXPORT, 0);
realtimeData->produce += kwhe;
float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_EXPORT);
if(exportPrice != PRICE_NO_VALUE) {
float income = exportPrice * kwhe;
this->realtimeData->incomeHour += income;
this->realtimeData->incomeDay += income;
realtimeData->incomeHour += income;
realtimeData->incomeDay += income;
}
}
this->realtimeData->lastExportUpdateMillis = amsData->getLastUpdateMillis();
realtimeData->lastExportUpdateMillis = amsData->getLastUpdateMillis();
}
if(config != NULL) {
while(getMonthMax() > config->thresholds[this->realtimeData->currentThresholdIdx] && this->realtimeData->currentThresholdIdx < 10) this->realtimeData->currentThresholdIdx++;
while(getMonthMax() > config->thresholds[realtimeData->currentThresholdIdx] && realtimeData->currentThresholdIdx < 10) realtimeData->currentThresholdIdx++;
}
return ret;
@@ -192,28 +196,36 @@ bool EnergyAccounting::update(AmsData* amsData) {
void EnergyAccounting::calcDayCost() {
time_t now = time(nullptr);
tmElements_t local, utc;
tmElements_t local, utc, lastUpdateUtc;
if(tz == NULL) return;
breakTime(tz->toLocal(now), local);
if(ps == NULL) return;
if(getPriceForHour(PRICE_DIRECTION_IMPORT, 0) != PRICE_NO_VALUE) {
if(initPrice) {
this->realtimeData->costDay = 0;
this->realtimeData->incomeDay = 0;
if(ps->hasPrice()) {
breakTime(data.lastUpdated, lastUpdateUtc);
uint8_t calcFromHour = 0;
if(lastUpdateUtc.Day != local.Day || lastUpdateUtc.Month != local.Month || lastUpdateUtc.Year != local.Year) {
realtimeData->costDay = 0;
realtimeData->incomeDay = 0;
calcFromHour = 0;
} else {
realtimeData->costDay = data.costToday / 100.0;
realtimeData->incomeDay = data.incomeToday / 100.0;
calcFromHour = lastUpdateUtc.Hour;
}
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
for(uint8_t i = calcFromHour; i < realtimeData->currentHour; i++) {
breakTime(now - ((local.Hour - i) * 3600), utc);
float priceIn = getPriceForHour(PRICE_DIRECTION_IMPORT, i - local.Hour);
float priceIn = ps->getPriceForHour(PRICE_DIRECTION_IMPORT, i - local.Hour);
if(priceIn != PRICE_NO_VALUE) {
int16_t wh = ds->getHourImport(utc.Hour);
this->realtimeData->costDay += priceIn * (wh / 1000.0);
realtimeData->costDay += priceIn * (wh / 1000.0);
}
float priceOut = getPriceForHour(PRICE_DIRECTION_EXPORT, i - local.Hour);
float priceOut = ps->getPriceForHour(PRICE_DIRECTION_EXPORT, i - local.Hour);
if(priceOut != PRICE_NO_VALUE) {
int16_t wh = ds->getHourExport(utc.Hour);
this->realtimeData->incomeDay += priceOut * (wh / 1000.0);
realtimeData->incomeDay += priceOut * (wh / 1000.0);
}
}
initPrice = true;
@@ -221,7 +233,7 @@ void EnergyAccounting::calcDayCost() {
}
float EnergyAccounting::getUseThisHour() {
return this->realtimeData->use;
return realtimeData->use;
}
float EnergyAccounting::getUseToday() {
@@ -231,7 +243,7 @@ float EnergyAccounting::getUseToday() {
if(now < FirmwareVersion::BuildEpoch) return 0.0;
tmElements_t utc, local;
breakTime(tz->toLocal(now), local);
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
for(uint8_t i = 0; i < realtimeData->currentHour; i++) {
breakTime(now - ((local.Hour - i) * 3600), utc);
ret += ds->getHourImport(utc.Hour) / 1000.0;
}
@@ -242,7 +254,7 @@ float EnergyAccounting::getUseThisMonth() {
time_t now = time(nullptr);
if(now < FirmwareVersion::BuildEpoch) return 0.0;
float ret = 0;
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
ret += ds->getDayImport(i) / 1000.0;
}
return ret + getUseToday();
@@ -253,7 +265,7 @@ float EnergyAccounting::getUseLastMonth() {
}
float EnergyAccounting::getProducedThisHour() {
return this->realtimeData->produce;
return realtimeData->produce;
}
float EnergyAccounting::getProducedToday() {
@@ -263,7 +275,7 @@ float EnergyAccounting::getProducedToday() {
if(now < FirmwareVersion::BuildEpoch) return 0.0;
tmElements_t utc, local;
breakTime(tz->toLocal(now), local);
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
for(uint8_t i = 0; i < realtimeData->currentHour; i++) {
breakTime(now - ((local.Hour - i) * 3600), utc);
ret += ds->getHourExport(utc.Hour) / 1000.0;
}
@@ -274,7 +286,7 @@ float EnergyAccounting::getProducedThisMonth() {
time_t now = time(nullptr);
if(now < FirmwareVersion::BuildEpoch) return 0.0;
float ret = 0;
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
ret += ds->getDayExport(i) / 1000.0;
}
return ret + getProducedToday();
@@ -285,11 +297,11 @@ float EnergyAccounting::getProducedLastMonth() {
}
float EnergyAccounting::getCostThisHour() {
return this->realtimeData->costHour;
return realtimeData->costHour;
}
float EnergyAccounting::getCostToday() {
return this->realtimeData->costDay;
return realtimeData->costDay;
}
float EnergyAccounting::getCostYesterday() {
@@ -305,11 +317,11 @@ float EnergyAccounting::getCostLastMonth() {
}
float EnergyAccounting::getIncomeThisHour() {
return this->realtimeData->incomeHour;
return realtimeData->incomeHour;
}
float EnergyAccounting::getIncomeToday() {
return this->realtimeData->incomeDay;
return realtimeData->incomeDay;
}
float EnergyAccounting::getIncomeYesterday() {
@@ -327,7 +339,7 @@ float EnergyAccounting::getIncomeLastMonth() {
uint8_t EnergyAccounting::getCurrentThreshold() {
if(config == NULL)
return 0;
return config->thresholds[this->realtimeData->currentThresholdIdx];
return config->thresholds[realtimeData->currentThresholdIdx];
}
float EnergyAccounting::getMonthMax() {
@@ -407,85 +419,65 @@ bool EnergyAccounting::load() {
char buf[file.size()];
file.readBytes(buf, file.size());
if(buf[0] == 6) {
if(buf[0] == 7) {
EnergyAccountingData* data = (EnergyAccountingData*) buf;
memcpy(&this->data, data, sizeof(this->data));
ret = true;
} else if(buf[0] == 5) {
EnergyAccountingData5* data = (EnergyAccountingData5*) buf;
this->data = { 6, data->month,
((uint32_t) data->costYesterday) * 10,
((uint32_t) data->costThisMonth) * 100,
((uint32_t) data->costLastMonth) * 100,
((uint32_t) data->incomeYesterday) * 10,
((uint32_t) data->incomeThisMonth) * 100,
((uint32_t) data->incomeLastMonth) * 100,
0,0,0, // Last month import, export and accuracy
data->peaks[0].day, data->peaks[0].value,
data->peaks[1].day, data->peaks[1].value,
data->peaks[2].day, data->peaks[2].value,
data->peaks[3].day, data->peaks[3].value,
data->peaks[4].day, data->peaks[4].value
};
ret = true;
} else if(buf[0] == 4) {
EnergyAccountingData4* data = (EnergyAccountingData4*) buf;
this->data = { 5, data->month,
((uint32_t) data->costYesterday) * 10,
((uint32_t) data->costThisMonth) * 100,
((uint32_t) data->costLastMonth) * 100,
0,0,0, // Income from production
0,0,0, // Last month import, export and accuracy
data->peaks[0].day, data->peaks[0].value,
data->peaks[1].day, data->peaks[1].value,
data->peaks[2].day, data->peaks[2].value,
data->peaks[3].day, data->peaks[3].value,
data->peaks[4].day, data->peaks[4].value
};
ret = true;
} else if(buf[0] == 3) {
EnergyAccountingData* data = (EnergyAccountingData*) buf;
this->data = { 5, data->month,
data->costYesterday * 10,
} else if(buf[0] == 6) {
EnergyAccountingData6* data = (EnergyAccountingData6*) buf;
this->data = { 7, data->month,
0, // Cost today
data->costYesterday,
data->costThisMonth,
data->costLastMonth,
0,0,0, // Income from production
0, // Income today
data->incomeYesterday,
data->incomeThisMonth,
data->incomeLastMonth,
data->lastMonthImport,
data->lastMonthExport,
data->lastMonthAccuracy,
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,
0 // Last updated
};
ret = true;
} else if(buf[0] == 5) {
EnergyAccountingData5* data = (EnergyAccountingData5*) buf;
this->data = { 7, data->month,
0, // Cost today
((int32_t) data->costYesterday) * 10,
((int32_t) data->costThisMonth) * 100,
((int32_t) data->costLastMonth) * 100,
0, // Income today
((int32_t) data->incomeYesterday) * 10,
((int32_t) data->incomeThisMonth) * 100,
((int32_t) data->incomeLastMonth) * 100,
0,0,0, // Last month import, export and accuracy
data->peaks[0].day, data->peaks[0].value,
data->peaks[1].day, data->peaks[1].value,
data->peaks[2].day, data->peaks[2].value,
data->peaks[3].day, data->peaks[3].value,
data->peaks[4].day, data->peaks[4].value
data->peaks[4].day, data->peaks[4].value,
0 // Last updated
};
ret = true;
} else {
data = { 5, 0,
0, 0, 0, // Cost
0,0,0, // Income from production
data = { 7, 0,
0,0,0,0, // Cost
0,0,0,0, // Income from production
0,0,0, // Last month import, export and accuracy
0, 0, // Peak 1
0, 0, // Peak 2
0, 0, // Peak 3
0, 0, // Peak 4
0, 0 // Peak 5
0, 0, // Peak 5
0 // Last updated
};
if(buf[0] == 2) {
EnergyAccountingData2* data = (EnergyAccountingData2*) buf;
this->data.month = data->month;
this->data.costYesterday = data->costYesterday * 10;
this->data.costThisMonth = data->costThisMonth;
this->data.costLastMonth = data->costLastMonth;
uint8_t b = 0;
for(uint8_t i = sizeof(this->data); i < file.size(); i+=2) {
this->data.peaks[b].day = b;
memcpy(&this->data.peaks[b].value, buf+i, 2);
b++;
if(b >= config->hours || b >= 5) break;
}
ret = true;
} else {
ret = false;
}
ret = false;
}
file.close();
@@ -550,8 +542,3 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
void EnergyAccounting::setCurrency(String currency) {
this->currency = currency;
}
float EnergyAccounting::getPriceForHour(uint8_t d, uint8_t h) {
if(ps == NULL) return PRICE_NO_VALUE;
return ps->getValueForHour(d, h);
}

View File

@@ -360,7 +360,7 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
if(pubTopic.isEmpty() || !mqtt.connected())
return false;
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
if(!ps->hasPrice())
return false;
publishPriceSensors(ps);
@@ -712,7 +712,7 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
prInit[i] = true;
}
float exportPrice = ps->getValueForHour(PRICE_DIRECTION_EXPORT, 0);
float exportPrice = ps->getPrice(PRICE_DIRECTION_EXPORT);
if(exportPrice != PRICE_NO_VALUE) {
char path[20];
snprintf(path, 20, "exportprices['%d']", 0);

View File

@@ -274,7 +274,7 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
bool JsonMqttHandler::publishPrices(PriceService* ps) {
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
return false;
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
if(!ps->hasPrice())
return false;
time_t now = time(nullptr);

View File

@@ -27,10 +27,6 @@
#define SSL_BUF_SIZE 512
#define PRICE_DIRECTION_IMPORT 0x01
#define PRICE_DIRECTION_EXPORT 0x02
#define PRICE_DIRECTION_BOTH 0x03
#define PRICE_DAY_MO 0x01
#define PRICE_DAY_TU 0x02
#define PRICE_DAY_WE 0x04
@@ -58,10 +54,11 @@ struct PriceConfig {
};
struct AmsPriceV2Header {
char source[4];
char currency[4];
char measurementUnit[4];
char source[4];
uint8_t resolutionInMinutes;
uint8_t hours;
bool differentExportPrices;
uint8_t numberOfPoints;
};
@@ -80,6 +77,22 @@ public:
char* getCurrency();
char* getArea();
char* getSource();
uint8_t getResolutionInMinutes();
uint8_t getNumberOfPointsAvailable();
bool isExportPricesDifferentFromImport() {
return today->isExportPricesDifferentFromImport();
}
bool hasPrice() { return hasPrice(PRICE_DIRECTION_IMPORT); }
bool hasPrice(uint8_t direction);
bool hasPricePoint(uint8_t direction, int8_t point);
float getPrice(uint8_t direction);
float getPricePoint(uint8_t direction, int8_t point);
float getPriceForHour(uint8_t direction, int8_t hour); // If not 60min interval, average
float getValueForHour(uint8_t direction, int8_t hour);
float getValueForHour(uint8_t direction, time_t ts, int8_t hour);

View File

@@ -4,35 +4,42 @@
*
*/
#include <stdint.h>
#ifndef _PRICESCONTAINER_H
#define _PRICESCONTAINER_H
#define PRICE_NO_VALUE -127
#define PRICE_DIRECTION_IMPORT 0x01
#define PRICE_DIRECTION_EXPORT 0x02
#define PRICE_DIRECTION_BOTH 0x03
class PricesContainer {
public:
PricesContainer(char* source);
void setup(uint8_t resolutionInMinutes, uint8_t hoursThisDay);
void setup(uint8_t resolutionInMinutes, uint8_t numberOfPoints, bool differentExportPrices);
char* getSource();
void setCurrency(char* currency);
char* getCurrency();
bool isExportPricesDifferentFromImport() {
return differentExportPrices;
}
uint8_t getResolutionInMinutes();
uint8_t getHours();
uint8_t getNumberOfPoints();
void setPrice(uint8_t point, int32_t value);
void setPrice(uint8_t point, float value);
bool hasPrice(uint8_t point);
float getPrice(uint8_t point); // int32_t / 10_000
void setPrice(uint8_t point, float value, uint8_t direction);
bool hasPrice(uint8_t point, uint8_t direction);
float getPrice(uint8_t point, uint8_t direction); // int32_t / 10_000
private:
char source[4];
char currency[4];
uint8_t resolutionInMinutes;
uint8_t hours;
bool differentExportPrices;
uint8_t numberOfPoints;
int32_t *points;
};

View File

@@ -73,7 +73,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
buf[pos] = '\0';
float val = String(buf).toFloat();
for(uint8_t i = pointNum; i < container->getNumberOfPoints(); i++) {
container->setPrice(i, val * multiplier);
container->setPrice(i, val * multiplier, PRICE_DIRECTION_IMPORT);
}
docPos = DOCPOS_SEEK;
pos = 0;
@@ -85,12 +85,12 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
buf[pos] = '\0';
// This happens if there are two time series in the XML. We are only interrested in the first one, so we ignore the rest of the document
if(container->hasPrice(0)) return 1;
if(container->hasPrice(0, PRICE_DIRECTION_IMPORT)) return 1;
if(strcmp_P(buf, PSTR("PT15M"))) {
container->setup(15, 24);
container->setup(15, 100, false);
} else if(strcmp_P(buf, PSTR("PT60M"))) {
container->setup(60, 24);
container->setup(60, 25, false);
}
docPos = DOCPOS_SEEK;
pos = 0;

View File

@@ -185,18 +185,18 @@ float PriceService::getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t h
if(pos >= hoursTomorrow) return PRICE_NO_VALUE;
if(tomorrow == NULL)
return PRICE_NO_VALUE;
if(!tomorrow->hasPrice(pos))
if(!tomorrow->hasPrice(pos, direction))
return PRICE_NO_VALUE;
value = tomorrow->getPrice(pos);
value = tomorrow->getPrice(pos, direction);
float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency, time(nullptr));
if(mult == 0) return PRICE_NO_VALUE;
multiplier *= mult;
} else if(pos >= 0) {
if(today == NULL)
return PRICE_NO_VALUE;
if(!today->hasPrice(pos))
if(!today->hasPrice(pos, direction))
return PRICE_NO_VALUE;
value = today->getPrice(pos);
value = today->getPrice(pos, direction);
float mult = getCurrencyMultiplier(today->getCurrency(), config->currency, time(nullptr));
if(mult == 0) return PRICE_NO_VALUE;
multiplier *= mult;
@@ -422,7 +422,7 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
PricesContainer* ret = new PricesContainer("EOE");
EntsoeA44Parser a44(ret);
if(retrieve(buf, &a44) && ret->hasPrice(0)) {
if(retrieve(buf, &a44) && ret->hasPrice(0, PRICE_DIRECTION_IMPORT)) {
return ret;
} else {
delete ret;
@@ -477,12 +477,17 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
AmsPriceV2Header* header = (AmsPriceV2Header*) (content-gcmRet);
PricesContainer* ret = new PricesContainer(header->source);
ret->setup(header->resolutionInMinutes, header->hours);
ret->setup(header->resolutionInMinutes, header->numberOfPoints, header->differentExportPrices);
ret->setCurrency(header->currency);
int32_t* points = (int32_t*) &header[1];
for(uint8_t i = 0; i < header->numberOfPoints; i++) {
ret->setPrice(i, points[i]);
ret->setPrice(i, points[i], PRICE_DIRECTION_IMPORT);
}
if(header->differentExportPrices) {
for(uint8_t i = 0; i < header->numberOfPoints; i++) {
ret->setPrice(i, points[i], PRICE_DIRECTION_EXPORT);
}
}
lastError = 0;
nextFetchDelayMinutes = 1;

View File

@@ -0,0 +1,67 @@
#include "PricesContainer.h"
#include <cstring>
PricesContainer::PricesContainer(char* source) {
strncpy(this->source, source, 4);
}
void PricesContainer::setup(uint8_t resolutionInMinutes, uint8_t numberOfPoints, bool differentExportPrices) {
this->resolutionInMinutes = resolutionInMinutes;
this->differentExportPrices = differentExportPrices;
this->numberOfPoints = numberOfPoints;
this->points = new int32_t[numberOfPoints * (differentExportPrices ? 2 : 1)];
memset(this->points, PRICE_NO_VALUE * 10000, numberOfPoints * (differentExportPrices ? 2 : 1) * sizeof(int32_t));
}
char* PricesContainer::getSource() {
return this->source;
}
void PricesContainer::setCurrency(char* currency) {
strncpy(this->currency, currency, 4);
}
char* PricesContainer::getCurrency() {
return this->currency;
}
uint8_t PricesContainer::getResolutionInMinutes() {
return this->resolutionInMinutes;
}
uint8_t PricesContainer::getNumberOfPoints() {
return this->numberOfPoints;
}
void PricesContainer::setPrice(uint8_t point, float value, uint8_t direction) {
if(direction == PRICE_DIRECTION_EXPORT && !differentExportPrices) {
return; // Export prices not supported
}
if(direction != PRICE_DIRECTION_EXPORT) {
points[point] = static_cast<int32_t>(value * 10000);
}
if(differentExportPrices && direction != PRICE_DIRECTION_IMPORT) {
points[point + numberOfPoints] = static_cast<int32_t>(value * 10000);
}
}
bool PricesContainer::hasPrice(uint8_t point, uint8_t direction) {
float val = getPrice(point, direction);
return val != PRICE_NO_VALUE;
}
float PricesContainer::getPrice(uint8_t point, uint8_t direction) {
if(differentExportPrices && direction == PRICE_DIRECTION_EXPORT) {
if(point < numberOfPoints) {
return static_cast<float>(points[point + numberOfPoints]) / 10000.0f;
}
}
if(differentExportPrices && direction == PRICE_DIRECTION_BOTH) return PRICE_NO_VALUE; // Can't get a price for both directions if the export prices are different
if(point < numberOfPoints) {
return static_cast<float>(points[point]) / 10000.0f;
}
return PRICE_NO_VALUE; // Invalid point
}

View File

@@ -230,7 +230,7 @@ bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
bool RawMqttHandler::publishPrices(PriceService* ps) {
if(topic.isEmpty() || !mqtt.connected())
return false;
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
if(!ps->hasPrice())
return false;
time_t now = time(nullptr);

View File

@@ -125,7 +125,10 @@ private:
void dataJson();
void dayplotJson();
void monthplotJson();
void energyPriceJson();
void energyPriceJson(); // Deprecated
void importPriceJson();
void exportPriceJson();
void priceJson(uint8_t direction);
void temperatureJson();
void tariffJson();
void realtimeJson();

View File

@@ -126,6 +126,8 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, AmsDa
server.on(context + F("/dayplot.json"), HTTP_GET, std::bind(&AmsWebServer::dayplotJson, this));
server.on(context + F("/monthplot.json"), HTTP_GET, std::bind(&AmsWebServer::monthplotJson, this));
server.on(context + F("/energyprice.json"), HTTP_GET, std::bind(&AmsWebServer::energyPriceJson, this));
server.on(context + F("/importprice.json"), HTTP_GET, std::bind(&AmsWebServer::importPriceJson, this));
server.on(context + F("/exportprice.json"), HTTP_GET, std::bind(&AmsWebServer::exportPriceJson, this));
server.on(context + F("/temperature.json"), HTTP_GET, std::bind(&AmsWebServer::temperatureJson, this));
server.on(context + F("/tariff.json"), HTTP_GET, std::bind(&AmsWebServer::tariffJson, this));
server.on(context + F("/realtime.json"), HTTP_GET, std::bind(&AmsWebServer::realtimeJson, this));
@@ -579,8 +581,8 @@ void AmsWebServer::dataJson() {
mqttStatus = 3;
}
float price = ea->getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
float exportPrice = ea->getPriceForHour(PRICE_DIRECTION_EXPORT, 0);
float price = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_IMPORT);
float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_EXPORT);
String peaks = "";
for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) {
@@ -721,14 +723,19 @@ void AmsWebServer::energyPriceJson() {
if(!checkSecurity(2))
return;
if(ps == NULL || !ps->hasPrice()) {
notFound();
return;
}
float prices[36];
for(int i = 0; i < 36; i++) {
prices[i] = ps == NULL ? PRICE_NO_VALUE : ps->getValueForHour(PRICE_DIRECTION_IMPORT, i);
prices[i] = ps->getValueForHour(PRICE_DIRECTION_IMPORT, i);
}
uint16_t pos = snprintf_P(buf, BufferSize, PSTR("{\"currency\":\"%s\",\"source\":\"%s\""),
ps == NULL ? "" : ps->getCurrency(),
ps == NULL ? "" : ps->getSource()
ps->getCurrency(),
ps->getSource()
);
for(uint8_t i = 0;i < 36; i++) {
@@ -749,6 +756,56 @@ void AmsWebServer::energyPriceJson() {
server.send(200, MIME_JSON, buf);
}
void AmsWebServer::importPriceJson() {
priceJson(PRICE_DIRECTION_IMPORT);
}
void AmsWebServer::exportPriceJson() {
priceJson(PRICE_DIRECTION_EXPORT);
}
void AmsWebServer::priceJson(uint8_t direction) {
if(!checkSecurity(2))
return;
if(ps == NULL || !ps->hasPrice()) {
notFound();
return;
}
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
float prices[numberOfPoints];
for(int i = 0; i < numberOfPoints; i++) {
prices[i] = ps->getPricePoint(direction, i);
}
uint16_t pos = snprintf_P(buf, BufferSize, PSTR("{\"currency\":\"%s\",\"source\":\"%s\",\"\"resolution\":%d,\"direction\":\"%s\",\"importExportPriceDifferent\":%s"),
ps->getCurrency(),
ps->getSource(),
ps->getResolutionInMinutes(),
direction == PRICE_DIRECTION_IMPORT ? "import" : direction == PRICE_DIRECTION_EXPORT ? "export" : "both",
ps->isExportPricesDifferentFromImport() ? "true" : "false"
);
for(uint8_t i = 0;i < numberOfPoints; i++) {
if(prices[i] == PRICE_NO_VALUE) {
pos += snprintf_P(buf+pos, BufferSize-pos, PSTR(",\"%02d\":null"), i);
} else {
pos += snprintf_P(buf+pos, BufferSize-pos, PSTR(",\"%02d\":%.4f"), i, prices[i]);
}
}
snprintf_P(buf+pos, BufferSize-pos, PSTR("}"));
addConditionalCloudHeaders();
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
server.setContentLength(strlen(buf));
server.send(200, MIME_JSON, buf);
}
void AmsWebServer::temperatureJson() {
if(!checkSecurity(2))
return;

View File

@@ -1545,7 +1545,7 @@ void MQTT_connect() {
if(mqttHandler != NULL) {
mqttHandler->connect();
mqttHandler->publishSystem(&hw, ps, &ea);
if(ps != NULL && ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) != PRICE_NO_VALUE) {
if(ps != NULL && ps->hasPrice()) {
mqttHandler->publishPrices(ps);
}
}