[awattar] Add TimeSeries support (#17172)

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2024-08-07 00:00:13 +02:00 committed by GitHub
parent 01488dd5fb
commit 81251c3b35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 122 additions and 11 deletions

View File

@ -60,6 +60,17 @@ Also, due to the time the aWATTar API delivers the data for the next day, it doe
## Channels
### Bridge
The bridge has two channels which support a time-series:
| channel | type | description |
| ------------ |--------------------| --------------------------------------------------------------------------------------------------------------------------------------- |
| market-net | Number:EnergyPrice | This net market price per kWh. This is directly taken from the price the aWATTar API delivers. |
| total-net | Number:EnergyPrice | Sum of net market price and configured base price |
If you need gross prices, please use the [VAT profile](https://www.openhab.org/addons/transformations/vat/).
### Prices Thing
For every hour, the `prices` thing provides the following prices:

View File

@ -14,7 +14,7 @@ package org.openhab.binding.awattar.internal.handler;
import static org.eclipse.jetty.http.HttpMethod.GET;
import static org.eclipse.jetty.http.HttpStatus.OK_200;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.BINDING_ID;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.*;
import java.time.Instant;
import java.time.LocalDate;
@ -27,6 +27,9 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -37,6 +40,8 @@ import org.openhab.binding.awattar.internal.AwattarPrice;
import org.openhab.binding.awattar.internal.dto.AwattarApiData;
import org.openhab.binding.awattar.internal.dto.Datum;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.CurrencyUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
@ -44,6 +49,8 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.TimeSeries;
import org.openhab.core.types.util.UnitUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -152,13 +159,30 @@ public class AwattarBridgeHandler extends BaseBridgeHandler {
SortedSet<AwattarPrice> result = new TreeSet<>(Comparator.comparing(AwattarPrice::timerange));
AwattarApiData apiData = gson.fromJson(content, AwattarApiData.class);
if (apiData != null) {
TimeSeries netMarketSeries = new TimeSeries(TimeSeries.Policy.REPLACE);
TimeSeries netTotalSeries = new TimeSeries(TimeSeries.Policy.REPLACE);
Unit<?> priceUnit = getPriceUnit();
for (Datum d : apiData.data) {
double netPrice = d.marketprice / 10.0;
TimeRange timerange = new TimeRange(d.startTimestamp, d.endTimestamp);
result.add(new AwattarPrice(netPrice, netPrice * vatFactor, netPrice + basePrice,
(netPrice + basePrice) * vatFactor, timerange));
double netMarket = d.marketprice / 10.0;
double grossMarket = netMarket * vatFactor;
double netTotal = netMarket + basePrice;
double grossTotal = netTotal * vatFactor;
Instant timestamp = Instant.ofEpochMilli(d.startTimestamp);
netMarketSeries.add(timestamp, new QuantityType<>(netMarket / 100.0, priceUnit));
netTotalSeries.add(timestamp, new QuantityType<>(netTotal / 100.0, priceUnit));
result.add(new AwattarPrice(netMarket, grossMarket, netTotal, grossTotal,
new TimeRange(d.startTimestamp, d.endTimestamp)));
}
prices = result;
// update channels
sendTimeSeries(CHANNEL_MARKET_NET, netMarketSeries);
sendTimeSeries(CHANNEL_TOTAL_NET, netTotalSeries);
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
@ -179,6 +203,29 @@ public class AwattarBridgeHandler extends BaseBridgeHandler {
}
}
private Unit<?> getPriceUnit() {
Unit<?> priceUnit = UnitUtils.parseUnit("EUR/kWh");
if (priceUnit == null) {
priceUnit = CurrencyUnits.BASE_ENERGY_PRICE;
logger.info("Using {} instead of EUR/kWh, because it is not available", priceUnit);
}
return priceUnit;
}
private void createAndSendTimeSeries(String channelId, Function<AwattarPrice, Double> valueFunction) {
SortedSet<AwattarPrice> prices = getPrices();
Unit<?> priceUnit = getPriceUnit();
if (prices == null) {
return;
}
TimeSeries timeSeries = new TimeSeries(TimeSeries.Policy.REPLACE);
prices.forEach(p -> {
timeSeries.add(Instant.ofEpochMilli(p.timerange().start()),
new QuantityType<>(valueFunction.apply(p) / 100.0, priceUnit));
});
sendTimeSeries(channelId, timeSeries);
}
/**
* Check if the data needs to be refreshed.
*
@ -253,9 +300,10 @@ public class AwattarBridgeHandler extends BaseBridgeHandler {
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refresh();
} else {
logger.debug("Binding {} only supports refresh command", BINDING_ID);
switch (channelUID.getId()) {
case CHANNEL_MARKET_NET -> createAndSendTimeSeries(CHANNEL_MARKET_NET, AwattarPrice::netPrice);
case CHANNEL_TOTAL_NET -> createAndSendTimeSeries(CHANNEL_TOTAL_NET, AwattarPrice::netTotal);
}
}
}
}

View File

@ -14,12 +14,12 @@
<option value="AT">AT</option>
</options>
</parameter>
<parameter name="vatPercent" type="decimal">
<parameter name="vatPercent" type="decimal" min="0">
<label>VAT Percent</label>
<description>Specifies the value added tax percentage</description>
<default>19</default>
</parameter>
<parameter name="basePrice" type="decimal">
<parameter name="basePrice" type="decimal" min="0" step="0.001">
<label>Base Price</label>
<description>Specifies the net base price per kWh</description>
<default>0</default>

View File

@ -7,6 +7,30 @@
<bridge-type id="bridge">
<label>aWATTar Bridge</label>
<description>Provides price data from the aWATTar API.</description>
<channels>
<channel id="market-net" typeId="uom-price">
<label>Net Market Price</label>
<description>Price without VAT and network charge</description>
</channel>
<channel id="market-gross" typeId="uom-price">
<label>Gross Market Price</label>
<description>Price with VAT but without network charge</description>
</channel>
<channel id="total-net" typeId="uom-price">
<label>Net Total Price</label>
<description>Price with network charge but without VAT</description>
</channel>
<channel id="total-gross" typeId="uom-price">
<label>Gross Total Price</label>
<description>Price with network charge and VAT</description>
</channel>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<config-description-ref uri="bridge-type:awattar:bridge"/>
</bridge-type>
@ -250,6 +274,12 @@
<config-description-ref uri="thing-type:awattar:bestprice"/>
</thing-type>
<channel-type id="uom-price">
<item-type>Number:EnergyPrice</item-type>
<label>Price</label>
<state readOnly="true" pattern="%.3f %unit%"/>
</channel-type>
<channel-type id="price">
<item-type>Number</item-type>
<label>Price</label>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
<thing-type uid="awattar:bridge">
<instruction-set targetVersion="1">
<add-channel id="market-net">
<type>awattar:uom-price</type>
<label>Net Market Price</label>
<description>Price without VAT and network charge</description>
</add-channel>
<add-channel id="total-net">
<type>awattar:uom-price</type>
<label>Net Total Price</label>
<description>Price with network charge but without VAT</description>
</add-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>

View File

@ -92,6 +92,7 @@ public class AwattarBridgeHandlerRefreshTest extends JavaTest {
when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.of("GMT+2"));
when(bridgeMock.getUID()).thenReturn(BRIDGE_UID);
bridgeHandler = new AwattarBridgeHandler(bridgeMock, httpClientMock, timeZoneProviderMock);
bridgeHandler.setCallback(bridgeCallbackMock);

View File

@ -105,10 +105,10 @@ public class AwattarBridgeHandlerTest extends JavaTest {
when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.of("GMT+2"));
when(bridgeMock.getUID()).thenReturn(BRIDGE_UID);
bridgeHandler = new AwattarBridgeHandler(bridgeMock, httpClientMock, timeZoneProviderMock);
bridgeHandler.setCallback(bridgeCallbackMock);
bridgeHandler.refreshIfNeeded();
when(bridgeMock.getHandler()).thenReturn(bridgeHandler);
// other mocks