mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[aWATTar] added inverted best price (#16877)
Signed-off-by: Thomas Leber <thomas@tl-photography.at>
This commit is contained in:
parent
8c392da542
commit
f7bcbe1575
@ -26,12 +26,12 @@ Auto discovery is not supported.
|
||||
|
||||
### aWATTar Bridge
|
||||
|
||||
| Parameter | Description |
|
||||
|-------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| vatPercent | Percentage of the value added tax to apply to net prices. Optional, defaults to 19. |
|
||||
| basePrice | The net(!) base price you have to pay for every kWh. Optional, but you most probably want to set it based on you delivery contract. |
|
||||
| timeZone | The time zone the hour definitions of the things below refer to. Default is `CET`, as it corresponds to the aWATTar API. It is strongly recommended not to change this. However, if you do so, be aware that the prices delivered by the API will not cover a whole calendar day in this timezone. **Advanced** |
|
||||
| country | The country prices should be received for. Use `DE` for Germany or `AT` for Austria. `DE` is the default. |
|
||||
| Parameter | Description |
|
||||
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| vatPercent | Percentage of the value added tax to apply to net prices. Optional, defaults to 19. |
|
||||
| basePrice | The net(!) base price you have to pay for every kWh. Optional, but you most probably want to set it based on you delivery contract. |
|
||||
| timeZone | The time zone the hour definitions of the things below refer to. Default is `CET`, as it corresponds to the aWATTar API. It is strongly recommended not to change this. However, if you do so, be aware that the prices delivered by the API will not cover a whole calendar day in this timezone. **Advanced** |
|
||||
| country | The country prices should be received for. Use `DE` for Germany or `AT` for Austria. `DE` is the default. |
|
||||
|
||||
### Prices Thing
|
||||
|
||||
@ -39,12 +39,13 @@ The prices thing does not need any configuration.
|
||||
|
||||
### Bestprice Thing
|
||||
|
||||
| Parameter | Description |
|
||||
|-------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| rangeStart | First hour of the time range the binding should search for the best prices. Default: `0` |
|
||||
| rangeDuration | The duration of the time range the binding should search for best prices. Default: `24` |
|
||||
| length | number of best price hours to find within the range. This value has to be at least `1` and below `rangeDuration` Default: `1` |
|
||||
| consecutive | if `true`, the thing identifies the cheapest consecutive range of `length` hours within the lookup range. Otherwise, the thing contains the cheapest `length` hours within the lookup range. Default: `true` |
|
||||
| Parameter | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| rangeStart | First hour of the time range the binding should search for the best prices. Default: `0` |
|
||||
| rangeDuration | The duration of the time range the binding should search for best prices. Default: `24` |
|
||||
| length | number of best price hours to find within the range. This value has to be at least `1` and below `rangeDuration` Default: `1` |
|
||||
| consecutive | if `true`, the thing identifies the cheapest consecutive range of `length` hours within the lookup range. Otherwise, the thing contains the cheapest `length` hours within the lookup range. Default: `true` |
|
||||
| inverted | if `true`, the worst prices will be searched instead of the best. Does currently not work in combination with 'consecutive'. Default: `false` |
|
||||
|
||||
#### Limitations
|
||||
|
||||
@ -59,31 +60,31 @@ Also, due to the time the aWATTar API delivers the data for the next day, it doe
|
||||
|
||||
For every hour, the `prices` thing provides the following prices:
|
||||
|
||||
| channel | type | description |
|
||||
|----------|--------|------------------------------|
|
||||
| market-net | Number | This net market price per kWh. This is directly taken from the price the aWATTar API delivers. |
|
||||
| market-gross | Number | The market price including VAT, using the defined VAT percentage. |
|
||||
| total-net | Number | Sum of net market price and configured base price |
|
||||
| total-gross | Number | Sum of market and base price with VAT applied. Most probably this is the final price you will have to pay for one kWh in a certain hour |
|
||||
| channel | type | description |
|
||||
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| market-net | Number | This net market price per kWh. This is directly taken from the price the aWATTar API delivers. |
|
||||
| market-gross | Number | The market price including VAT, using the defined VAT percentage. |
|
||||
| total-net | Number | Sum of net market price and configured base price |
|
||||
| total-gross | Number | Sum of market and base price with VAT applied. Most probably this is the final price you will have to pay for one kWh in a certain hour |
|
||||
|
||||
All prices are available in each of the following channel groups:
|
||||
|
||||
| channel group | description |
|
||||
|----------|--------------------------------|
|
||||
| current | The prices for the current hour |
|
||||
| today00, today01, today02 ... today23 | Hourly prices for today. `today00` provides the price from 0:00 to 1:00, `today01` from 1:00 to 02:00 and so on. As long as the API is working, this data should always be available |
|
||||
| tomorrow00, tomorrow01, ... tomorrow23 | Hourly prices for the next day. They should be available starting at 14:00. |
|
||||
| channel group | description |
|
||||
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| current | The prices for the current hour |
|
||||
| today00, today01, today02 ... today23 | Hourly prices for today. `today00` provides the price from 0:00 to 1:00, `today01` from 1:00 to 02:00 and so on. As long as the API is working, this data should always be available |
|
||||
| tomorrow00, tomorrow01, ... tomorrow23 | Hourly prices for the next day. They should be available starting at 14:00. |
|
||||
|
||||
### Bestprice Thing
|
||||
|
||||
| channel | type | description |
|
||||
|----------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| active | Switch | `ON` if the current time is within the bestprice period, `OFF` otherwise. If `consecutive` was set to `false`, this channel may change between `ON` and `OFF` multiple times within the bestprice period. |
|
||||
| start | DateTime | The exact start time of the bestprice range. If `consecutive` was `false`, it is the start time of the first hour found. |
|
||||
| end | DateTime | The exact end time of the bestprice range. If `consecutive` was `false`, it is the end time of the last hour found. |
|
||||
| countdown | Number:Time | The time in minutes until start of the bestprice range. If start time passed. the channel will be set to `UNDEFINED` until the values for the next day are available. |
|
||||
| remaining | Number:Time | The time in minutes until end of the bestprice range. If start time passed. the channel will be set to `UNDEFINED` until the values for the next day are available. |
|
||||
| hours | String | A comma separated list of hours this bestprice period contains. |
|
||||
| channel | type | description |
|
||||
| --------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| active | Switch | `ON` if the current time is within the bestprice period, `OFF` otherwise. If `consecutive` was set to `false`, this channel may change between `ON` and `OFF` multiple times within the bestprice period. |
|
||||
| start | DateTime | The exact start time of the bestprice range. If `consecutive` was `false`, it is the start time of the first hour found. |
|
||||
| end | DateTime | The exact end time of the bestprice range. If `consecutive` was `false`, it is the end time of the last hour found. |
|
||||
| countdown | Number:Time | The time in minutes until start of the bestprice range. If start time passed. the channel will be set to `UNDEFINED` until the values for the next day are available. |
|
||||
| remaining | Number:Time | The time in minutes until end of the bestprice range. If start time passed. the channel will be set to `UNDEFINED` until the values for the next day are available. |
|
||||
| hours | String | A comma separated list of hours this bestprice period contains. |
|
||||
|
||||
## Full Example
|
||||
|
||||
@ -93,7 +94,7 @@ awattar.things:
|
||||
|
||||
```java
|
||||
Bridge awattar:bridge:bridge1 "aWATTar Bridge" [ country="DE", vatPercent="19", basePrice="17.22"] {
|
||||
Thing prices price1 "aWATTar Price" []
|
||||
Thing prices price1 "aWATTar Price" []
|
||||
// The car should be loaded for 4 hours during the night
|
||||
Thing bestprice carloader "Car Loader" [ rangeStart="22", rangeDuration="8", length="4", consecutive="true" ]
|
||||
// In the cheapest hour of the night the garden should be watered
|
||||
|
@ -20,11 +20,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
* @author Wolfgang Klimt - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AwattarBestpriceConfiguration {
|
||||
public class AwattarBestPriceConfiguration {
|
||||
public int rangeStart = 0;
|
||||
public int rangeDuration = 24;
|
||||
public int length = 1;
|
||||
public boolean consecutive = true;
|
||||
public boolean inverted = false;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
@ -28,6 +28,7 @@ import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
@ -36,8 +37,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.awattar.internal.AwattarBestPriceConfiguration;
|
||||
import org.openhab.binding.awattar.internal.AwattarBestPriceResult;
|
||||
import org.openhab.binding.awattar.internal.AwattarBestpriceConfiguration;
|
||||
import org.openhab.binding.awattar.internal.AwattarConsecutiveBestPriceResult;
|
||||
import org.openhab.binding.awattar.internal.AwattarNonConsecutiveBestPriceResult;
|
||||
import org.openhab.binding.awattar.internal.AwattarPrice;
|
||||
@ -60,28 +61,28 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AwattarBestpriceHandler} is responsible for computing the best prices for a given configuration.
|
||||
* The {@link AwattarBestPriceHandler} is responsible for computing the best prices for a given configuration.
|
||||
*
|
||||
* @author Wolfgang Klimt - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AwattarBestpriceHandler extends BaseThingHandler {
|
||||
public class AwattarBestPriceHandler extends BaseThingHandler {
|
||||
private static final int THING_REFRESH_INTERVAL = 60;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AwattarBestpriceHandler.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(AwattarBestPriceHandler.class);
|
||||
|
||||
private @Nullable ScheduledFuture<?> thingRefresher;
|
||||
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
public AwattarBestpriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
|
||||
public AwattarBestPriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
|
||||
super(thing);
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
AwattarBestpriceConfiguration config = getConfigAs(AwattarBestpriceConfiguration.class);
|
||||
AwattarBestPriceConfiguration config = getConfigAs(AwattarBestPriceConfiguration.class);
|
||||
|
||||
if (config.length >= config.rangeDuration) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.length.value");
|
||||
@ -137,7 +138,7 @@ public class AwattarBestpriceHandler extends BaseThingHandler {
|
||||
updateState(channelUID, state);
|
||||
return;
|
||||
}
|
||||
AwattarBestpriceConfiguration config = getConfigAs(AwattarBestpriceConfiguration.class);
|
||||
AwattarBestPriceConfiguration config = getConfigAs(AwattarBestPriceConfiguration.class);
|
||||
TimeRange timerange = getRange(config.rangeStart, config.rangeDuration, bridgeHandler.getTimeZone());
|
||||
if (!(bridgeHandler.containsPriceFor(timerange.start()) && bridgeHandler.containsPriceFor(timerange.end()))) {
|
||||
updateState(channelUID, state);
|
||||
@ -162,15 +163,20 @@ public class AwattarBestpriceHandler extends BaseThingHandler {
|
||||
result = res;
|
||||
} else {
|
||||
range.sort(Comparator.naturalOrder());
|
||||
|
||||
// sort in descending order when inverted
|
||||
if (config.inverted) {
|
||||
Collections.reverse(range);
|
||||
}
|
||||
|
||||
AwattarNonConsecutiveBestPriceResult res = new AwattarNonConsecutiveBestPriceResult(
|
||||
bridgeHandler.getTimeZone());
|
||||
int ct = 0;
|
||||
for (AwattarPrice price : range) {
|
||||
res.addMember(price);
|
||||
if (++ct >= config.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
// take up to config.length prices
|
||||
for (int i = 0; i < Math.min(config.length, range.size()); i++) {
|
||||
res.addMember(range.get(i));
|
||||
}
|
||||
|
||||
result = res;
|
||||
}
|
||||
String channelId = channelUID.getIdWithoutGroup();
|
@ -36,7 +36,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AwattarHandlerFactory} is responsible for creating things and thing
|
||||
* The {@link AwattarHandlerFactory} is responsible for creating things and
|
||||
* thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Wolfgang Klimt - Initial contribution
|
||||
@ -72,7 +73,7 @@ public class AwattarHandlerFactory extends BaseThingHandlerFactory {
|
||||
} else if (THING_TYPE_PRICE.equals(thingTypeUID)) {
|
||||
return new AwattarPriceHandler(thing, timeZoneProvider);
|
||||
} else if (THING_TYPE_BESTPRICE.equals(thingTypeUID)) {
|
||||
return new AwattarBestpriceHandler(thing, timeZoneProvider);
|
||||
return new AwattarBestPriceHandler(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
logger.warn("Unknown thing type {}, not creating handler!", thingTypeUID);
|
||||
|
@ -47,6 +47,11 @@
|
||||
<description>Do the hours need to be consecutive?</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="inverted" type="boolean">
|
||||
<label>Inverted</label>
|
||||
<description>Should the highest prices be returned?</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
|
@ -28,6 +28,8 @@ thing-type.config.awattar.bestprice.length.label = Length
|
||||
thing-type.config.awattar.bestprice.length.description = The number of hours the bestprice period should last
|
||||
thing-type.config.awattar.bestprice.consecutive.label = Consecutive
|
||||
thing-type.config.awattar.bestprice.consecutive.description = Do the hours need to be consecutive?
|
||||
thing-type.config.awattar.bestprice.inverted.label = Inverted
|
||||
thing-type.config.awattar.bestprice.inverted.description = Invert the search for the highest price
|
||||
|
||||
# channel types
|
||||
channel-type.awattar.price.label = ct/kWh
|
||||
|
@ -28,6 +28,8 @@ thing-type.config.awattar.bestprice.length.label = Länge
|
||||
thing-type.config.awattar.bestprice.length.description = Die Anzahl der zu findenden günstigen Stunden
|
||||
thing-type.config.awattar.bestprice.consecutive.label = Durchgehend
|
||||
thing-type.config.awattar.bestprice.consecutive.description = Wird ein einzelner durchgehender Zeitraum gesucht?
|
||||
thing-type.config.awattar.bestprice.inverted.label = Invertiert
|
||||
thing-type.config.awattar.bestprice.inverted.description = Wird nach den teuersten Stunden gesucht?
|
||||
|
||||
# channel types
|
||||
channel-type.awattar.price.label = ct/kWh
|
||||
|
@ -278,6 +278,13 @@
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="input-inverted">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Inverted</label>
|
||||
<description>Return highest prices?</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
|
||||
<channel-type id="switch-type">
|
||||
<item-type>Switch</item-type>
|
||||
|
@ -177,7 +177,7 @@ public class AwattarBridgeHandlerTest extends JavaTest {
|
||||
Map<String, Object> config = Map.of("length", length, "consecutive", consecutive);
|
||||
when(bestpriceMock.getConfiguration()).thenReturn(new Configuration(config));
|
||||
|
||||
AwattarBestpriceHandler handler = new AwattarBestpriceHandler(bestpriceMock, timeZoneProviderMock) {
|
||||
AwattarBestPriceHandler handler = new AwattarBestPriceHandler(bestpriceMock, timeZoneProviderMock) {
|
||||
@Override
|
||||
protected TimeRange getRange(int start, int duration, ZoneId zoneId) {
|
||||
return new TimeRange(1718402400000L, 1718488800000L);
|
||||
|
Loading…
Reference in New Issue
Block a user