[awattar] Initial contribution (#11976)

* First alpha version of the awattar binding

Signed-off-by: wolfii <wolfgang.klimt@consol.de>
Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Corrected typos

Signed-off-by: wolfii <wolfgang.klimt@consol.de>
Signed-off-by: Wolfgang Klimt <github@klimt.de>

* More typos

Signed-off-by: wolfii <wolfgang.klimt@consol.de>
Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Improved time handling to consider time zone.

Signed-off-by: wolfii <wolfgang.klimt@consol.de>
Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Corrected logical time problem, start adding nextprice thing

Signed-off-by: wolfii <wolfgang.klimt@consol.de>
Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Added support for Austria

Signed-off-by: wolfii <wolfgang.klimt@consol.de>
Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Use List instead of Set

Signed-off-by: wolfii <wolfgang.klimt@consol.de>
Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Minor corrections

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Removed unneeded handler, corrected fetching of prices

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Added i18n, updated documentation

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Corrected pom.xml after rebase

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Updated version to 3.3.0-SNAPSHOT

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Corrected findings of Code analysis tool

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Updated copyright notice

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Updates to get rid of compiler warnings

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Worked on review comments from @fwolter

Obeyed most of the review comments. Exceptions:

* binding is already added to bom/openhab-addons/pom.xml (at least I found it there and there was a commit notice in git log)
* mvn license:format brought back 2021, so I manually set everything to 2022. Should I try to rebase my whole branch?
* In two places the binding needs to adjust to minute boundaries, hence scheduleWithFixedDelay will not work.
* I removed empty trailing lines, but mvn spotless:apply brought them back
* The OhInfXmlUsageCheck seems to be wrong.
* The ConstantNameCheck in AwattarUtil seems to be mislead by the fact that all members of the class are static, including the logger. From my point of view it is not a real "constant".

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Updated Readme to match code changes

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Further work on review comments from @fwolter

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Corrected config definition

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Changed Copyright to 2022

Signed-off-by: Wolfgang Klimt <github@klimt.de>

* Review comments from @fwolter. Improved timezone handling

Signed-off-by: Wolfgang Klimt <github@klimt.de>

Co-authored-by: wolfii <wolfgang.klimt@consol.de>
This commit is contained in:
Wolfgang Klimt 2022-05-02 08:22:47 +02:00 committed by GitHub
parent 3cac11b16b
commit 61de1a5387
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2420 additions and 0 deletions

View File

@ -29,6 +29,7 @@
/bundles/org.openhab.binding.autelis/ @digitaldan
/bundles/org.openhab.binding.automower/ @maxpg
/bundles/org.openhab.binding.avmfritz/ @cweitkamp
/bundles/org.openhab.binding.awattar/ @Wolfgang1966
/bundles/org.openhab.binding.benqprojector/ @mlobstein
/bundles/org.openhab.binding.bigassfan/ @mhilbush
/bundles/org.openhab.binding.bluetooth/ @cdjackson @cpmeister

View File

@ -136,6 +136,11 @@
<artifactId>org.openhab.binding.avmfritz</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.awattar</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.benqprojector</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,263 @@
# aWATTar Binding
This binding provides access to the hourly prices for electricity for the German and Austrian provider aWATTar.
## Supported Things
There are three supported things.
### aWATTar Bridge
The `bridge` reads price data from the aWATTar API and stores the (optional) config values for VAT and energy base price.
### Prices Thing
The `prices` Thing provides todays and (after 14:00) tomorrows net and gross prices.
### Bestprice Thing
The `bestprice` Thing identifies the hours with the cheapest prices based on the given parameters.
## Discovery
Auto discovery is not supported.
## Thing Configuration
### 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. |
### Prices Thing
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` |
#### Limitations
The channels of a bestprice thing are only defined when the binding has enough data to compute them.
The thing is recomputed after the end of the candidate time range for the next day, but only as soon as data for the next day is available from the aWATTar API, which is around 14:00.
So for a bestprice thing with `[ rangeStart=5, rangeDuration=5 ]` all channels will be undefined from 10:00 to 14:00.
Also, due to the time the aWATTar API delivers the data for the next day, it doesn't make sense to define a thing with `[ rangeStart=12, rangeDuration=20 ]` as the binding will be able to compute the channels only after 14:00.
## Channels
### Prices Thing
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 |
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. |
### 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. |
## Full Example
### Things
awattar.things:
```
Bridge awattar:bridge:bridge1 "aWATTar Bridge" [ country="DE", vatPercent="19", basePrice="17.22"] {
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
Thing bestprice water "Water timer" [ rangeStart="19", rangeDuration="12", length="1" ]
// The heatpump should run the 12 cheapest hours per day
Thing bestprice heatpump "Heat pump" [ length="12", consecutive="false" ]
}
```
### Items
awattar.items:
```
Number:Dimensionless currentnet "Current price [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:current#market-net" }
Number:Dimensionless currentgross "Current price [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:current#market-gross" }
Number:Dimensionless totalnet "Current price [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:current#total-net" }
Number:Dimensionless totalgross "Current price [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:current#total-gross" }
Number:Dimensionless totalgross "Current price [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:current#total-gross" }
Number:Dimensionless today00 "Today 00-01 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today00#total-gross" }
Number:Dimensionless today01 "Today 01-02 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today01#total-gross" }
Number:Dimensionless today02 "Today 02-03 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today02#total-gross" }
Number:Dimensionless today03 "Today 03-04 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today03#total-gross" }
Number:Dimensionless today04 "Today 04-05 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today04#total-gross" }
Number:Dimensionless today05 "Today 05-06 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today05#total-gross" }
Number:Dimensionless today06 "Today 06-07 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today06#total-gross" }
Number:Dimensionless today07 "Today 07-08 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today07#total-gross" }
Number:Dimensionless today08 "Today 08-09 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today08#total-gross" }
Number:Dimensionless today09 "Today 09-10 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today09#total-gross" }
Number:Dimensionless today10 "Today 10-11 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today10#total-gross" }
Number:Dimensionless today11 "Today 11-12 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today11#total-gross" }
Number:Dimensionless today12 "Today 12-13 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today12#total-gross" }
Number:Dimensionless today13 "Today 13-14 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today13#total-gross" }
Number:Dimensionless today14 "Today 14-15 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today14#total-gross" }
Number:Dimensionless today15 "Today 15-16 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today15#total-gross" }
Number:Dimensionless today16 "Today 16-17 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today16#total-gross" }
Number:Dimensionless today17 "Today 17-18 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today17#total-gross" }
Number:Dimensionless today18 "Today 18-19 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today18#total-gross" }
Number:Dimensionless today19 "Today 19-20 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today19#total-gross" }
Number:Dimensionless today20 "Today 20-21 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today20#total-gross" }
Number:Dimensionless today21 "Today 21-22 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today21#total-gross" }
Number:Dimensionless today22 "Today 22-23 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today22#total-gross" }
Number:Dimensionless today23 "Today 23-00 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:today23#total-gross" }
Number:Dimensionless tomorrow00 "Tomorrow 00-01 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow00#total-gross" }
Number:Dimensionless tomorrow01 "Tomorrow 01-02 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow01#total-gross" }
Number:Dimensionless tomorrow02 "Tomorrow 02-03 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow02#total-gross" }
Number:Dimensionless tomorrow03 "Tomorrow 03-04 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow03#total-gross" }
Number:Dimensionless tomorrow04 "Tomorrow 04-05 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow04#total-gross" }
Number:Dimensionless tomorrow05 "Tomorrow 05-06 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow05#total-gross" }
Number:Dimensionless tomorrow06 "Tomorrow 06-07 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow06#total-gross" }
Number:Dimensionless tomorrow07 "Tomorrow 07-08 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow07#total-gross" }
Number:Dimensionless tomorrow08 "Tomorrow 08-09 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow08#total-gross" }
Number:Dimensionless tomorrow09 "Tomorrow 09-10 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow09#total-gross" }
Number:Dimensionless tomorrow10 "Tomorrow 10-11 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow10#total-gross" }
Number:Dimensionless tomorrow11 "Tomorrow 11-12 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow11#total-gross" }
Number:Dimensionless tomorrow12 "Tomorrow 12-13 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow12#total-gross" }
Number:Dimensionless tomorrow13 "Tomorrow 13-14 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow13#total-gross" }
Number:Dimensionless tomorrow14 "Tomorrow 14-15 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow14#total-gross" }
Number:Dimensionless tomorrow15 "Tomorrow 15-16 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow15#total-gross" }
Number:Dimensionless tomorrow16 "Tomorrow 16-17 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow16#total-gross" }
Number:Dimensionless tomorrow17 "Tomorrow 17-18 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow17#total-gross" }
Number:Dimensionless tomorrow18 "Tomorrow 18-19 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow18#total-gross" }
Number:Dimensionless tomorrow19 "Tomorrow 19-20 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow19#total-gross" }
Number:Dimensionless tomorrow20 "Tomorrow 20-21 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow20#total-gross" }
Number:Dimensionless tomorrow21 "Tomorrow 21-22 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow21#total-gross" }
Number:Dimensionless tomorrow22 "Tomorrow 22-23 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow22#total-gross" }
Number:Dimensionless tomorrow23 "Tomorrow 23-00 [%2.2f ct/kWh]" { channel="awattar:prices:bridge1:price1:tomorrow23#total-gross" }
DateTime CarStart "Start car loader [%1$tH:%1$tM]" { channel="awattar:bestprice:bridge1:carloader:start" }
DateTime CarEnd "End car loader [%1$tH:%1$tM]" { channel="awattar:bestprice:bridge1:carloader:end" }
String CarCountdown "Countdown for car loader [%s]" { channel="awattar:bestprice:bridge1:carloader:countdown" }
String CarHours "Hours for car loader [%s]" { channel="awattar:bestprice:bridge1:carloader:hours" }
Switch CarActive { channel="awattar:bestprice:bridge1:carloader:active" }
Switch WaterActive { channel="awattar:bestprice:bridge1:water:active" }
Switch HeatpumpActive { channel="awattar:bestprice:bridge1:heatpump:active" }
```
### Sitemap
```
sitemap default label="aWATTar Sitemap"
{
Frame label="Car Loader" {
Switch item=CarActive
Text item=CarCountdown
Text item=CarStart
Text item=CarEnd
Text item=CarHours
}
Frame label="Current Prices" {
Text label="Current Net" item=currentnet
Text label="Current Gross" item=currentgross
Text label="Total Net" item=totalnet
Text label="Total Gross" item=totalgross
}
Frame label="Todays Prices (total gross)" {
Text item=today00
Text item=today01
Text item=today02
Text item=today03
Text item=today04
Text item=today05
Text item=today06
Text item=today07
Text item=today08
Text item=today09
Text item=today10
Text item=today11
Text item=today12
Text item=today13
Text item=today14
Text item=today15
Text item=today16
Text item=today17
Text item=today18
Text item=today19
Text item=today20
Text item=today21
Text item=today22
Text item=today23
}
Frame label="Tomorrows Prices (total gross)" {
Text item=tomorrow00
Text item=tomorrow01
Text item=tomorrow02
Text item=tomorrow03
Text item=tomorrow04
Text item=tomorrow05
Text item=tomorrow06
Text item=tomorrow07
Text item=tomorrow08
Text item=tomorrow09
Text item=tomorrow10
Text item=tomorrow11
Text item=tomorrow12
Text item=tomorrow13
Text item=tomorrow14
Text item=tomorrow15
Text item=tomorrow16
Text item=tomorrow17
Text item=tomorrow18
Text item=tomorrow19
Text item=tomorrow20
Text item=tomorrow21
Text item=tomorrow22
Text item=tomorrow23
}
}
```
### Usage hints
The idea of this binding is to support both automated and non automated components of your home.
For automated components, just decide when and how long you want to power them on and use the `active` switch of the bestprice thing to do so.
Many non automated components still allow some kind of locally programmed start and end times, e.g. washing machines or dishwashers.
So if you know your dishwasher needs less than 3 hour for one run and you want it to be done the next morning, use either the `countdown` or the `remaining` channel of a bestprice thing to determine the best start or end time to select.

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.awattar</artifactId>
<name>openHAB Add-ons :: Bundles :: aWATTar Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.awattar-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-awattar" description="aWATTar Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.awattar/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Base class for results
*
* @author Wolfgang Klimt - initial contribution
*/
@NonNullByDefault
public abstract class AwattarBestPriceResult {
private long start;
private long end;
public AwattarBestPriceResult() {
}
public long getStart() {
return start;
}
public void updateStart(long start) {
if (this.start == 0 || this.start > start) {
this.start = start;
}
}
public long getEnd() {
return end;
}
public void updateEnd(long end) {
if (this.end == 0 || this.end < end) {
this.end = end;
}
}
public abstract boolean isActive();
public abstract String getHours();
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Stores the bestprice config
*
* @author Wolfgang Klimt - initial contribution
*/
@NonNullByDefault
public class AwattarBestpriceConfiguration {
public int rangeStart;
public int rangeDuration;
public int length;
public boolean consecutive;
public String toString() {
return String.format("{ s: %d, d: %d, l: %d, c: %b )", rangeStart, rangeDuration, length, consecutive);
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelGroupTypeUID;
/**
* The {@link AwattarBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Wolfgang Klimt - Initial contribution
*/
@NonNullByDefault
public class AwattarBindingConstants {
public static final String BINDING_ID = "awattar";
public static final String API = "api";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_PRICE = new ThingTypeUID(BINDING_ID, "prices");
public static final ThingTypeUID THING_TYPE_BESTPRICE = new ThingTypeUID(BINDING_ID, "bestprice");
public static final ThingTypeUID THING_TYPE_BESTNEXT = new ThingTypeUID(BINDING_ID, "bestnext");
public static final ChannelGroupTypeUID CHANNEL_GROUP_TYPE_HOURLY_PRICES = new ChannelGroupTypeUID(BINDING_ID,
"hourly-prices");
public static final String CHANNEL_GROUP_CURRENT = "current";
// List of all Channel ids
public static final String CHANNEL_TOTAL_NET = "total-net";
public static final String CHANNEL_TOTAL_GROSS = "total-gross";
public static final String CHANNEL_MARKET_NET = "market-net";
public static final String CHANNEL_MARKET_GROSS = "market-gross";
public static final String CHANNEL_ACTIVE = "active";
public static final String CHANNEL_START = "start";
public static final String CHANNEL_END = "end";
public static final String CHANNEL_COUNTDOWN = "countdown";
public static final String CHANNEL_REMAINING = "remaining";
public static final String CHANNEL_HOURS = "hours";
public static final String CHANNEL_DURATION = "rangeDuration";
public static final String CHANNEL_LOOKUP_HOURS = "lookupHours";
public static final String CHANNEL_CONSECUTIVE = "consecutive";
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Stores the bridge configuration
*
* @author Wolfgang Klimt - initial contribution
*/
@NonNullByDefault
public class AwattarBridgeConfiguration {
public double basePrice;
public double vatPercent;
public String country = "";
}

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import static org.openhab.binding.awattar.internal.AwattarUtil.formatDate;
import static org.openhab.binding.awattar.internal.AwattarUtil.getHourFrom;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Stores a consecutive bestprice result
*
* @author Wolfgang Klimt - initial contribution
*/
@NonNullByDefault
public class AwattarConsecutiveBestPriceResult extends AwattarBestPriceResult {
private double priceSum = 0;
private int length = 0;
private String hours;
private ZoneId zoneId;
public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, ZoneId zoneId) {
super();
this.zoneId = zoneId;
StringBuilder hours = new StringBuilder();
boolean second = false;
for (AwattarPrice price : prices) {
priceSum += price.getPrice();
length++;
updateStart(price.getStartTimestamp());
updateEnd(price.getEndTimestamp());
if (second) {
hours.append(',');
}
hours.append(getHourFrom(price.getStartTimestamp(), zoneId));
second = true;
}
this.hours = hours.toString();
}
@Override
public boolean isActive() {
return contains(Instant.now().toEpochMilli());
}
public boolean contains(long timestamp) {
return timestamp >= getStart() && timestamp < getEnd();
}
public double getPriceSum() {
return priceSum;
}
public String toString() {
return String.format("{%s, %s, %.2f}", formatDate(getStart(), zoneId), formatDate(getEnd(), zoneId),
priceSum / length);
}
public String getHours() {
return hours;
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import static org.openhab.binding.awattar.internal.AwattarUtil.*;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Stores a non consecutive bestprice result
*
* @author Wolfgang Klimt - initial contribution
*/
@NonNullByDefault
public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult {
private List<AwattarPrice> members;
private ZoneId zoneId;
private boolean sorted = true;
public AwattarNonConsecutiveBestPriceResult(int size, ZoneId zoneId) {
super();
this.zoneId = zoneId;
members = new ArrayList<AwattarPrice>();
}
public void addMember(AwattarPrice member) {
sorted = false;
members.add(member);
updateStart(member.getStartTimestamp());
updateEnd(member.getEndTimestamp());
}
@Override
public boolean isActive() {
return members.stream().anyMatch(x -> x.contains(Instant.now().toEpochMilli()));
}
public String toString() {
return String.format("NonConsecutiveBestpriceResult with %s", members.toString());
}
private void sort() {
if (!sorted) {
members.sort(new Comparator<AwattarPrice>() {
@Override
public int compare(AwattarPrice o1, AwattarPrice o2) {
return Long.compare(o1.getStartTimestamp(), o2.getStartTimestamp());
}
});
}
}
public String getHours() {
boolean second = false;
sort();
StringBuilder res = new StringBuilder();
for (AwattarPrice price : members) {
if (second) {
res.append(',');
}
res.append(getHourFrom(price.getStartTimestamp(), zoneId));
second = true;
}
return res.toString();
}
}

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class to store hourly price data.
*
* @author Wolfgang Klimt - initial contribution
*/
@NonNullByDefault
public class AwattarPrice implements Comparable<AwattarPrice> {
private final Double price;
private final long endTimestamp;
private final long startTimestamp;
private final int hour;
public AwattarPrice(double price, long startTimestamp, long endTimestamp, ZoneId zoneId) {
this.price = price;
this.endTimestamp = endTimestamp;
this.startTimestamp = startTimestamp;
this.hour = ZonedDateTime.ofInstant(Instant.ofEpochMilli(startTimestamp), zoneId).getHour();
}
public long getStartTimestamp() {
return startTimestamp;
}
public long getEndTimestamp() {
return endTimestamp;
}
public double getPrice() {
return price;
}
public String toString() {
return String.format("(%1$tF %1$tR - %2$tR: %3$.3f)", startTimestamp, endTimestamp, getPrice());
}
public int getHour() {
return hour;
}
@Override
public int compareTo(AwattarPrice o) {
return price.compareTo(o.price);
}
public boolean isBetween(long start, long end) {
return startTimestamp >= start && endTimestamp <= end;
}
public boolean contains(long timestamp) {
return startTimestamp <= timestamp && endTimestamp > timestamp;
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import javax.measure.quantity.Time;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
/**
* Some utility methods
*
* @author Wolfgang Klimt - initial contribution
*/
@NonNullByDefault
public class AwattarUtil {
public static long getMillisToNextMinute(int mod, TimeZoneProvider timeZoneProvider) {
long now = Instant.now().toEpochMilli();
ZonedDateTime dt = ZonedDateTime.now(timeZoneProvider.getTimeZone()).truncatedTo(ChronoUnit.MINUTES);
int min = dt.getMinute();
int offset = min % mod;
offset = offset == 0 ? mod : offset;
dt = dt.plusMinutes(offset);
long result = dt.toInstant().toEpochMilli() - now;
return result;
}
public static ZonedDateTime getCalendarForHour(int hour, ZoneId zone) {
return ZonedDateTime.now(zone).truncatedTo(ChronoUnit.DAYS).plus(hour, ChronoUnit.HOURS);
}
public static DateTimeType getDateTimeType(long time, TimeZoneProvider tz) {
return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochMilli(time), tz.getTimeZone()));
}
public static QuantityType<Time> getDuration(long millis) {
long minutes = millis / 60000;
return QuantityType.valueOf(minutes, Units.MINUTE);
}
public static String formatDate(long date, ZoneId zoneId) {
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(date), zoneId).toString();
}
public static String getHourFrom(long timestamp, ZoneId zoneId) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId);
return String.format("%02d", zdt.getHour());
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal.dto;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* Represents data from aWATTar API
*
* @author Wolfgang Klimt - initial contribution
*/
public class AwattarApiData {
@SerializedName("data")
@Expose
public List<Datum> data = null;
@SerializedName("object")
@Expose
public String object;
@SerializedName("url")
@Expose
public String url;
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal.dto;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Datum
*
* @author Wolfgang Klimt - initial contribution
*/
public class Datum {
@SerializedName("end_timestamp")
@Expose
public long endTimestamp;
@SerializedName("marketprice")
@Expose
public double marketprice;
@SerializedName("start_timestamp")
@Expose
public long startTimestamp;
@SerializedName("unit")
@Expose
public String unit;
}

View File

@ -0,0 +1,266 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal.handler;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.BINDING_ID;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_ACTIVE;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_COUNTDOWN;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_END;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_HOURS;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_REMAINING;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_START;
import static org.openhab.binding.awattar.internal.AwattarUtil.getCalendarForHour;
import static org.openhab.binding.awattar.internal.AwattarUtil.getDateTimeType;
import static org.openhab.binding.awattar.internal.AwattarUtil.getDuration;
import static org.openhab.binding.awattar.internal.AwattarUtil.getMillisToNextMinute;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 {
private final Logger logger = LoggerFactory.getLogger(AwattarBestpriceHandler.class);
private final int thingRefreshInterval = 60;
@Nullable
private ScheduledFuture<?> thingRefresher;
private final TimeZoneProvider timeZoneProvider;
public AwattarBestpriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
}
@Override
public void initialize() {
AwattarBestpriceConfiguration config = getConfigAs(AwattarBestpriceConfiguration.class);
boolean configValid = true;
if (config.length >= config.rangeDuration) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.length.value");
configValid = false;
}
if (!configValid) {
return;
}
synchronized (this) {
ScheduledFuture<?> localRefresher = thingRefresher;
if (localRefresher == null || localRefresher.isCancelled()) {
/*
* The scheduler is required to run exactly at minute borders, hence we can't use scheduleWithFixedDelay
* here
*/
thingRefresher = scheduler.scheduleAtFixedRate(this::refreshChannels,
getMillisToNextMinute(1, timeZoneProvider), thingRefreshInterval * 1000, TimeUnit.MILLISECONDS);
}
}
updateStatus(ThingStatus.UNKNOWN);
}
@Override
public void dispose() {
ScheduledFuture<?> localRefresher = thingRefresher;
if (localRefresher != null) {
localRefresher.cancel(true);
thingRefresher = null;
}
}
public void refreshChannels() {
updateStatus(ThingStatus.ONLINE);
for (Channel channel : getThing().getChannels()) {
ChannelUID channelUID = channel.getUID();
if (ChannelKind.STATE.equals(channel.getKind()) && isLinked(channelUID)) {
refreshChannel(channel.getUID());
}
}
}
public void refreshChannel(ChannelUID channelUID) {
State state = UnDefType.UNDEF;
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
updateState(channelUID, state);
return;
}
AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
if (bridgeHandler == null || bridgeHandler.getPriceMap() == null) {
logger.debug("No prices available, so can't refresh channel.");
// no prices available, can't continue
updateState(channelUID, state);
return;
}
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);
return;
}
AwattarBestPriceResult result;
if (config.consecutive) {
ArrayList<AwattarPrice> range = new ArrayList<AwattarPrice>(config.rangeDuration);
range.addAll(getPriceRange(bridgeHandler, timerange,
(o1, o2) -> Long.compare(o1.getStartTimestamp(), o2.getStartTimestamp())));
AwattarConsecutiveBestPriceResult res = new AwattarConsecutiveBestPriceResult(
range.subList(0, config.length), bridgeHandler.getTimeZone());
for (int i = 1; i <= range.size() - config.length; i++) {
AwattarConsecutiveBestPriceResult res2 = new AwattarConsecutiveBestPriceResult(
range.subList(i, i + config.length), bridgeHandler.getTimeZone());
if (res2.getPriceSum() < res.getPriceSum()) {
res = res2;
}
}
result = res;
} else {
List<AwattarPrice> range = getPriceRange(bridgeHandler, timerange,
(o1, o2) -> Double.compare(o1.getPrice(), o2.getPrice()));
AwattarNonConsecutiveBestPriceResult res = new AwattarNonConsecutiveBestPriceResult(config.length,
bridgeHandler.getTimeZone());
int ct = 0;
for (AwattarPrice price : range) {
res.addMember(price);
if (++ct >= config.length) {
break;
}
}
result = res;
}
String channelId = channelUID.getIdWithoutGroup();
long diff;
switch (channelId) {
case CHANNEL_ACTIVE:
state = OnOffType.from(result.isActive());
break;
case CHANNEL_START:
state = getDateTimeType(result.getStart(), timeZoneProvider);
break;
case CHANNEL_END:
state = getDateTimeType(result.getEnd(), timeZoneProvider);
break;
case CHANNEL_COUNTDOWN:
diff = result.getStart() - Instant.now().toEpochMilli();
if (diff >= 0) {
state = getDuration(diff);
}
break;
case CHANNEL_REMAINING:
diff = result.getEnd() - Instant.now().toEpochMilli();
if (result.isActive()) {
state = getDuration(diff);
}
break;
case CHANNEL_HOURS:
state = new StringType(result.getHours());
break;
default:
logger.warn("Unknown channel id {} for Thing type {}", channelUID, getThing().getThingTypeUID());
}
updateState(channelUID, state);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refreshChannel(channelUID);
} else {
logger.debug("Binding {} only supports refresh command", BINDING_ID);
}
}
private List<AwattarPrice> getPriceRange(AwattarBridgeHandler bridgeHandler, Timerange range,
Comparator<AwattarPrice> comparator) {
ArrayList<AwattarPrice> result = new ArrayList<>();
SortedMap<Long, AwattarPrice> priceMap = bridgeHandler.getPriceMap();
if (priceMap == null) {
logger.debug("No prices available, can't compute ranges");
return result;
}
result.addAll(priceMap.values().stream().filter(x -> x.isBetween(range.start, range.end))
.collect(Collectors.toSet()));
result.sort(comparator);
return result;
}
private Timerange getRange(int start, int duration, ZoneId zoneId) {
ZonedDateTime startCal = getCalendarForHour(start, zoneId);
ZonedDateTime endCal = startCal.plusHours(duration);
ZonedDateTime now = ZonedDateTime.now(zoneId);
if (now.getHour() < start) {
// we are before the range, so we might be still within the last range
startCal = startCal.minusDays(1);
endCal = endCal.minusDays(1);
}
if (endCal.toInstant().toEpochMilli() < Instant.now().toEpochMilli()) {
// span is in the past, add one day
startCal = startCal.plusDays(1);
endCal = endCal.plusDays(1);
}
return new Timerange(startCal.toInstant().toEpochMilli(), endCal.toInstant().toEpochMilli());
}
private class Timerange {
long start;
long end;
Timerange(long start, long end) {
this.start = start;
this.end = end;
}
}
}

View File

@ -0,0 +1,268 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
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 java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.openhab.binding.awattar.internal.AwattarBridgeConfiguration;
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.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* The {@link AwattarBridgeHandler} is responsible for retrieving data from the aWATTar API.
*
* The API provides hourly prices for the current day and, starting from 14:00, hourly prices for the next day.
* Check the documentation at https://www.awattar.de/services/api
*
*
*
* @author Wolfgang Klimt - Initial contribution
*/
@NonNullByDefault
public class AwattarBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(AwattarBridgeHandler.class);
private final HttpClient httpClient;
@Nullable
private ScheduledFuture<?> dataRefresher;
private static final String URLDE = "https://api.awattar.de/v1/marketdata";
private static final String URLAT = "https://api.awattar.at/v1/marketdata";
private String url;
// This cache stores price data for up to two days
@Nullable
private SortedMap<Long, AwattarPrice> priceMap;
private final int dataRefreshInterval = 60;
private double vatFactor = 0;
private long lastUpdated = 0;
private double basePrice = 0;
private long minTimestamp = 0;
private long maxTimestamp = 0;
private ZoneId zone;
private TimeZoneProvider timeZoneProvider;
public AwattarBridgeHandler(Bridge thing, HttpClient httpClient, TimeZoneProvider timeZoneProvider) {
super(thing);
this.httpClient = httpClient;
url = URLDE;
this.timeZoneProvider = timeZoneProvider;
zone = timeZoneProvider.getTimeZone();
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
AwattarBridgeConfiguration config = getConfigAs(AwattarBridgeConfiguration.class);
vatFactor = 1 + (config.vatPercent / 100);
basePrice = config.basePrice;
zone = timeZoneProvider.getTimeZone();
switch (config.country) {
case "DE":
url = URLDE;
break;
case "AT":
url = URLAT;
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/error.unsupported.country");
return;
}
dataRefresher = scheduler.scheduleWithFixedDelay(this::refreshIfNeeded, 0, dataRefreshInterval * 1000,
TimeUnit.MILLISECONDS);
}
@Override
public void dispose() {
ScheduledFuture<?> localRefresher = dataRefresher;
if (localRefresher != null) {
localRefresher.cancel(true);
}
dataRefresher = null;
priceMap = null;
lastUpdated = 0;
}
public void refreshIfNeeded() {
if (needRefresh()) {
refresh();
}
updateStatus(ThingStatus.ONLINE);
}
private void getPrices() {
try {
// we start one day in the past to cover ranges that already started yesterday
ZonedDateTime zdt = LocalDate.now(zone).atStartOfDay(zone).minusDays(1);
long start = zdt.toInstant().toEpochMilli();
// Starting from midnight yesterday we add three days so that the range covers the whole next day.
zdt = zdt.plusDays(3);
long end = zdt.toInstant().toEpochMilli();
StringBuilder request = new StringBuilder(url);
request.append("?start=").append(start).append("&end=").append(end);
logger.trace("aWATTar API request: = '{}'", request);
ContentResponse contentResponse = httpClient.newRequest(request.toString()).method(GET)
.timeout(10, TimeUnit.SECONDS).send();
int httpStatus = contentResponse.getStatus();
String content = contentResponse.getContentAsString();
logger.trace("aWATTar API response: status = {}, content = '{}'", httpStatus, content);
switch (httpStatus) {
case OK_200:
Gson gson = new Gson();
SortedMap<Long, AwattarPrice> result = new TreeMap<>();
minTimestamp = 0;
maxTimestamp = 0;
AwattarApiData apiData = gson.fromJson(content, AwattarApiData.class);
if (apiData != null) {
for (Datum d : apiData.data) {
result.put(d.startTimestamp,
new AwattarPrice(d.marketprice / 10.0, d.startTimestamp, d.endTimestamp, zone));
updateMin(d.startTimestamp);
updateMax(d.endTimestamp);
}
priceMap = result;
updateStatus(ThingStatus.ONLINE);
lastUpdated = Instant.now().toEpochMilli();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/error.invalid.data");
}
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/warn.awattar.statuscode");
}
} catch (JsonSyntaxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.json");
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.interrupted");
} catch (ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.execution");
} catch (TimeoutException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.timeout");
}
}
private boolean needRefresh() {
if (getThing().getStatus() != ThingStatus.ONLINE) {
return true;
}
SortedMap<Long, AwattarPrice> localMap = priceMap;
if (localMap == null) {
return true;
}
return localMap.lastKey() < Instant.now().toEpochMilli() + 9 * 3600 * 1000;
}
private void refresh() {
getPrices();
}
public double getVatFactor() {
return vatFactor;
}
public double getBasePrice() {
return basePrice;
}
public long getLastUpdated() {
return lastUpdated;
}
public ZoneId getTimeZone() {
return zone;
}
@Nullable
public synchronized SortedMap<Long, AwattarPrice> getPriceMap() {
if (priceMap == null) {
refresh();
}
return priceMap;
}
@Nullable
public AwattarPrice getPriceFor(long timestamp) {
SortedMap<Long, AwattarPrice> priceMap = getPriceMap();
if (priceMap == null) {
return null;
}
if (!containsPriceFor(timestamp)) {
return null;
}
for (AwattarPrice price : priceMap.values()) {
if (timestamp >= price.getStartTimestamp() && timestamp < price.getEndTimestamp()) {
return price;
}
}
return null;
}
public boolean containsPriceFor(long timestamp) {
return minTimestamp <= timestamp && maxTimestamp >= timestamp;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refresh();
} else {
logger.debug("Binding {} only supports refresh command", BINDING_ID);
}
}
private void updateMin(long ts) {
minTimestamp = (minTimestamp == 0) ? ts : Math.min(minTimestamp, ts);
}
private void updateMax(long ts) {
maxTimestamp = (maxTimestamp == 0) ? ts : Math.max(ts, maxTimestamp);
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal.handler;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.THING_TYPE_BESTPRICE;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.THING_TYPE_BRIDGE;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.THING_TYPE_PRICE;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AwattarHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Wolfgang Klimt - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.awattar", service = ThingHandlerFactory.class)
public class AwattarHandlerFactory extends BaseThingHandlerFactory {
private Logger logger = LoggerFactory.getLogger(AwattarHandlerFactory.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PRICE, THING_TYPE_BESTPRICE,
THING_TYPE_BRIDGE);
private final HttpClient httpClient;
private final TimeZoneProvider timeZoneProvider;
@Activate
public AwattarHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
final @Reference TimeZoneProvider timeZoneProvider) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.timeZoneProvider = timeZoneProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
return new AwattarBridgeHandler((Bridge) thing, httpClient, timeZoneProvider);
}
if (THING_TYPE_PRICE.equals(thingTypeUID)) {
return new AwattarPriceHandler(thing, timeZoneProvider);
}
if (THING_TYPE_BESTPRICE.equals(thingTypeUID)) {
return new AwattarBestpriceHandler(thing, timeZoneProvider);
}
logger.warn("Unknown thing type {}, not creating handler!", thingTypeUID);
return null;
}
}

View File

@ -0,0 +1,185 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal.handler;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.BINDING_ID;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_GROUP_CURRENT;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_MARKET_GROSS;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_MARKET_NET;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_TOTAL_GROSS;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_TOTAL_NET;
import static org.openhab.binding.awattar.internal.AwattarUtil.getCalendarForHour;
import static org.openhab.binding.awattar.internal.AwattarUtil.getMillisToNextMinute;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.ZonedDateTime;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.awattar.internal.AwattarPrice;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AwattarPriceHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Wolfgang Klimt - Initial contribution
*/
@NonNullByDefault
public class AwattarPriceHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(AwattarPriceHandler.class);
private int thingRefreshInterval = 60;
private TimeZoneProvider timeZoneProvider;
private @Nullable ScheduledFuture<?> thingRefresher;
public AwattarPriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refreshChannel(channelUID);
} else {
logger.debug("Binding {} only supports refresh command", BINDING_ID);
}
}
/**
* Initialize the binding and start the refresh job.
* The refresh job runs once after initialization and afterwards every hour.
*/
@Override
public void initialize() {
synchronized (this) {
ScheduledFuture<?> localRefresher = thingRefresher;
if (localRefresher == null || localRefresher.isCancelled()) {
/*
* The scheduler is required to run exactly at minute borders, hence we can't use scheduleWithFixedDelay
* here
*/
thingRefresher = scheduler.scheduleAtFixedRate(this::refreshChannels,
getMillisToNextMinute(1, timeZoneProvider), thingRefreshInterval * 1000, TimeUnit.MILLISECONDS);
}
}
updateStatus(ThingStatus.UNKNOWN);
}
public void dispose() {
ScheduledFuture<?> localRefresher = thingRefresher;
if (localRefresher != null) {
localRefresher.cancel(true);
thingRefresher = null;
}
}
public void refreshChannels() {
updateStatus(ThingStatus.ONLINE);
for (Channel channel : getThing().getChannels()) {
ChannelUID channelUID = channel.getUID();
if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
&& isLinked(channelUID)) {
refreshChannel(channel.getUID());
}
}
}
public void refreshChannel(ChannelUID channelUID) {
State state = UnDefType.UNDEF;
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
return;
}
AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
return;
}
String group = channelUID.getGroupId();
if (group == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/error.channelgroup.missing");
return;
}
ZonedDateTime target;
if (group.equals(CHANNEL_GROUP_CURRENT)) {
target = ZonedDateTime.now(bridgeHandler.getTimeZone());
} else if (group.startsWith("today")) {
target = getCalendarForHour(Integer.valueOf(group.substring(5)), bridgeHandler.getTimeZone());
} else if (group.startsWith("tomorrow")) {
target = getCalendarForHour(Integer.valueOf(group.substring(8)), bridgeHandler.getTimeZone()).plusDays(1);
} else {
logger.warn("Unsupported channel group {}", group);
updateState(channelUID, state);
return;
}
AwattarPrice price = bridgeHandler.getPriceFor(target.toInstant().toEpochMilli());
if (price == null) {
logger.trace("No price found for hour {}", target.toString());
updateState(channelUID, state);
return;
}
double currentprice = price.getPrice();
String channelId = channelUID.getIdWithoutGroup();
switch (channelId) {
case CHANNEL_MARKET_NET:
state = toDecimalType(currentprice);
break;
case CHANNEL_MARKET_GROSS:
state = toDecimalType(currentprice * bridgeHandler.getVatFactor());
break;
case CHANNEL_TOTAL_NET:
state = toDecimalType(currentprice + bridgeHandler.getBasePrice());
break;
case CHANNEL_TOTAL_GROSS:
state = toDecimalType((currentprice + bridgeHandler.getBasePrice()) * bridgeHandler.getVatFactor());
break;
default:
logger.warn("Unknown channel id {} for Thing type {}", channelUID, getThing().getThingTypeUID());
}
updateState(channelUID, state);
}
private DecimalType toDecimalType(Double value) {
BigDecimal bd = BigDecimal.valueOf(value);
return new DecimalType(bd.setScale(2, RoundingMode.HALF_UP));
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="awattar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>aWATTar Binding</name>
<description>Hourly Electricity Prices for Germany and Austria.</description>
</binding:binding>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="bridge-type:awattar:bridge">
<parameter name="country" type="text" pattern="DE|AT">
<label>Country</label>
<default>DE</default>
<description>Country to get prices for. Only DE (Germany) and AT (Austria) are supported.</description>
<options>
<option value="DE">DE</option>
<option value="AT">AT</option>
</options>
</parameter>
<parameter name="vatPercent" type="decimal">
<label>VAT Percent</label>
<description>Specifies the value added tax percentage</description>
<default>19</default>
</parameter>
<parameter name="basePrice" type="decimal">
<label>Base Price</label>
<description>Specifies the net base price per kWh</description>
<default>0</default>
</parameter>
</config-description>
<config-description uri="thing-type:awattar:bestprice">
<parameter name="rangeStart" type="integer" min="0" max="23">
<label>Range Start</label>
<description>Earliest possible hour of bestprice period.</description>
<default>0</default>
</parameter>
<parameter name="rangeDuration" type="integer" min="1" max="24">
<label>Range Duration</label>
<description>Duration of bestprice candidate range</description>
<default>24</default>
</parameter>
<parameter name="length" type="integer" min="1" max="23">
<label>Length</label>
<description>The number of hours the bestprice period should last</description>
<default>1</default>
</parameter>
<parameter name="consecutive" type="boolean">
<label>Consecutive</label>
<description>Do the hours need to be consecutive?</description>
<default>true</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,171 @@
binding.awattar.name = aWATTar Binding
binding.awattar.description = Hourly market electricity prices for Germany and Austria
bridge-type.awattar.bridge.label = aWATTar Bridge
bridge-type.awattar.bridge.description = Provides price data from the aWATTar API.
bridge-type.config.awattar.bridge.country.label = Country
bridge-type.config.awattar.bridge.country.description = Country to get prices for. Only DE (Germany) and AT (Austria) are supported
bridge-type.config.awattar.bridge.vatPercent.label = VAT Percent
bridge-type.config.awattar.bridge.vatPercent.description = Specifies the value added tax percentage
bridge-type.config.awattar.bridge.basePrice.label = Base price
bridge-type.config.awattar.bridge.basePrice.description = Specifies the net base price per kWh
bridge-type.config.awattar.bridge.timeZone.label = Time zone
bridge-type.config.awattar.bridge.timeZone.description = Time zone to apply to the hour definitions. Default CET aligns to the aWATTar API
# thing types
thing-type.awattar.prices.label = aWATTar Hourly Prices
thing-type.awattar.prices.description = Prices for one kilowatt-hour at the given hour in Cent
thing-type.awattar.bestprice.label = Best price
thing-type.awattar.bestprice.description = Evaluates the lowest price period for the given settings
# thing type config description
thing-type.config.awattar.bestprice.rangeStart.label = Range Start
thing-type.config.awattar.bestprice.rangeStart.description = Earliest possible hour of bestprice period.
thing-type.config.awattar.bestprice.rangeDuration.label = Range duration
thing-type.config.awattar.bestprice.rangeDuration.description = Duration of bestprice candidate range
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?
# channel types
channel-type.awattar.price.label = ct/kWh
channel-type.awattar.price.description = Price in ct/kWh
channel-type.awattar.input-duration.label = Duration
channel-type.awattar.input-duration.description = Length of the bestprice period to search for (hours)
channel-type.awattar.input-hours.label = Lookup time
channel-type.awattar.input-hours.description = How many hours from now should be checked?
channel-type.awattar.input-switch.label = Consecutive
channel-type.awattar.input-switch.description = Consecutive range needed?
channel-type.awattar.switch-type.label = Active
channel-type.awattar.switch-type.description = Currently activated
channel-type.awattar.start-time-stamp.label = Starttime
channel-type.awattar.start-time-stamp.description = Starting time of period.
channel-type.awattar.end-time-stamp.label = end-time-stamp
channel-type.awattar.end-time-stamp.description = End time of period.
channel-type.awattar.countdown-type.label = Countdown
channel-type.awattar.countdown-type.description = Time until start of period.
channel-type.awattar.remaining-type.label = Remaining
channel-type.awattar.remaining-type.description = Time until end of period.
channel-type.awattar.hours-type.label = Hours
channel-type.awattar.hours-type.description = A list of all hours within this bestprice range
# channel group types
channel-group-type.awattar.hourly-prices.label = Hourly prices
channel-group-type.awattar.hourly-prices.description = Hourly net and gross prices
channel-group-type.awattar.current.label = Current prices
channel-group-type.awattar.current.description = The prices of the current hour
channel-group-type.awattar.today00.label = Today 00:00
channel-group-type.awattar.today00.description = Todays prices from 00:00 to 01:00
channel-group-type.awattar.today01.label = Today 01:00
channel-group-type.awattar.today01.description = Todays prices from 01:00 to 02:00
channel-group-type.awattar.today02.label = Today 02:00
channel-group-type.awattar.today02.description = Todays prices from 02:00 to 03:00
channel-group-type.awattar.today03.label = Today 03:00
channel-group-type.awattar.today03.description = Todays prices from 03:00 to 04:00
channel-group-type.awattar.today04.label = Today 04:00
channel-group-type.awattar.today04.description = Todays prices from 04:00 to 05:00
channel-group-type.awattar.today05.label = Today 05:00
channel-group-type.awattar.today05.description = Todays prices from 05:00 to 06:00
channel-group-type.awattar.today06.label = Today 06:00
channel-group-type.awattar.today06.description = Todays prices from 06:00 to 07:00
channel-group-type.awattar.today07.label = Today 07:00
channel-group-type.awattar.today07.description = Todays prices from 07:00 to 08:00
channel-group-type.awattar.today08.label = Today 08:00
channel-group-type.awattar.today08.description = Todays prices from 08:00 to 09:00
channel-group-type.awattar.today09.label = Today 09:00
channel-group-type.awattar.today09.description = Todays prices from 09:00 to 10:00
channel-group-type.awattar.today10.label = Today 10:00
channel-group-type.awattar.today10.description = Todays prices from 10:00 to 11:00
channel-group-type.awattar.today11.label = Today 11:00
channel-group-type.awattar.today11.description = Todays prices from 11:00 to 12:00
channel-group-type.awattar.today12.label = Today 12:00
channel-group-type.awattar.today12.description = Todays prices from 12:00 to 13:00
channel-group-type.awattar.today13.label = Today 13:00
channel-group-type.awattar.today13.description = Todays prices from 13:00 to 14:00
channel-group-type.awattar.today14.label = Today 14:00
channel-group-type.awattar.today14.description = Todays prices from 14:00 to 15:00
channel-group-type.awattar.today15.label = Today 15:00
channel-group-type.awattar.today15.description = Todays prices from 15:00 to 16:00
channel-group-type.awattar.today16.label = Today 16:00
channel-group-type.awattar.today16.description = Todays prices from 16:00 to 17:00
channel-group-type.awattar.today17.label = Today 17:00
channel-group-type.awattar.today17.description = Todays prices from 17:00 to 18:00
channel-group-type.awattar.today18.label = Today 18:00
channel-group-type.awattar.today18.description = Todays prices from 18:00 to 19:00
channel-group-type.awattar.today19.label = Today 19:00
channel-group-type.awattar.today19.description = Todays prices from 19:00 to 20:00
channel-group-type.awattar.today20.label = Today 20:00
channel-group-type.awattar.today20.description = Todays prices from 20:00 to 21:00
channel-group-type.awattar.today21.label = Today 21:00
channel-group-type.awattar.today21.description = Todays prices from 21:00 to 22:00
channel-group-type.awattar.today22.label = Today 22:00
channel-group-type.awattar.today22.description = Todays prices from 22:00 to 23:00
channel-group-type.awattar.today23.label = Today 23:00
channel-group-type.awattar.today23.description = Todays prices from 23:00 to 00:00
channel-group-type.awattar.tomorrow00.label = Tomorrow 00:00
channel-group-type.awattar.tomorrow00.description = Tomorrows prices from 00:00 to 01:00
channel-group-type.awattar.tomorrow01.label = Tomorrow 01:00
channel-group-type.awattar.tomorrow01.description = Tomorrows prices from 01:00 to 02:00
channel-group-type.awattar.tomorrow02.label = Tomorrow 02:00
channel-group-type.awattar.tomorrow02.description = Tomorrows prices from 02:00 to 03:00
channel-group-type.awattar.tomorrow03.label = Tomorrow 03:00
channel-group-type.awattar.tomorrow03.description = Tomorrows prices from 03:00 to 04:00
channel-group-type.awattar.tomorrow04.label = Tomorrow 04:00
channel-group-type.awattar.tomorrow04.description = Tomorrows prices from 04:00 to 05:00
channel-group-type.awattar.tomorrow05.label = Tomorrow 05:00
channel-group-type.awattar.tomorrow05.description = Tomorrows prices from 05:00 to 06:00
channel-group-type.awattar.tomorrow06.label = Tomorrow 06:00
channel-group-type.awattar.tomorrow06.description = Tomorrows prices from 06:00 to 07:00
channel-group-type.awattar.tomorrow07.label = Tomorrow 07:00
channel-group-type.awattar.tomorrow07.description = Tomorrows prices from 07:00 to 08:00
channel-group-type.awattar.tomorrow08.label = Tomorrow 08:00
channel-group-type.awattar.tomorrow08.description = Tomorrows prices from 08:00 to 09:00
channel-group-type.awattar.tomorrow09.label = Tomorrow 09:00
channel-group-type.awattar.tomorrow09.description = Tomorrows prices from 09:00 to 10:00
channel-group-type.awattar.tomorrow10.label = Tomorrow 10:00
channel-group-type.awattar.tomorrow10.description = Tomorrows prices from 10:00 to 11:00
channel-group-type.awattar.tomorrow11.label = Tomorrow 11:00
channel-group-type.awattar.tomorrow11.description = Tomorrows prices from 11:00 to 12:00
channel-group-type.awattar.tomorrow12.label = Tomorrow 12:00
channel-group-type.awattar.tomorrow12.description = Tomorrows prices from 12:00 to 13:00
channel-group-type.awattar.tomorrow13.label = Tomorrow 13:00
channel-group-type.awattar.tomorrow13.description = Tomorrows prices from 13:00 to 14:00
channel-group-type.awattar.tomorrow14.label = Tomorrow 14:00
channel-group-type.awattar.tomorrow14.description = Tomorrows prices from 14:00 to 15:00
channel-group-type.awattar.tomorrow15.label = Tomorrow 15:00
channel-group-type.awattar.tomorrow15.description = Tomorrows prices from 15:00 to 16:00
channel-group-type.awattar.tomorrow16.label = Tomorrow 16:00
channel-group-type.awattar.tomorrow16.description = Tomorrows prices from 16:00 to 17:00
channel-group-type.awattar.tomorrow17.label = Tomorrow 17:00
channel-group-type.awattar.tomorrow17.description = Tomorrows prices from 17:00 to 18:00
channel-group-type.awattar.tomorrow18.label = Tomorrow 18:00
channel-group-type.awattar.tomorrow18.description = Tomorrows prices from 18:00 to 19:00
channel-group-type.awattar.tomorrow19.label = Tomorrow 19:00
channel-group-type.awattar.tomorrow19.description = Tomorrows prices from 19:00 to 20:00
channel-group-type.awattar.tomorrow20.label = Tomorrow 20:00
channel-group-type.awattar.tomorrow20.description = Tomorrows prices from 20:00 to 21:00
channel-group-type.awattar.tomorrow21.label = Tomorrow 21:00
channel-group-type.awattar.tomorrow21.description = Tomorrows prices from 21:00 to 22:00
channel-group-type.awattar.tomorrow22.label = Tomorrow 22:00
channel-group-type.awattar.tomorrow22.description = Tomorrows prices from 22:00 to 23:00
channel-group-type.awattar.tomorrow23.label = Tomorrow 23:00
channel-group-type.awattar.tomorrow23.description = Tomorrows prices from 23:00 to 00:00
error.config.missing=Configuration missing!
error.bridge.missing=Bridge is missing!
error.channelgroup.missing=Channelgroup missing!
error.unsupported.country=Unsupported country, only DE and AT are supported
error.duration.value=Invalid duration value
error.json=Invalid JSON response from aWATTar API
error.interrupted=Communication interrupted
error.execution=Execution error
error.timeout=Timeout retrieving prices from aWATTar API
error.invalid.data=No or invalid data received from aWATTar API
error.length.value=length needs to be > 0 and < duration.
warn.awattar.statuscode=aWATTar server did not respond with status code 200
error.start.value=Invalid start value

View File

@ -0,0 +1,171 @@
binding.awattar.name = aWATTar Binding
binding.awattar.description = Stündlich wechselnde Strompreise für Deutschland und Österreich
bridge-type.awattar.bridge.label = aWATTar Bridge
bridge-type.awattar.bridge.description = Ermittelt Strompreise von der aWATTar API.
bridge-type.config.awattar.bridge.country.label = Land
bridge-type.config.awattar.bridge.country.description = Land, für das Preise ermittelt werden sollen. Nur Deutschland (DE) und Österreich (AT) werden unterstützt
bridge-type.config.awattar.bridge.vatPercent.label = USt-Satz
bridge-type.config.awattar.bridge.vatPercent.description = Umsatzsteuer in Prozent
bridge-type.config.awattar.bridge.basePrice.label = Basispreis
bridge-type.config.awattar.bridge.basePrice.description = Der Netto-Grundpreis pro Kilowattstunde
bridge-type.config.awattar.bridge.timeZone.label = Zeitzone
bridge-type.config.awattar.bridge.timeZone.description = Zeitzone für die Stundenangaben. Default ist CET, passend zur aWATTar API
# thing types
thing-type.awattar.prices.label = aWATTar Stundenpreise
thing-type.awattar.prices.description = Preise pro Kilowattstunde für die jeweilige Stunde in Cent
thing-type.awattar.bestprice.label = Bester Preis
thing-type.awattar.bestprice.description = Ermittelt die Stunden mit den günstigsten Preisen im angegebenen Zeitraum
# thing type config description
thing-type.config.awattar.bestprice.rangeStart.label = Startzeit
thing-type.config.awattar.bestprice.rangeStart.description = Erste Stunde des zu durchsuchenden Zeitraums.
thing-type.config.awattar.bestprice.rangeDuration.label = Dauer
thing-type.config.awattar.bestprice.rangeDuration.description = Dauer des zu durchsuchenden Zeitraums
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?
# channel types
channel-type.awattar.price.label = ct/kWh
channel-type.awattar.price.description = Preis in ct/kWh
channel-type.awattar.input-duration.label = Dauer
channel-type.awattar.input-duration.description = Die Anzahl der zu findenden günstigen Stunden
channel-type.awattar.input-hours.label = Suchzeitraum
channel-type.awattar.input-hours.description = Wie viele Stunden sollen durchsucht werden?
channel-type.awattar.input-switch.label = Durchgehend
channel-type.awattar.input-switch.description = Wird ein durchgehender Zeitraum gesucht?
channel-type.awattar.switch-type.label = Aktiv
channel-type.awattar.switch-type.description = Kennzeichnet Zeiträume mit günstigen Preisen
channel-type.awattar.start-time-stamp.label = Startzeit
channel-type.awattar.start-time-stamp.description = Start der gefundenen Periode
channel-type.awattar.end-time-stamp.label = Endzeit
channel-type.awattar.end-time-stamp.description = Ende der gefundenen Periode
channel-type.awattar.countdown-type.label = Countdown
channel-type.awattar.countdown-type.description = Zeit bis zum Beginn der gefundenen Periode
channel-type.awattar.remaining-type.label = Verbleibend
channel-type.awattar.remaining-type.description = Zeit bis zum Ende der gefundenen Periode
channel-type.awattar.hours-type.label = Stunden
channel-type.awattar.hours-type.description = Eine Liste aller gefundenen Stunden mit günstigen Preisen
# channel group types
channel-group-type.awattar.hourly-prices.label = Preise
channel-group-type.awattar.hourly-prices.description = Stündliche Netto- und Bruttopreise
channel-group-type.awattar.current.label = Aktuelle Preise
channel-group-type.awattar.current.description = Die aktuellen Netto- und Bruttopreise
channel-group-type.awattar.today00.label = Heute 00:00
channel-group-type.awattar.today00.description = Heutige Preise von 00:00 bis 01:00
channel-group-type.awattar.today01.label = Heute 01:00
channel-group-type.awattar.today01.description = Heutige Preise von 01:00 bis 02:00
channel-group-type.awattar.today02.label = Heute 02:00
channel-group-type.awattar.today02.description = Heutige Preise von 02:00 bis 03:00
channel-group-type.awattar.today03.label = Heute 03:00
channel-group-type.awattar.today03.description = Heutige Preise von 03:00 bis 04:00
channel-group-type.awattar.today04.label = Heute 04:00
channel-group-type.awattar.today04.description = Heutige Preise von 04:00 bis 05:00
channel-group-type.awattar.today05.label = Heute 05:00
channel-group-type.awattar.today05.description = Heutige Preise von 05:00 bis 06:00
channel-group-type.awattar.today06.label = Heute 06:00
channel-group-type.awattar.today06.description = Heutige Preise von 06:00 bis 07:00
channel-group-type.awattar.today07.label = Heute 07:00
channel-group-type.awattar.today07.description = Heutige Preise von 07:00 bis 08:00
channel-group-type.awattar.today08.label = Heute 08:00
channel-group-type.awattar.today08.description = Heutige Preise von 08:00 bis 09:00
channel-group-type.awattar.today09.label = Heute 09:00
channel-group-type.awattar.today09.description = Heutige Preise von 09:00 bis 10:00
channel-group-type.awattar.today10.label = Heute 10:00
channel-group-type.awattar.today10.description = Heutige Preise von 10:00 bis 11:00
channel-group-type.awattar.today11.label = Heute 11:00
channel-group-type.awattar.today11.description = Heutige Preise von 11:00 bis 12:00
channel-group-type.awattar.today12.label = Heute 12:00
channel-group-type.awattar.today12.description = Heutige Preise von 12:00 bis 13:00
channel-group-type.awattar.today13.label = Heute 13:00
channel-group-type.awattar.today13.description = Heutige Preise von 13:00 bis 14:00
channel-group-type.awattar.today14.label = Heute 14:00
channel-group-type.awattar.today14.description = Heutige Preise von 14:00 bis 15:00
channel-group-type.awattar.today15.label = Heute 15:00
channel-group-type.awattar.today15.description = Heutige Preise von 15:00 bis 16:00
channel-group-type.awattar.today16.label = Heute 16:00
channel-group-type.awattar.today16.description = Heutige Preise von 16:00 bis 17:00
channel-group-type.awattar.today17.label = Heute 17:00
channel-group-type.awattar.today17.description = Heutige Preise von 17:00 bis 18:00
channel-group-type.awattar.today18.label = Heute 18:00
channel-group-type.awattar.today18.description = Heutige Preise von 18:00 bis 19:00
channel-group-type.awattar.today19.label = Heute 19:00
channel-group-type.awattar.today19.description = Heutige Preise von 19:00 bis 20:00
channel-group-type.awattar.today20.label = Heute 20:00
channel-group-type.awattar.today20.description = Heutige Preise von 20:00 bis 21:00
channel-group-type.awattar.today21.label = Heute 21:00
channel-group-type.awattar.today21.description = Heutige Preise von 21:00 bis 22:00
channel-group-type.awattar.today22.label = Heute 22:00
channel-group-type.awattar.today22.description = Heutige Preise von 22:00 bis 23:00
channel-group-type.awattar.today23.label = Heute 23:00
channel-group-type.awattar.today23.description = Heutige Preise von 23:00 bis 00:00
channel-group-type.awattar.tomorrow00.label = Morgen 00:00
channel-group-type.awattar.tomorrow00.description = Morgige Preise von 00:00 bis 01:00
channel-group-type.awattar.tomorrow01.label = Morgen 01:00
channel-group-type.awattar.tomorrow01.description = Morgige Preise von 01:00 bis 02:00
channel-group-type.awattar.tomorrow02.label = Morgen 02:00
channel-group-type.awattar.tomorrow02.description = Morgige Preise von 02:00 bis 03:00
channel-group-type.awattar.tomorrow03.label = Morgen 03:00
channel-group-type.awattar.tomorrow03.description = Morgige Preise von 03:00 bis 04:00
channel-group-type.awattar.tomorrow04.label = Morgen 04:00
channel-group-type.awattar.tomorrow04.description = Morgige Preise von 04:00 bis 05:00
channel-group-type.awattar.tomorrow05.label = Morgen 05:00
channel-group-type.awattar.tomorrow05.description = Morgige Preise von 05:00 bis 06:00
channel-group-type.awattar.tomorrow06.label = Morgen 06:00
channel-group-type.awattar.tomorrow06.description = Morgige Preise von 06:00 bis 07:00
channel-group-type.awattar.tomorrow07.label = Morgen 07:00
channel-group-type.awattar.tomorrow07.description = Morgige Preise von 07:00 bis 08:00
channel-group-type.awattar.tomorrow08.label = Morgen 08:00
channel-group-type.awattar.tomorrow08.description = Morgige Preise von 08:00 bis 09:00
channel-group-type.awattar.tomorrow09.label = Morgen 09:00
channel-group-type.awattar.tomorrow09.description = Morgige Preise von 09:00 bis 10:00
channel-group-type.awattar.tomorrow10.label = Morgen 10:00
channel-group-type.awattar.tomorrow10.description = Morgige Preise von 10:00 bis 11:00
channel-group-type.awattar.tomorrow11.label = Morgen 11:00
channel-group-type.awattar.tomorrow11.description = Morgige Preise von 11:00 bis 12:00
channel-group-type.awattar.tomorrow12.label = Morgen 12:00
channel-group-type.awattar.tomorrow12.description = Morgige Preise von 12:00 bis 13:00
channel-group-type.awattar.tomorrow13.label = Morgen 13:00
channel-group-type.awattar.tomorrow13.description = Morgige Preise von 13:00 bis 14:00
channel-group-type.awattar.tomorrow14.label = Morgen 14:00
channel-group-type.awattar.tomorrow14.description = Morgige Preise von 14:00 bis 15:00
channel-group-type.awattar.tomorrow15.label = Morgen 15:00
channel-group-type.awattar.tomorrow15.description = Morgige Preise von 15:00 bis 16:00
channel-group-type.awattar.tomorrow16.label = Morgen 16:00
channel-group-type.awattar.tomorrow16.description = Morgige Preise von 16:00 bis 17:00
channel-group-type.awattar.tomorrow17.label = Morgen 17:00
channel-group-type.awattar.tomorrow17.description = Morgige Preise von 17:00 bis 18:00
channel-group-type.awattar.tomorrow18.label = Morgen 18:00
channel-group-type.awattar.tomorrow18.description = Morgige Preise von 18:00 bis 19:00
channel-group-type.awattar.tomorrow19.label = Morgen 19:00
channel-group-type.awattar.tomorrow19.description = Morgige Preise von 19:00 bis 20:00
channel-group-type.awattar.tomorrow20.label = Morgen 20:00
channel-group-type.awattar.tomorrow20.description = Morgige Preise von 20:00 bis 21:00
channel-group-type.awattar.tomorrow21.label = Morgen 21:00
channel-group-type.awattar.tomorrow21.description = Morgige Preise von 21:00 bis 22:00
channel-group-type.awattar.tomorrow22.label = Morgen 22:00
channel-group-type.awattar.tomorrow22.description = Morgige Preise von 22:00 bis 23:00
channel-group-type.awattar.tomorrow23.label = Morgen 23:00
channel-group-type.awattar.tomorrow23.description = Morgige Preise von 23:00 bis 00:00
error.config.missing=Konfiguration fehlt!
error.bridge.missing=Bridge fehlt!
error.channelgroup.missing=Channelgroup fehlt!
error.unsupported.country=Land wird nicht unterstützt, bitte DE oder AT verwenden
error.duration.value=Ungültiger Wert für Dauer
error.json=Ungültiges JSON von aWATTar
error.interrupted=Kommunikation unterbrochen
error.execution=Ausführungsfehler
error.timeout=Timeout beim Abrufen der Preise von aWATTar
error.invalid.data=Keine oder ungültige Daten von der aWATTar API erhalten
error.length.value=Length muss größer als 0 und kleiner als duration sein.
warn.awattar.statuscode=Der aWATTar Server antwortete nicht mit Statuscode 200
error.start.value=Ungültiger Startwert

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openweathermap"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="bridge">
<label>aWATTar Bridge</label>
<description>Provides price data from the aWATTar API.</description>
<config-description-ref uri="bridge-type:awattar:bridge"/>
</bridge-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,345 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="awattar"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="prices">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Hourly Price</label>
<description>Prices for one kilowatt-hour at the given hour in Cent</description>
<channel-groups>
<channel-group id="current" typeId="hourly-prices">
<label>Current Prices</label>
<description>The prices of the current hour</description>
</channel-group>
<channel-group id="today00" typeId="hourly-prices">
<label>Today 00:00</label>
<description>Todays prices from 00:00 to 01:00</description>
</channel-group>
<channel-group id="today01" typeId="hourly-prices">
<label>Today 01:00</label>
<description>Todays prices from 01:00 to 02:00</description>
</channel-group>
<channel-group id="today02" typeId="hourly-prices">
<label>Today 02:00</label>
<description>Todays prices from 02:00 to 03:00</description>
</channel-group>
<channel-group id="today03" typeId="hourly-prices">
<label>Today 03:00</label>
<description>Todays prices from 03:00 to 04:00</description>
</channel-group>
<channel-group id="today04" typeId="hourly-prices">
<label>Today 04:00</label>
<description>Todays prices from 04:00 to 05:00</description>
</channel-group>
<channel-group id="today05" typeId="hourly-prices">
<label>Today 05:00</label>
<description>Todays prices from 05:00 to 06:00</description>
</channel-group>
<channel-group id="today06" typeId="hourly-prices">
<label>Today 06:00</label>
<description>Todays prices from 06:00 to 07:00</description>
</channel-group>
<channel-group id="today07" typeId="hourly-prices">
<label>Today 07:00</label>
<description>Todays prices from 07:00 to 08:00</description>
</channel-group>
<channel-group id="today08" typeId="hourly-prices">
<label>Today 08:00</label>
<description>Todays prices from 08:00 to 09:00</description>
</channel-group>
<channel-group id="today09" typeId="hourly-prices">
<label>Today 09:00</label>
<description>Todays prices from 09:00 to 10:00</description>
</channel-group>
<channel-group id="today10" typeId="hourly-prices">
<label>Today 10:00</label>
<description>Todays prices from 10:00 to 11:00</description>
</channel-group>
<channel-group id="today11" typeId="hourly-prices">
<label>Today 11:00</label>
<description>Todays prices from 11:00 to 12:00</description>
</channel-group>
<channel-group id="today12" typeId="hourly-prices">
<label>Today 12:00</label>
<description>Todays prices from 12:00 to 13:00</description>
</channel-group>
<channel-group id="today13" typeId="hourly-prices">
<label>Today 13:00</label>
<description>Todays prices from 13:00 to 14:00</description>
</channel-group>
<channel-group id="today14" typeId="hourly-prices">
<label>Today 14:00</label>
<description>Todays prices from 14:00 to 15:00</description>
</channel-group>
<channel-group id="today15" typeId="hourly-prices">
<label>Today 15:00</label>
<description>Todays prices from 15:00 to 16:00</description>
</channel-group>
<channel-group id="today16" typeId="hourly-prices">
<label>Today 16:00</label>
<description>Todays prices from 16:00 to 17:00</description>
</channel-group>
<channel-group id="today17" typeId="hourly-prices">
<label>Today 17:00</label>
<description>Todays prices from 17:00 to 18:00</description>
</channel-group>
<channel-group id="today18" typeId="hourly-prices">
<label>Today 18:00</label>
<description>Todays prices from 18:00 to 19:00</description>
</channel-group>
<channel-group id="today19" typeId="hourly-prices">
<label>Today 19:00</label>
<description>Todays prices from 19:00 to 10:00</description>
</channel-group>
<channel-group id="today20" typeId="hourly-prices">
<label>Today 20:00</label>
<description>Todays prices from 20:00 to 21:00</description>
</channel-group>
<channel-group id="today21" typeId="hourly-prices">
<label>Today 21:00</label>
<description>Todays prices from 21:00 to 22:00</description>
</channel-group>
<channel-group id="today22" typeId="hourly-prices">
<label>Today 22:00</label>
<description>Todays prices from 22:00 to 23:00</description>
</channel-group>
<channel-group id="today23" typeId="hourly-prices">
<label>Today 23:00</label>
<description>Todays prices from 23:00 to 24:00</description>
</channel-group>
<!-- Tomorrow -->
<channel-group id="tomorrow00" typeId="hourly-prices">
<label>Tomorrow 00:00</label>
<description>Tomorrows prices from 00:00 to 01:00</description>
</channel-group>
<channel-group id="tomorrow01" typeId="hourly-prices">
<label>Tomorrow 01:00</label>
<description>Tomorrows prices from 01:00 to 02:00</description>
</channel-group>
<channel-group id="tomorrow02" typeId="hourly-prices">
<label>Tomorrow 02:00</label>
<description>Tomorrows prices from 02:00 to 03:00</description>
</channel-group>
<channel-group id="tomorrow03" typeId="hourly-prices">
<label>Tomorrow 03:00</label>
<description>Tomorrows prices from 03:00 to 04:00</description>
</channel-group>
<channel-group id="tomorrow04" typeId="hourly-prices">
<label>Tomorrow 04:00</label>
<description>Tomorrows prices from 04:00 to 05:00</description>
</channel-group>
<channel-group id="tomorrow05" typeId="hourly-prices">
<label>Tomorrow 05:00</label>
<description>Tomorrows prices from 05:00 to 06:00</description>
</channel-group>
<channel-group id="tomorrow06" typeId="hourly-prices">
<label>Tomorrow 06:00</label>
<description>Tomorrows prices from 06:00 to 07:00</description>
</channel-group>
<channel-group id="tomorrow07" typeId="hourly-prices">
<label>Tomorrow 07:00</label>
<description>Tomorrows prices from 07:00 to 08:00</description>
</channel-group>
<channel-group id="tomorrow08" typeId="hourly-prices">
<label>Tomorrow 08:00</label>
<description>Tomorrows prices from 08:00 to 09:00</description>
</channel-group>
<channel-group id="tomorrow09" typeId="hourly-prices">
<label>Tomorrow 09:00</label>
<description>Tomorrows prices from 09:00 to 10:00</description>
</channel-group>
<channel-group id="tomorrow10" typeId="hourly-prices">
<label>Tomorrow 10:00</label>
<description>Tomorrows prices from 10:00 to 11:00</description>
</channel-group>
<channel-group id="tomorrow11" typeId="hourly-prices">
<label>Tomorrow 11:00</label>
<description>Tomorrows prices from 11:00 to 12:00</description>
</channel-group>
<channel-group id="tomorrow12" typeId="hourly-prices">
<label>Tomorrow 12:00</label>
<description>Tomorrows prices from 12:00 to 13:00</description>
</channel-group>
<channel-group id="tomorrow13" typeId="hourly-prices">
<label>Tomorrow 13:00</label>
<description>Tomorrows prices from 13:00 to 14:00</description>
</channel-group>
<channel-group id="tomorrow14" typeId="hourly-prices">
<label>Tomorrow 14:00</label>
<description>Tomorrows prices from 14:00 to 15:00</description>
</channel-group>
<channel-group id="tomorrow15" typeId="hourly-prices">
<label>Tomorrow 15:00</label>
<description>Tomorrows prices from 15:00 to 16:00</description>
</channel-group>
<channel-group id="tomorrow16" typeId="hourly-prices">
<label>Tomorrow 16:00</label>
<description>Tomorrows prices from 16:00 to 17:00</description>
</channel-group>
<channel-group id="tomorrow17" typeId="hourly-prices">
<label>Tomorrow 17:00</label>
<description>Tomorrows prices from 17:00 to 18:00</description>
</channel-group>
<channel-group id="tomorrow18" typeId="hourly-prices">
<label>Tomorrow 18:00</label>
<description>Tomorrows prices from 18:00 to 19:00</description>
</channel-group>
<channel-group id="tomorrow19" typeId="hourly-prices">
<label>Tomorrow 19:00</label>
<description>Tomorrows prices from 19:00 to 10:00</description>
</channel-group>
<channel-group id="tomorrow20" typeId="hourly-prices">
<label>Tomorrow 20:00</label>
<description>Tomorrows prices from 20:00 to 21:00</description>
</channel-group>
<channel-group id="tomorrow21" typeId="hourly-prices">
<label>Tomorrow 21:00</label>
<description>Tomorrows prices from 21:00 to 22:00</description>
</channel-group>
<channel-group id="tomorrow22" typeId="hourly-prices">
<label>Tomorrow 22:00</label>
<description>Tomorrows prices from 22:00 to 23:00</description>
</channel-group>
<channel-group id="tomorrow23" typeId="hourly-prices">
<label>Tomorrow 23:00</label>
<description>Tomorrows prices from 23:00 to 24:00</description>
</channel-group>
</channel-groups>
</thing-type>
<thing-type id="bestprice">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>
Best Price
</label>
<description>Evaluates the lowest price period for the given settings</description>
<channels>
<channel id="active" typeId="switch-type">
<label>Active</label>
</channel>
<channel id="start" typeId="start-time-stamp"/>
<channel id="end" typeId="end-time-stamp"/>
<channel id="countdown" typeId="countdown-type"/>
<channel id="remaining" typeId="remaining-type"/>
<channel id="hours" typeId="hours-type"/>
</channels>
<config-description-ref uri="thing-type:awattar:bestprice"/>
</thing-type>
<channel-type id="price">
<item-type>Number</item-type>
<label>Price</label>
<description>Price in ct/kWh</description>
<state readOnly="true" pattern="%.3f ct"/>
</channel-type>
<channel-type id="input-duration">
<item-type>Number</item-type>
<label>Duration</label>
<description>Length of the bestprice period to search for (hours)</description>
<state readOnly="false" pattern="%d"/>
</channel-type>
<channel-type id="input-hours">
<item-type>Number</item-type>
<label>Lookup Time</label>
<description>How many hours from now should be checked?</description>
<state readOnly="false" pattern="%d"/>
</channel-type>
<channel-type id="input-switch">
<item-type>Switch</item-type>
<label>Consecutive</label>
<description>Consecutive range needed?</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="switch-type">
<item-type>Switch</item-type>
<label>Active</label>
<description>Currently activated</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="start-time-stamp">
<item-type>DateTime</item-type>
<label>Starttime</label>
<description>Starting time of period.</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tH:%1$tM"/>
</channel-type>
<channel-type id="end-time-stamp">
<item-type>DateTime</item-type>
<label>Endtime</label>
<description>End time of period.</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tH:%1$tM"/>
</channel-type>
<channel-type id="countdown-type">
<item-type>Number:Time</item-type>
<label>Countdown</label>
<description>Time until start of period.</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="remaining-type">
<item-type>Number:Time</item-type>
<label>Remaining</label>
<description>Time until end of period.</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="hours-type">
<item-type>String</item-type>
<label>Hours</label>
<description>A list of all hours within this bestprice range.</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
<channel-group-type id="hourly-prices">
<label>Hourly Prices</label>
<description>Hourly net and gross prices</description>
<channels>
<channel id="market-net" typeId="price">
<label>Net Marketprice</label>
</channel>
<channel id="market-gross" typeId="price">
<label>Gross Marketprice</label>
</channel>
<channel id="total-net" typeId="price">
<label>Net Total</label>
</channel>
<channel id="total-gross" typeId="price">
<label>Gross Total</label>
</channel>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -61,6 +61,7 @@
<module>org.openhab.binding.autelis</module>
<module>org.openhab.binding.automower</module>
<module>org.openhab.binding.avmfritz</module>
<module>org.openhab.binding.awattar</module>
<module>org.openhab.binding.benqprojector</module>
<module>org.openhab.binding.bigassfan</module>
<module>org.openhab.binding.bluetooth</module>