[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:
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user