Possibility to specify fixed energy price

This commit is contained in:
Gunnar Skjold 2023-04-01 10:57:21 +02:00
parent ea43b2b632
commit 700f023292
14 changed files with 118 additions and 78 deletions

View File

@ -180,7 +180,8 @@ struct EntsoeConfig {
char currency[4];
uint32_t multiplier;
bool enabled;
}; // 62
uint16_t fixedPrice;
}; // 64
struct EnergyAccountingConfig {
uint16_t thresholds[10];

View File

@ -551,6 +551,7 @@ bool AmsConfiguration::setEntsoeConfig(EntsoeConfig& config) {
entsoeChanged |= strcmp(config.currency, existing.currency) != 0;
entsoeChanged |= config.multiplier != existing.multiplier;
entsoeChanged |= config.enabled != existing.enabled;
entsoeChanged |= config.fixedPrice != existing.fixedPrice;
} else {
entsoeChanged = true;
}
@ -1037,6 +1038,11 @@ bool AmsConfiguration::relocateConfig102() {
clearHomeAssistantConfig(haconf);
EEPROM.put(CONFIG_HA_START, haconf);
EntsoeConfig entsoe;
EEPROM.get(CONFIG_ENTSOE_START, entsoe);
entsoe.fixedPrice = 0;
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 103);
bool ret = EEPROM.commit();
EEPROM.end();

View File

@ -81,6 +81,9 @@ public:
EnergyAccountingData getData();
void setData(EnergyAccountingData&);
void setFixedPrice(double price);
double getPriceForHour(uint8_t h);
private:
RemoteDebug* debugger = NULL;
unsigned long lastUpdateMillis = 0;
@ -93,6 +96,7 @@ private:
double use = 0, costHour = 0, costDay = 0;
double produce = 0, incomeHour = 0, incomeDay = 0;
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
double fixedPrice = 0;
void calcDayCost();
bool updateMax(uint16_t val, uint8_t day);

View File

@ -68,7 +68,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
init = true;
}
if(!initPrice && eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
if(!initPrice && eapi != NULL && getPriceForHour(0) != ENTSOE_NO_VALUE) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lu\n", (int32_t) now);
calcDayCost();
}
@ -129,8 +129,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
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);
if(getPriceForHour(0) != ENTSOE_NO_VALUE) {
float price = getPriceForHour(0);
float cost = price * kwhi;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency());
costHour += cost;
@ -140,8 +140,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
if(kwhe > 0) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh export\n", kwhe);
produce += kwhe;
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
float price = eapi->getValueForHour(0);
if(getPriceForHour(0) != ENTSOE_NO_VALUE) {
float price = getPriceForHour(0);
float income = price * kwhe;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", income / 100.0, eapi->getCurrency());
incomeHour += income;
@ -163,13 +163,13 @@ void EnergyAccounting::calcDayCost() {
tmElements_t local, utc;
breakTime(tz->toLocal(now), local);
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
if(eapi != NULL && getPriceForHour(0) != ENTSOE_NO_VALUE) {
if(initPrice) {
costDay = 0;
incomeDay = 0;
}
for(int i = 0; i < currentHour; i++) {
float price = eapi->getValueForHour(i - local.Hour);
float price = getPriceForHour(i - local.Hour);
if(price == ENTSOE_NO_VALUE) break;
breakTime(now - ((local.Hour - i) * 3600), utc);
int16_t wh = ds->getHourImport(utc.Hour);
@ -502,4 +502,14 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
return true;
}
return false;
}
void EnergyAccounting::setFixedPrice(double price) {
this->fixedPrice = price;
}
double EnergyAccounting::getPriceForHour(uint8_t h) {
if(fixedPrice > 0.0) return fixedPrice;
if(eapi == NULL) return ENTSOE_NO_VALUE;
return eapi->getValueForHour(h);
}

File diff suppressed because one or more lines are too long

View File

@ -6,9 +6,11 @@
export let currency;
export let hasExport;
let hasCost = false;
let cols = 3
$: {
cols = currency ? 3 : 2;
hasCost = data && data.h && (data.h.c || data.d.c || data.m.c || data.h.i || data.d.i || data.m.i);
}
</script>
@ -22,25 +24,25 @@
<div class="grid grid-cols-{cols} mb-3">
<div>Hour</div>
<div class="text-right">{fmtnum(data.h.u,2)} kWh</div>
{#if currency}<div class="text-right">{fmtnum(data.h.c,2)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(data.h.c,2)} {currency}</div>{/if}
<div>Day</div>
<div class="text-right">{fmtnum(data.d.u,1)} kWh</div>
{#if currency}<div class="text-right">{fmtnum(data.d.c,1)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(data.d.c,1)} {currency}</div>{/if}
<div>Month</div>
<div class="text-right">{fmtnum(data.m.u)} kWh</div>
{#if currency}<div class="text-right">{fmtnum(data.m.c)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(data.m.c)} {currency}</div>{/if}
</div>
<strong>Export</strong>
<div class="grid grid-cols-{cols}">
<div>Hour</div>
<div class="text-right">{fmtnum(data.h.p,2)} kWh</div>
{#if currency}<div class="text-right">{fmtnum(data.h.i,2)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(data.h.i,2)} {currency}</div>{/if}
<div>Day</div>
<div class="text-right">{fmtnum(data.d.p,1)} kWh</div>
{#if currency}<div class="text-right">{fmtnum(data.d.i,1)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(data.d.i,1)} {currency}</div>{/if}
<div>Month</div>
<div class="text-right">{fmtnum(data.m.p)} kWh</div>
{#if currency}<div class="text-right">{fmtnum(data.m.i)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(data.m.i)} {currency}</div>{/if}
</div>
{:else}
<strong>Consumption</strong>
@ -52,7 +54,7 @@
<div>Month</div>
<div class="text-right">{fmtnum(data.m.u)} kWh</div>
</div>
{#if currency}
{#if hasCost}
<strong>Cost</strong>
<div class="grid grid-cols-2">
<div>Hour</div>

View File

@ -79,7 +79,7 @@
t: [0,0,0,0,0,0,0,0,0,0], h: 1
},
p: {
e: false, t: '', r: '', c: '', m: 1.0
e: false, t: '', r: '', c: '', m: 1.0, f: null
},
d: {
s: false, t: false, l: 5
@ -202,49 +202,57 @@
</div>
<input type="hidden" name="p" value="true"/>
<div class="my-1">
Price region<br/>
<select name="pr" bind:value={configuration.p.r} class="in-s">
<optgroup label="Norway">
<option value="10YNO-1--------2">NO1</option>
<option value="10YNO-2--------T">NO2</option>
<option value="10YNO-3--------J">NO3</option>
<option value="10YNO-4--------9">NO4</option>
<option value="10Y1001A1001A48H">NO5</option>
</optgroup>
<optgroup label="Sweden">
<option value="10Y1001A1001A44P">SE1</option>
<option value="10Y1001A1001A45N">SE2</option>
<option value="10Y1001A1001A46L">SE3</option>
<option value="10Y1001A1001A47J">SE4</option>
<div class="flex">
<div class="w-full">
Price region<br/>
<select name="pr" bind:value={configuration.p.r} class="in-f w-full">
<optgroup label="Norway">
<option value="10YNO-1--------2">NO1</option>
<option value="10YNO-2--------T">NO2</option>
<option value="10YNO-3--------J">NO3</option>
<option value="10YNO-4--------9">NO4</option>
<option value="10Y1001A1001A48H">NO5</option>
</optgroup>
<optgroup label="Denmark">
<option value="10YDK-1--------W">DK1</option>
<option value="10YDK-2--------M">DK2</option>
</optgroup>
<option value="10YAT-APG------L">Austria</option>
<option value="10YBE----------2">Belgium</option>
<option value="10YCZ-CEPS-----N">Czech Republic</option>
<option value="10Y1001A1001A39I">Estonia</option>
<option value="10YFI-1--------U">Finland</option>
<option value="10YFR-RTE------C">France</option>
<option value="10Y1001A1001A83F">Germany</option>
<option value="10YGB----------A">Great Britain</option>
<option value="10YLV-1001A00074">Latvia</option>
<option value="10YLT-1001A0008Q">Lithuania</option>
<option value="10YNL----------L">Netherland</option>
<option value="10YPL-AREA-----S">Poland</option>
<option value="10YCH-SWISSGRIDZ">Switzerland</option>
</select>
<optgroup label="Sweden">
<option value="10Y1001A1001A44P">SE1</option>
<option value="10Y1001A1001A45N">SE2</option>
<option value="10Y1001A1001A46L">SE3</option>
<option value="10Y1001A1001A47J">SE4</option>
</optgroup>
<optgroup label="Denmark">
<option value="10YDK-1--------W">DK1</option>
<option value="10YDK-2--------M">DK2</option>
</optgroup>
<option value="10YAT-APG------L">Austria</option>
<option value="10YBE----------2">Belgium</option>
<option value="10YCZ-CEPS-----N">Czech Republic</option>
<option value="10Y1001A1001A39I">Estonia</option>
<option value="10YFI-1--------U">Finland</option>
<option value="10YFR-RTE------C">France</option>
<option value="10Y1001A1001A83F">Germany</option>
<option value="10YGB----------A">Great Britain</option>
<option value="10YLV-1001A00074">Latvia</option>
<option value="10YLT-1001A0008Q">Lithuania</option>
<option value="10YNL----------L">Netherland</option>
<option value="10YPL-AREA-----S">Poland</option>
<option value="10YCH-SWISSGRIDZ">Switzerland</option>
</select>
</div>
<div>
Currency<br/>
<select name="pc" bind:value={configuration.p.c} class="in-l">
{#each ["NOK","SEK","DKK","EUR"] as c}
<option value={c}>{c}</option>
{/each}
</select>
</div>
</div>
</div>
<div class="my-1">
<div class="flex">
<div class="w-1/2">
Currency<br/>
<select name="pc" bind:value={configuration.p.c} class="in-f w-full">
{#each ["NOK","SEK","DKK","EUR"] as c}
<option value={c}>{c}</option>
{/each}
</select>
Fixed price<br/>
<input name="pf" bind:value={configuration.p.f} type="number" min="0.001" max="65" step="0.001" class="in-f tr w-full"/>
</div>
<div class="w-1/2">
Multiplier<br/>

View File

@ -37,7 +37,7 @@
<div class="cnt">
<div class="grid grid-cols-2">
<div class="col-span-2">
<PowerGauge val={data.i ? data.i : 0} max={data.im ? data.im : 15000} unit="W" label="Import" sub={data.p} subunit={prices.currency} colorFn={ampcol}/>
<PowerGauge val={data.i ? data.i : 0} max={data.im ? data.im : 15000} unit="W" label="Import" sub={data.p} subunit={data.pc} colorFn={ampcol}/>
</div>
<div>{data.mt ? metertype(data.mt) : '-'}</div>
<div class="text-right">{data.ic ? data.ic.toFixed(1) : '-'} kWh</div>
@ -72,7 +72,7 @@
{/if}
{#if uiVisibility(sysinfo.ui.c, data.ea)}
<div class="cnt">
<AccountingData data={data.ea} currency={prices.currency} hasExport={data.om > 0 || data.e > 0}/>
<AccountingData data={data.ea} currency={data.pc} hasExport={data.om > 0 || data.e > 0}/>
</div>
{/if}
{#if uiVisibility(sysinfo.ui.t, data.pr && (data.pr.startsWith("10YNO") || data.pr == '10Y1001A1001A48H'))}
@ -80,7 +80,7 @@
<TariffPeakChart />
</div>
{/if}
{#if uiVisibility(sysinfo.ui.p, (typeof data.p == "number") && !Number.isNaN(data.p))}
{#if uiVisibility(sysinfo.ui.p, data.pe && !Number.isNaN(data.p))}
<div class="cnt gwf">
<PricePlot json={prices}/>
</div>

View File

@ -50,7 +50,7 @@ export const dataStore = readable(data, (set) => {
lastTemp = data.t;
setTimeout(getTemperatures, 2000);
}
if(lastPrice != data.p) {
if(lastPrice != data.p && data.pe) {
lastPrice = data.p;
setTimeout(getPrices, 4000);
}

View File

@ -38,7 +38,7 @@ public:
void setTimezone(Timezone* tz);
void setMqttEnabled(bool);
void setEntsoeApi(EntsoeApi* eapi);
void setPriceRegion(String);
void setPriceSettings(String region, String currency);
private:
RemoteDebug* debugger;
@ -61,6 +61,7 @@ private:
bool performUpgrade = false;
bool rebootForUpgrade = false;
String priceRegion = "";
String priceCurrency = "";
#if defined(AMS2MQTT_FIRMWARE_URL)
String customFirmwareUrl = AMS2MQTT_FIRMWARE_URL;
#else

View File

@ -3,5 +3,6 @@
"t": "%s",
"r": "%s",
"c": "%s",
"m": %.3f
"m": %.3f,
"f": %s
},

View File

@ -56,7 +56,9 @@
"i" : %.2f
}
},
"pe" : %s,
"pr" : "%s",
"pc" : "%s",
"he" : %d,
"ee" : %d,
"c" : %lu,

View File

@ -413,9 +413,7 @@ void AmsWebServer::dataJson() {
mqttStatus = 3;
}
float price = ENTSOE_NO_VALUE;
if(eapi != NULL)
price = eapi->getValueForHour(0);
float price = ea->getPriceForHour(0);
String peaks = "";
for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) {
@ -475,7 +473,9 @@ void AmsWebServer::dataJson() {
ea->getCostThisMonth(),
ea->getProducedThisMonth(),
ea->getIncomeThisMonth(),
eapi == NULL ? "" : priceRegion.c_str(),
eapi == NULL ? "false" : "true",
priceRegion.c_str(),
priceCurrency.c_str(),
meterState->getLastError(),
eapi == NULL ? 0 : eapi->getLastError(),
(uint32_t) now,
@ -902,7 +902,8 @@ void AmsWebServer::configurationJson() {
entsoe.token,
entsoe.area,
entsoe.currency,
entsoe.multiplier / 1000.0
entsoe.multiplier / 1000.0,
entsoe.fixedPrice == 0 ? "null" : String(entsoe.fixedPrice / 1000.0, 10).c_str()
);
server.sendContent(buf);
snprintf_P(buf, BufferSize, CONF_DEBUG_JSON,
@ -1423,6 +1424,7 @@ void AmsWebServer::handleSave() {
strcpy(entsoe.area, priceRegion.c_str());
strcpy(entsoe.currency, server.arg(F("pc")).c_str());
entsoe.multiplier = server.arg(F("pm")).toFloat() * 1000;
entsoe.fixedPrice = server.arg(F("pf")).toFloat() * 1000;
config->setEntsoeConfig(entsoe);
}
@ -1884,8 +1886,9 @@ void AmsWebServer::tariffJson() {
server.send(200, MIME_JSON, buf);
}
void AmsWebServer::setPriceRegion(String priceRegion) {
this->priceRegion = priceRegion;
void AmsWebServer::setPriceSettings(String region, String currency) {
this->priceRegion = region;
this->priceCurrency = currency;
}
void AmsWebServer::configFileDownload() {

View File

@ -172,7 +172,8 @@ void setup() {
eapi->setup(entsoe);
ws.setEntsoeApi(eapi);
}
ws.setPriceRegion(entsoe.area);
ws.setPriceSettings(entsoe.area, entsoe.currency);
ea.setFixedPrice(entsoe.fixedPrice / 1000.0);
bool shared = false;
config.getMeterConfig(meterConfig);
Serial.flush();
@ -537,8 +538,9 @@ void loop() {
eapi = NULL;
ws.setEntsoeApi(NULL);
}
ws.setPriceRegion(entsoe.area);
ws.setPriceSettings(entsoe.area, entsoe.currency);
config.ackEntsoeChange();
ea.setFixedPrice(entsoe.fixedPrice / 1000.0);
}
} catch(const std::exception& e) {
debugE("Exception in ENTSO-E loop (%s)", e.what());