Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/AmsToMqttBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ void handleCustomMqtt() {
debugE_P(PSTR("Custom MQTT connector reporting error (%d)"), err);
customMqttHandler->connect();
customMqttHandler->publishSystem(&hw, ps, &ea);
if(ps != NULL && ps->hasPrice()) {
if(ps != NULL && ps->hasAnyPrice()) {
customMqttHandler->publishPrices(ps);
}
}
Expand Down Expand Up @@ -1451,7 +1451,7 @@ void handlePriceService(unsigned long now) {

if(config.isPriceServiceChanged()) {
PriceServiceConfig price;
if(config.getPriceServiceConfig(price) && price.enabled && strlen(price.area) > 0) {
if(config.getPriceServiceConfig(price)) {
if(ps == NULL) {
ps = new PriceService(&Debug);
ea.setPriceService(ps);
Expand All @@ -1462,11 +1462,13 @@ void handlePriceService(unsigned long now) {
}
#endif
}
// Kept alive even when fetching is disabled, as it also holds the fixed prices
ps->setup(price);
} else if(ps != NULL) {
delete ps;
ps = NULL;
ws.setPriceService(NULL);
ea.setPriceService(NULL);
}
ws.setPriceSettings(price.area, price.currency);
config.ackPriceServiceChange();
Expand Down Expand Up @@ -1983,7 +1985,7 @@ void MQTT_connect() {
mqttHandler->setDataStorage(&ds);
mqttHandler->connect();
mqttHandler->publishSystem(&hw, ps, &ea);
if(ps != NULL && ps->hasPrice()) {
if(ps != NULL && ps->hasAnyPrice()) {
mqttHandler->publishPrices(ps);
}
}
Expand Down
91 changes: 86 additions & 5 deletions src/PriceService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ void PriceService::setup(PriceServiceConfig& config) {
#endif

load();
dynamicPriceNeedKnown = false;
}

void PriceService::setTimezone(Timezone* tz) {
Expand Down Expand Up @@ -102,7 +103,7 @@ char* PriceService::getSource() {
return this->today->getSource();
} else if(tomorrow != NULL) {
return this->tomorrow->getSource();
} else if(!this->config->enabled && this->priceConfig.capacity() != 0) {
} else if(hasFixedPrice()) {
return "FIX"; // Fixed price
}
return "";
Expand All @@ -118,6 +119,68 @@ uint8_t PriceService::getNumberOfPointsAvailable() {
return today->getNumberOfPoints();
}

bool PriceService::hasFixedPrice() {
for (uint8_t i = 0; i < priceConfig.size(); i++) {
if(priceConfig.at(i).type == PRICE_TYPE_FIXED) {
return true;
}
}
return false;
}

bool PriceService::isDynamicPriceNeeded() {
if(!dynamicPriceNeedKnown) {
dynamicPriceNeeded = calculateDynamicPriceNeed();
dynamicPriceNeedKnown = true;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(PriceService) Dynamic price is %sneeded\n"), dynamicPriceNeeded ? "" : "not ");
}
return dynamicPriceNeeded;
}

bool PriceService::calculateDynamicPriceNeed() {
if(!hasFixedPrice()) return true;

time_t ts = time(nullptr);
tmElements_t tm;
breakTime(entsoeTz->toLocal(ts), tm);
tm.Hour = tm.Minute = tm.Second = 0;
time_t startOfDay = entsoeTz->toUTC(makeTime(tm));

// Fixed price periods have hour granularity, and we can be asked for any hour of
// today and tomorrow, so those 48 hours are the whole horizon. Anchored at the start
// of today rather than at the current point, as the day cost is calculated backwards
// from midnight.
for(uint8_t hour = 0; hour < 48; hour++) {
breakTime(tz->toLocal(startOfDay + (hour * SECS_PER_HOUR)), tm);
tm.Minute = tm.Second = 0;

uint8_t covered = 0;
for(uint8_t i = 0; i < priceConfig.size(); i++) {
PriceConfig pc = priceConfig.at(i);
if(pc.type != PRICE_TYPE_FIXED) continue;
if(!timeIsInPeriod(tm, pc)) continue;
covered |= pc.direction;
}
if((covered & PRICE_DIRECTION_BOTH) != PRICE_DIRECTION_BOTH) return true;
}
return false;
}

int16_t PriceService::getLastKnownPricePoint(uint8_t direction) {
// Searching backwards, as the common case is that we know the price all the way
// to the end of the horizon and can return on the first probe.
uint8_t currentPricePointIndex = getCurrentPricePointIndex();
for(int16_t point = getNumberOfPointsAvailable() - 1; point >= currentPricePointIndex; point--) {
if(getPricePoint(direction, point) != PRICE_NO_VALUE) {
return point;
}
}
return -1;
}

bool PriceService::isExportPricesDifferentFromImport() {
for (uint8_t i = 0; i < priceConfig.size(); i++) {
PriceConfig pc = priceConfig.at(i);
Expand Down Expand Up @@ -237,7 +300,7 @@ float PriceService::getPriceForRelativeHour(uint8_t direction, int8_t hour) {
return valueSum / valueCount;
}

float PriceService::getFixedPrice(uint8_t direction, int8_t point) {
float PriceService::getFixedPrice(uint8_t direction, uint8_t point) {
time_t ts = time(nullptr);

tmElements_t tm;
Expand Down Expand Up @@ -281,7 +344,9 @@ bool PriceService::loop() {
#endif
debugger->printf_P(PSTR("(PriceService) Day init\n"));
currentDay = tm.Day;
dynamicPriceNeedKnown = false;
currentPricePoint = getCurrentPricePointIndex();
return hasFixedPrice(); // Publish a fixed price right away, we have nothing to wait for
}

if(currentDay != tm.Day) {
Expand All @@ -295,20 +360,34 @@ bool PriceService::loop() {
tomorrow = NULL;
}
currentDay = tm.Day;
dynamicPriceNeedKnown = false;
currentPricePoint = getCurrentPricePointIndex();
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
return today != NULL || hasFixedPrice(); // Only trigger MQTT publish if we have todays prices, or a fixed price to fall back on.
} else if(currentPricePoint != getCurrentPricePointIndex()) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(PriceService) Price point reset\n"));
currentPricePoint = getCurrentPricePointIndex();
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
return today != NULL || hasFixedPrice(); // Only trigger MQTT publish if we have todays prices, or a fixed price to fall back on.
}

if(!config->enabled)
return false;

// A fixed price covering every hour in both directions makes the dynamic price
// irrelevant, so do not spend requests on fetching it
if(!isDynamicPriceNeeded()) {
if(today != NULL || tomorrow != NULL) {
if(today != NULL) delete today;
if(tomorrow != NULL) delete tomorrow;
today = tomorrow = NULL;
lastTodayFetch = lastTomorrowFetch = 0;
}
lastError = 0;
return false;
}

#ifndef AMS2MQTT_PRICE_KEY
if(strlen(getToken()) == 0) {
return false;
Expand All @@ -335,7 +414,7 @@ bool PriceService::loop() {
today = NULL;
}
currentPricePoint = getCurrentPricePointIndex();
return today != NULL && !readyToFetchForTomorrow; // Only trigger MQTT publish if we have todays prices and we are not immediately ready to fetch price for tomorrow.
return today != NULL; // Publish as soon as we have todays prices. Tomorrows fetch publishes again if it succeeds.
}

// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
Expand Down Expand Up @@ -649,6 +728,7 @@ std::vector<PriceConfig>& PriceService::getPriceConfig() {
}

void PriceService::setPriceConfig(uint8_t index, PriceConfig &priceConfig) {
dynamicPriceNeedKnown = false;
stripNonAscii((uint8_t*) priceConfig.name, 32, true);

if(this->priceConfig.capacity() != index+1)
Expand All @@ -660,6 +740,7 @@ void PriceService::setPriceConfig(uint8_t index, PriceConfig &priceConfig) {
}

void PriceService::cropPriceConfig(uint8_t size) {
dynamicPriceNeedKnown = false;
this->priceConfig.resize(size);
this->priceConfig.shrink_to_fit();

Expand Down
10 changes: 9 additions & 1 deletion src/PriceService.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ class PriceService {
bool hasPrice() { return hasPrice(PRICE_DIRECTION_IMPORT); }
bool hasPrice(uint8_t direction) { return getCurrentPrice(direction) != PRICE_NO_VALUE; }
bool hasPricePoint(uint8_t direction, int8_t point) { return getPricePoint(direction, point) != PRICE_NO_VALUE; }
bool hasAnyPrice() { return getLastKnownPricePoint(PRICE_DIRECTION_IMPORT) > -1 || getLastKnownPricePoint(PRICE_DIRECTION_EXPORT) > -1; }
bool hasFixedPrice();
bool isDynamicPriceNeeded(); // False when a fixed price covers the whole horizon, making the dynamic price irrelevant
int16_t getLastKnownPricePoint(uint8_t direction); // Last point from the current one onwards that we know a price for, -1 if none

float getCurrentPrice(uint8_t direction);
float getPricePoint(uint8_t direction, uint8_t point);
Expand Down Expand Up @@ -132,11 +136,15 @@ class PriceService {

int16_t lastError = 0;

bool dynamicPriceNeeded = true;
bool dynamicPriceNeedKnown = false;
bool calculateDynamicPriceNeed();

PricesContainer* fetchPrices(time_t);
bool retrieve(const char* url, Stream* doc);
float getCurrencyMultiplier(const char* from, const char* to, time_t t);
bool timeIsInPeriod(tmElements_t tm, PriceConfig pc);
float getFixedPrice(uint8_t direction, int8_t point);
float getFixedPrice(uint8_t direction, uint8_t point);
float getEnergyPricePoint(uint8_t direction, uint8_t point);
};
#endif
25 changes: 12 additions & 13 deletions src/mqtt/HomeAssistantMqttHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
if(pubTopic[0] == '\0' || !connected())
return false;
if(!ps->hasPrice())
if(!ps->hasAnyPrice())
return false;

publishPriceSensors(ps);
Expand All @@ -347,7 +347,7 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i);
values[i] = val;

if(val == PRICE_NO_VALUE) break;
if(val == PRICE_NO_VALUE) continue; // A hole, the price for this hour depends on a dynamic price we do not have

if(val < min) min = val;
if(val > max) max = val;
Expand Down Expand Up @@ -700,13 +700,14 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
}

uint8_t currentPricePointIndex = ps->getCurrentPricePointIndex();
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
// Discover sensors all the way out to the last point we know a price for. Points in
// between can still be unknown, if they depend on a dynamic price we do not have.
int16_t lastImportPoint = ps->getLastKnownPricePoint(PRICE_DIRECTION_IMPORT);
int16_t lastExportPoint = ps->getLastKnownPricePoint(PRICE_DIRECTION_EXPORT);

if(priceImportInit < numberOfPoints-currentPricePointIndex) {
if(priceImportInit < lastImportPoint-currentPricePointIndex+1) {
uint8_t importPriceSensorNo = 0;
for(int pricePointIndex = currentPricePointIndex; pricePointIndex < numberOfPoints; pricePointIndex++) {
float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, pricePointIndex);
if(val == PRICE_NO_VALUE) break;
for(int pricePointIndex = currentPricePointIndex; pricePointIndex <= lastImportPoint; pricePointIndex++) {
if(importPriceSensorNo < priceImportInit) {
importPriceSensorNo++;
continue;
Expand All @@ -732,7 +733,7 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
importPriceSensorNo == 0 ? "Current import price" : name,
"/prices",
path,
resolution * 60 + 300,
resolution * 60 * 2 + 300,
uom.c_str(),
"monetary",
importPriceSensorNo == 0 ? "total" : "",
Expand All @@ -744,11 +745,9 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
}
}

if(priceExportInit < numberOfPoints-currentPricePointIndex) {
if(priceExportInit < lastExportPoint-currentPricePointIndex+1) {
uint8_t exportPriceSensorNo = 0;
for(int pricePointIndex = currentPricePointIndex; pricePointIndex < numberOfPoints; pricePointIndex++) {
float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, pricePointIndex);
if(val == PRICE_NO_VALUE) break;
for(int pricePointIndex = currentPricePointIndex; pricePointIndex <= lastExportPoint; pricePointIndex++) {
if(exportPriceSensorNo < priceExportInit) {
exportPriceSensorNo++;
continue;
Expand All @@ -774,7 +773,7 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
exportPriceSensorNo == 0 ? "Current export price" : name,
"/prices",
path,
resolution * 60 + 300,
resolution * 60 * 2 + 300,
uom.c_str(),
"monetary",
exportPriceSensorNo == 0 ? "total" : "",
Expand Down
4 changes: 2 additions & 2 deletions src/mqtt/JsonMqttHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
bool JsonMqttHandler::publishPrices(PriceService* ps) {
if(strlen(mqttConfig.publishTopic) == 0 || !connected())
return false;
if(!ps->hasPrice())
if(!ps->hasAnyPrice())
return false;

time_t now = time(nullptr);
Expand All @@ -319,7 +319,7 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i);
values[i] = val;

if(val == PRICE_NO_VALUE) break;
if(val == PRICE_NO_VALUE) continue; // A hole, the price for this hour depends on a dynamic price we do not have

if(val < min) min = val;
if(val > max) max = val;
Expand Down
4 changes: 2 additions & 2 deletions src/mqtt/RawMqttHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
bool RawMqttHandler::publishPrices(PriceService* ps) {
if(topic.isEmpty() || !connected())
return false;
if(!ps->hasPrice())
if(!ps->hasAnyPrice())
return false;

time_t now = time(nullptr);
Expand All @@ -258,7 +258,7 @@ bool RawMqttHandler::publishPrices(PriceService* ps) {
values[i] = val;

if(i > 23) continue;
if(val == PRICE_NO_VALUE) break;
if(val == PRICE_NO_VALUE) continue; // A hole, the price for this hour depends on a dynamic price we do not have

if(val < min) min = val;
if(val > max) max = val;
Expand Down
Loading