[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
27 changed files with 2420 additions and 0 deletions

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>