mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[awattar] Add TimeSeries support (#17172)
Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
parent
01488dd5fb
commit
81251c3b35
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user