mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-26 15:21:41 +01:00
[awattar] Add TimeSeries support (#17172)
Signed-off-by: Jan N. Klug <github@klug.nrw> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
7861ccfd59
commit
833724b8b1
@ -60,6 +60,17 @@ Also, due to the time the aWATTar API delivers the data for the next day, it doe
|
|||||||
|
|
||||||
## Channels
|
## 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
|
### Prices Thing
|
||||||
|
|
||||||
For every hour, the `prices` thing provides the following prices:
|
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.HttpMethod.GET;
|
||||||
import static org.eclipse.jetty.http.HttpStatus.OK_200;
|
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.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@ -27,6 +27,9 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.AwattarApiData;
|
||||||
import org.openhab.binding.awattar.internal.dto.Datum;
|
import org.openhab.binding.awattar.internal.dto.Datum;
|
||||||
import org.openhab.core.i18n.TimeZoneProvider;
|
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.Bridge;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
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.thing.binding.BaseBridgeHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -152,13 +159,30 @@ public class AwattarBridgeHandler extends BaseBridgeHandler {
|
|||||||
SortedSet<AwattarPrice> result = new TreeSet<>(Comparator.comparing(AwattarPrice::timerange));
|
SortedSet<AwattarPrice> result = new TreeSet<>(Comparator.comparing(AwattarPrice::timerange));
|
||||||
AwattarApiData apiData = gson.fromJson(content, AwattarApiData.class);
|
AwattarApiData apiData = gson.fromJson(content, AwattarApiData.class);
|
||||||
if (apiData != null) {
|
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) {
|
for (Datum d : apiData.data) {
|
||||||
double netPrice = d.marketprice / 10.0;
|
double netMarket = d.marketprice / 10.0;
|
||||||
TimeRange timerange = new TimeRange(d.startTimestamp, d.endTimestamp);
|
double grossMarket = netMarket * vatFactor;
|
||||||
result.add(new AwattarPrice(netPrice, netPrice * vatFactor, netPrice + basePrice,
|
double netTotal = netMarket + basePrice;
|
||||||
(netPrice + basePrice) * vatFactor, timerange));
|
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;
|
prices = result;
|
||||||
|
|
||||||
|
// update channels
|
||||||
|
sendTimeSeries(CHANNEL_MARKET_NET, netMarketSeries);
|
||||||
|
sendTimeSeries(CHANNEL_TOTAL_NET, netTotalSeries);
|
||||||
|
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
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.
|
* Check if the data needs to be refreshed.
|
||||||
*
|
*
|
||||||
@ -253,9 +300,10 @@ public class AwattarBridgeHandler extends BaseBridgeHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (command instanceof RefreshType) {
|
if (command instanceof RefreshType) {
|
||||||
refresh();
|
switch (channelUID.getId()) {
|
||||||
} else {
|
case CHANNEL_MARKET_NET -> createAndSendTimeSeries(CHANNEL_MARKET_NET, AwattarPrice::netPrice);
|
||||||
logger.debug("Binding {} only supports refresh command", BINDING_ID);
|
case CHANNEL_TOTAL_NET -> createAndSendTimeSeries(CHANNEL_TOTAL_NET, AwattarPrice::netTotal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
<option value="AT">AT</option>
|
<option value="AT">AT</option>
|
||||||
</options>
|
</options>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="vatPercent" type="decimal">
|
<parameter name="vatPercent" type="decimal" min="0">
|
||||||
<label>VAT Percent</label>
|
<label>VAT Percent</label>
|
||||||
<description>Specifies the value added tax percentage</description>
|
<description>Specifies the value added tax percentage</description>
|
||||||
<default>19</default>
|
<default>19</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="basePrice" type="decimal">
|
<parameter name="basePrice" type="decimal" min="0" step="0.001">
|
||||||
<label>Base Price</label>
|
<label>Base Price</label>
|
||||||
<description>Specifies the net base price per kWh</description>
|
<description>Specifies the net base price per kWh</description>
|
||||||
<default>0</default>
|
<default>0</default>
|
||||||
|
@ -7,6 +7,30 @@
|
|||||||
<bridge-type id="bridge">
|
<bridge-type id="bridge">
|
||||||
<label>aWATTar Bridge</label>
|
<label>aWATTar Bridge</label>
|
||||||
<description>Provides price data from the aWATTar API.</description>
|
<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"/>
|
<config-description-ref uri="bridge-type:awattar:bridge"/>
|
||||||
</bridge-type>
|
</bridge-type>
|
||||||
|
|
||||||
@ -250,6 +274,12 @@
|
|||||||
<config-description-ref uri="thing-type:awattar:bestprice"/>
|
<config-description-ref uri="thing-type:awattar:bestprice"/>
|
||||||
</thing-type>
|
</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">
|
<channel-type id="price">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Price</label>
|
<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(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.of("GMT+2"));
|
||||||
|
|
||||||
|
when(bridgeMock.getUID()).thenReturn(BRIDGE_UID);
|
||||||
bridgeHandler = new AwattarBridgeHandler(bridgeMock, httpClientMock, timeZoneProviderMock);
|
bridgeHandler = new AwattarBridgeHandler(bridgeMock, httpClientMock, timeZoneProviderMock);
|
||||||
bridgeHandler.setCallback(bridgeCallbackMock);
|
bridgeHandler.setCallback(bridgeCallbackMock);
|
||||||
|
|
||||||
|
@ -105,10 +105,10 @@ public class AwattarBridgeHandlerTest extends JavaTest {
|
|||||||
|
|
||||||
when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.of("GMT+2"));
|
when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.of("GMT+2"));
|
||||||
|
|
||||||
|
when(bridgeMock.getUID()).thenReturn(BRIDGE_UID);
|
||||||
bridgeHandler = new AwattarBridgeHandler(bridgeMock, httpClientMock, timeZoneProviderMock);
|
bridgeHandler = new AwattarBridgeHandler(bridgeMock, httpClientMock, timeZoneProviderMock);
|
||||||
bridgeHandler.setCallback(bridgeCallbackMock);
|
bridgeHandler.setCallback(bridgeCallbackMock);
|
||||||
bridgeHandler.refreshIfNeeded();
|
bridgeHandler.refreshIfNeeded();
|
||||||
|
|
||||||
when(bridgeMock.getHandler()).thenReturn(bridgeHandler);
|
when(bridgeMock.getHandler()).thenReturn(bridgeHandler);
|
||||||
|
|
||||||
// other mocks
|
// other mocks
|
||||||
|
Loading…
Reference in New Issue
Block a user