added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.tibber/.classpath
Normal file
32
bundles/org.openhab.binding.tibber/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.tibber/.project
Normal file
23
bundles/org.openhab.binding.tibber/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.tibber</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.tibber/NOTICE
Normal file
13
bundles/org.openhab.binding.tibber/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
136
bundles/org.openhab.binding.tibber/README.md
Normal file
136
bundles/org.openhab.binding.tibber/README.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Tibber Binding
|
||||
|
||||
The Tibber Binding connects to the [Tibber API](https://developer.tibber.com), and enables users to retrieve electricity data:
|
||||
|
||||
* Default: Frequent polls are performed to retrieve electricity price and cost/consumption information
|
||||
* Optional: For users having Tibber Pulse, a websocket connection is established to retrieve live measurements
|
||||
|
||||
Refresh time (poll frequency) is set manually as part of setup, minimum 1 minute.
|
||||
|
||||
Tibber Pulse will automatically be detected by the Binding if present and associated with the token/HomeID used for setup.
|
||||
|
||||
## Supported Things
|
||||
|
||||
Provided one have a Tibber User Account, the Tibber API is recognized as a thing in openHAB using the Tibber Binding.
|
||||
|
||||
Tibber Pulse is optional, but will enable live measurements.
|
||||
|
||||
The channels (i.e. measurements) associated with the Binding:
|
||||
|
||||
Tibber Default:
|
||||
|
||||
| Channel ID | Description | Read-only |
|
||||
|--------------------|-----------------------------------------|-----------|
|
||||
| Current Total | Current Total Price (energy + tax) | True |
|
||||
| Starts At | Current Price Timestamp | True |
|
||||
| Daily Cost | Daily Cost (last/previous day) | True |
|
||||
| Daily Consumption | Daily Consumption (last/previous day) | True |
|
||||
| Daily From | Timestamp (daily from) | True |
|
||||
| Daily To | Timestamp (daily to) | True |
|
||||
| Hourly Cost | Hourly Cost (last/previous hour) | True |
|
||||
| Hourly Consumption | Hourly Consumption (last/previous hour) | True |
|
||||
| Hourly From | Timestamp (hourly from) | True |
|
||||
| Hourly To | Timestamp (hourly to) | True |
|
||||
|
||||
Tibber Pulse (optional):
|
||||
|
||||
| Channel ID | Description | Read-only |
|
||||
|-------------------------|------------------------------------------|-----------|
|
||||
| Timestamp | Timestamp for live measurements | True |
|
||||
| Power | Live Power Consumption | True |
|
||||
| Last Meter Consumption | Last Recorded Meter Consumption | True |
|
||||
| Accumulated Consumption | Accumulated Consumption since Midnight | True |
|
||||
| Accumulated Cost | Accumulated Cost since Midnight | True |
|
||||
| Currency | Currency of Cost | True |
|
||||
| Min Power | Min Power Consumption since Midnight | True |
|
||||
| Average Power | Average Power Consumption since Midnight | True |
|
||||
| Max Power | Max Power Consumption since Midnight | True |
|
||||
| Voltage 1-3 | Voltage per Phase | True |
|
||||
| Current 1-3 | Current per Phase | True |
|
||||
| Power Production | Live Power Production | True |
|
||||
| Accumulated Production | Accumulated Production since Midnight | True |
|
||||
| Min Power Production | Min Power Production since Midnight | True |
|
||||
| Max Power Production | Max Power Production since Midnight | True |
|
||||
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
To access and initiate the Tibber Binding, a Tibber user account is required.
|
||||
|
||||
The following input is required for initialization:
|
||||
|
||||
* Tibber token
|
||||
* Tibber HomeId
|
||||
* Refresh Interval (min 1 minute)
|
||||
|
||||
Note: Tibber token is retrieved from your Tibber account:
|
||||
[Tibber Account](https://developer.tibber.com/settings/accesstoken)
|
||||
|
||||
Note: Tibber HomeId is retrieved from [www.developer.com](https://developer.tibber.com/explorer):
|
||||
|
||||
* Sign in (Tibber user account) and "load" personal token.
|
||||
* Copy query from below and paste into the Tibber API Explorer, and run query.
|
||||
* If Tibber Pulse is connected, the Tibber API Explorer will report "true" for "realTimeConsumptionEnabled"
|
||||
* Copy HomeId from Tibber API Explorer, without quotation marks, and paste into Paper UI configuration
|
||||
|
||||
|
||||
```
|
||||
{
|
||||
viewer {
|
||||
homes {
|
||||
id
|
||||
features {
|
||||
realTimeConsumptionEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If user have multiple HomeIds / Pulse, separate Things have to be created for the different/desired HomeIds in Paper UI.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
When Tibber Binding is installed, Tibber API should be autodiscovered in Paper UI.
|
||||
|
||||
Retrieve personal token and HomeId from description above, and initialize/start Binding from Paper UI.
|
||||
|
||||
Tibber API will be autodiscovered if provided input is correct.
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
Number:Dimensionless TibberAPICurrentTotal "Current Total Price [%.2f NOK]" {channel="tibber:tibberapi:7cfae492:current_total"}
|
||||
DateTime TibberAPICurrentStartsAt "Timestamp - Current Price" {channel="tibber:tibberapi:7cfae492:current_startsAt"}
|
||||
DateTime TibberAPIDailyFrom "Timestamp - Daily From" {channel="tibber:tibberapi:7cfae492:daily_from"}
|
||||
DateTime TibberAPIDailyTo "Timestamp - Daily To" {channel="tibber:tibberapi:7cfae492:daily_to"}
|
||||
Number:Dimensionless TibberAPIDailyCost "Total Daily Cost [%.2f NOK]" {channel="tibber:tibberapi:7cfae492:daily_cost"}
|
||||
Number:Energy TibberAPIDailyConsumption "Total Daily Consumption [%.2f kWh]" {channel="tibber:tibberapi:7cfae492:daily_consumption"}
|
||||
DateTime TibberAPIHourlyFrom "Timestamp - Hourly From" {channel="tibber:tibberapi:7cfae492:hourly_from"}
|
||||
DateTime TibberAPIHourlyTo "Timestamp - Hourly To" {channel="tibber:tibberapi:7cfae492:hourly_to"}
|
||||
Number:Dimensionless TibberAPIHourlyCost "Total Hourly Cost [%.2f NOK]" {channel="tibber:tibberapi:7cfae492:hourly_cost"}
|
||||
Number:Energy TibberAPIHourlyConsumption "Total Hourly Consumption [%.2f kWh]" {channel="tibber:tibberapi:7cfae492:hourly_consumption"}
|
||||
DateTime TibberAPILiveTimestamp "Timestamp - Live Measurement" {channel="tibber:tibberapi:7cfae492:live_timestamp"}
|
||||
Number:Power TibberAPILivePower "Live Power Consumption [W]" {channel="tibber:tibberapi:7cfae492:live_power"}
|
||||
Number:Energy TibberAPILiveLastMeterConsumption "Last Meter Consumption [%.2f kWh]" {channel="tibber:tibberapi:7cfae492:live_lastMeterConsumption"}
|
||||
Number:Energy TibberAPILiveAccumulatedConsumption "Accumulated Consumption [%.2f kWh]" {channel="tibber:tibberapi:7cfae492:live_accumulatedConsumption"}
|
||||
Number:Dimensionless TibberAPILiveAccumulatedCost "Accumulated Cost [%.2f NOK]" {channel="tibber:tibberapi:7cfae492:live_accumulatedCost"}
|
||||
String TibberAPILiveCurrency "Currency" {channel="tibber:tibberapi:7cfae492:live_currency"}
|
||||
Number:Power TibberAPILiveMinPower "Min Power Consumption [W]" {channel="tibber:tibberapi:7cfae492:live_minPower"}
|
||||
Number:Power TibberAPILiveAveragePower "Average Power Consumption [W]" {channel="tibber:tibberapi:7cfae492:live_averagePower"}
|
||||
Number:Power TibberAPILiveMaxPower "Max Power Consumption [W]" {channel="tibber:tibberapi:7cfae492:live_maxPower"}
|
||||
Number:ElectricPotential TibberAPILiveVoltage1 "Live Voltage Phase 1 [V]" {channel="tibber:tibberapi:7cfae492:live_voltage1"}
|
||||
Number:ElectricPotential TibberAPILiveVoltage2 "Live Voltage Phase 2 [V]" {channel="tibber:tibberapi:7cfae492:live_voltage2"}
|
||||
Number:ElectricPotential TibberAPILiveVoltage3 "Live Voltage Phase 3 [V]" {channel="tibber:tibberapi:7cfae492:live_voltage3"}
|
||||
Number:ElectricCurrent TibberAPILiveCurrent1 "Live Current Phase 1 [V]" {channel="tibber:tibberapi:7cfae492:live_current1"}
|
||||
Number:ElectricCurrent TibberAPILiveCurrent2 "Live Current Phase 2 [V]" {channel="tibber:tibberapi:7cfae492:live_current2"}
|
||||
Number:ElectricCurrent TibberAPILiveCurrent3 "Live Current Phase 3 [V]" {channel="tibber:tibberapi:7cfae492:live_current3"}
|
||||
Number:Power TibberAPILivePowerProduction "Live Power Production [W]" {channel="tibber:tibberapi:7cfae492:live_powerProduction"}
|
||||
Number:Power TibberAPILiveMinPowerproduction "Min Power Production [W]" {channel="tibber:tibberapi:7cfae492:live_minPowerproduction"}
|
||||
Number:Power TibberAPILiveMaxPowerproduction "Power consumption/production" {channel="tibber:tibberapi:7cfae492:live_maxPowerproduction"}
|
||||
Number:Energy TibberAPILiveAccumulatedProduction "Accumulated Production [%.2f kWh]" {channel="tibber:tibberapi:7cfae492:live_accumulatedProduction"}
|
||||
```
|
||||
17
bundles/org.openhab.binding.tibber/pom.xml
Normal file
17
bundles/org.openhab.binding.tibber/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.tibber</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Tibber Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.tibber-${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-tibber" description="tibber Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tibber/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.tibber.internal;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link TibberBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Stian Kjoglum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TibberBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "tibber";
|
||||
public static final String JSON_CONTENT_TYPE = "application/json";
|
||||
|
||||
// Tibber base URL for queries and mutations
|
||||
public static final String BASE_URL = "https://api.tibber.com/v1-beta/gql";
|
||||
|
||||
// Tibber websocket endpoint for live subscription
|
||||
public static final String SUBSCRIPTION_URL = "wss://api.tibber.com/v1-beta/gql/subscriptions";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID TIBBER_THING_TYPE = new ThingTypeUID(BINDING_ID, "tibberapi");
|
||||
|
||||
// List of all Channel ids - Used for queries
|
||||
public static final String CURRENT_TOTAL = "current_total";
|
||||
public static final String CURRENT_STARTSAT = "current_startsAt";
|
||||
public static final String DAILY_FROM = "daily_from";
|
||||
public static final String DAILY_TO = "daily_to";
|
||||
public static final String DAILY_COST = "daily_cost";
|
||||
public static final String DAILY_CONSUMPTION = "daily_consumption";
|
||||
public static final String HOURLY_FROM = "hourly_from";
|
||||
public static final String HOURLY_TO = "hourly_to";
|
||||
public static final String HOURLY_COST = "hourly_cost";
|
||||
public static final String HOURLY_CONSUMPTION = "hourly_consumption";
|
||||
public static final String LIVE_TIMESTAMP = "live_timestamp";
|
||||
public static final String LIVE_POWER = "live_power";
|
||||
public static final String LIVE_LASTMETERCONSUMPTION = "live_lastMeterConsumption";
|
||||
public static final String LIVE_ACCUMULATEDCONSUMPTION = "live_accumulatedConsumption";
|
||||
public static final String LIVE_ACCUMULATEDCOST = "live_accumulatedCost";
|
||||
public static final String LIVE_CURRENCY = "live_currency";
|
||||
public static final String LIVE_MINPOWER = "live_minPower";
|
||||
public static final String LIVE_AVERAGEPOWER = "live_averagePower";
|
||||
public static final String LIVE_MAXPOWER = "live_maxPower";
|
||||
public static final String LIVE_VOLTAGE1 = "live_voltage1";
|
||||
public static final String LIVE_VOLTAGE2 = "live_voltage2";
|
||||
public static final String LIVE_VOLTAGE3 = "live_voltage3";
|
||||
public static final String LIVE_CURRENT1 = "live_current1";
|
||||
public static final String LIVE_CURRENT2 = "live_current2";
|
||||
public static final String LIVE_CURRENT3 = "live_current3";
|
||||
public static final String LIVE_POWERPRODUCTION = "live_powerProduction";
|
||||
public static final String LIVE_ACCUMULATEDPRODUCTION = "live_accumulatedProduction";
|
||||
public static final String LIVE_MINPOWERPRODUCTION = "live_minPowerproduction";
|
||||
public static final String LIVE_MAXPOWERPRODUCTION = "live_maxPowerproduction";
|
||||
|
||||
// Lift of all config ids
|
||||
public static final String CONFIG_BRIDGE_TOKEN = "token";
|
||||
public static final String CONFIG_BRIDGE_REFRESH = "refresh";
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream.of(TIBBER_THING_TYPE)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.tibber.internal;
|
||||
|
||||
import static org.openhab.binding.tibber.internal.TibberBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tibber.internal.handler.TibberHandler;
|
||||
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.Component;
|
||||
|
||||
/**
|
||||
* The {@link TibberHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Stian Kjoglum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.tibber", service = ThingHandlerFactory.class)
|
||||
public class TibberHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
@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 (thingTypeUID.equals(TIBBER_THING_TYPE)) {
|
||||
return new TibberHandler(thing);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.tibber.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link TibberConfiguration} class contains fields mapping configuration parameters.
|
||||
*
|
||||
* @author Stian Kjoglum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TibberConfiguration {
|
||||
private String token = "";
|
||||
private String homeid = "";
|
||||
private int refresh;
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getHomeid() {
|
||||
return homeid;
|
||||
}
|
||||
|
||||
public int getRefresh() {
|
||||
return refresh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.tibber.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tibber.internal.TibberBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.tibber.internal.config.TibberConfiguration;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link TibberHandler} is responsible for handling queries to/from Tibber API.
|
||||
*
|
||||
* @author Stian Kjoglum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TibberHandler extends BaseThingHandler {
|
||||
private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(20);
|
||||
private final Logger logger = LoggerFactory.getLogger(TibberHandler.class);
|
||||
private final Properties httpHeader = new Properties();
|
||||
private final SslContextFactory sslContextFactory = new SslContextFactory(true);
|
||||
private final Executor websocketExecutor = ThreadPoolManager.getPool("tibber.websocket");
|
||||
private TibberConfiguration tibberConfig = new TibberConfiguration();
|
||||
private @Nullable TibberWebSocketListener socket;
|
||||
private @Nullable Session session;
|
||||
private @Nullable WebSocketClient client;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private @Nullable Future<?> sessionFuture;
|
||||
private String rtEnabled = "false";
|
||||
|
||||
public TibberHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
tibberConfig = getConfigAs(TibberConfiguration.class);
|
||||
|
||||
getTibberParameters();
|
||||
startRefresh(tibberConfig.getRefresh());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
startRefresh(tibberConfig.getRefresh());
|
||||
} else {
|
||||
logger.debug("Tibber API is read-only and does not handle commands");
|
||||
}
|
||||
}
|
||||
|
||||
public void getTibberParameters() {
|
||||
try {
|
||||
httpHeader.put("cache-control", "no-cache");
|
||||
httpHeader.put("content-type", JSON_CONTENT_TYPE);
|
||||
httpHeader.put("Authorization", "Bearer " + tibberConfig.getToken());
|
||||
|
||||
TibberPriceConsumptionHandler tibberQuery = new TibberPriceConsumptionHandler();
|
||||
InputStream connectionStream = tibberQuery.connectionInputStream(tibberConfig.getHomeid());
|
||||
String response = HttpUtil.executeUrl("POST", BASE_URL, httpHeader, connectionStream, null,
|
||||
REQUEST_TIMEOUT);
|
||||
|
||||
if (!response.contains("error") && !response.contains("<html>")) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
getURLInput(BASE_URL);
|
||||
|
||||
InputStream inputStream = tibberQuery.getRealtimeInputStream(tibberConfig.getHomeid());
|
||||
String jsonResponse = HttpUtil.executeUrl("POST", BASE_URL, httpHeader, inputStream, null,
|
||||
REQUEST_TIMEOUT);
|
||||
|
||||
JsonObject object = (JsonObject) new JsonParser().parse(jsonResponse);
|
||||
rtEnabled = object.getAsJsonObject("data").getAsJsonObject("viewer").getAsJsonObject("home")
|
||||
.getAsJsonObject("features").get("realTimeConsumptionEnabled").toString();
|
||||
|
||||
if ("true".equals(rtEnabled)) {
|
||||
logger.debug("Pulse associated with HomeId: Live stream will be started");
|
||||
open();
|
||||
} else {
|
||||
logger.debug("No Pulse associated with HomeId: No live stream will be started");
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Problems connecting/communicating with server: " + response);
|
||||
}
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void getURLInput(String url) throws IOException {
|
||||
TibberPriceConsumptionHandler tibberQuery = new TibberPriceConsumptionHandler();
|
||||
|
||||
InputStream inputStream = tibberQuery.getInputStream(tibberConfig.getHomeid());
|
||||
String jsonResponse = HttpUtil.executeUrl("POST", url, httpHeader, inputStream, null, REQUEST_TIMEOUT);
|
||||
logger.debug("API response: {}", jsonResponse);
|
||||
|
||||
if (!jsonResponse.contains("error") && !jsonResponse.contains("<html>")) {
|
||||
if (getThing().getStatus() == ThingStatus.OFFLINE || getThing().getStatus() == ThingStatus.INITIALIZING) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
JsonObject object = (JsonObject) new JsonParser().parse(jsonResponse);
|
||||
|
||||
if (jsonResponse.contains("total")) {
|
||||
try {
|
||||
JsonObject myObject = object.getAsJsonObject("data").getAsJsonObject("viewer")
|
||||
.getAsJsonObject("home").getAsJsonObject("currentSubscription").getAsJsonObject("priceInfo")
|
||||
.getAsJsonObject("current");
|
||||
|
||||
updateState(CURRENT_TOTAL, new DecimalType(myObject.get("total").toString()));
|
||||
String timestamp = myObject.get("startsAt").toString().substring(1, 20);
|
||||
updateState(CURRENT_STARTSAT, new DateTimeType(timestamp));
|
||||
|
||||
} catch (JsonSyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error communicating with Tibber API: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (jsonResponse.contains("daily")) {
|
||||
try {
|
||||
JsonObject myObject = (JsonObject) object.getAsJsonObject("data").getAsJsonObject("viewer")
|
||||
.getAsJsonObject("home").getAsJsonObject("daily").getAsJsonArray("nodes").get(0);
|
||||
|
||||
String timestampDailyFrom = myObject.get("from").toString().substring(1, 20);
|
||||
updateState(DAILY_FROM, new DateTimeType(timestampDailyFrom));
|
||||
|
||||
String timestampDailyTo = myObject.get("to").toString().substring(1, 20);
|
||||
updateState(DAILY_TO, new DateTimeType(timestampDailyTo));
|
||||
|
||||
updateChannel(DAILY_COST, myObject.get("cost").toString());
|
||||
updateChannel(DAILY_CONSUMPTION, myObject.get("consumption").toString());
|
||||
|
||||
} catch (JsonSyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error communicating with Tibber API: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (jsonResponse.contains("hourly")) {
|
||||
try {
|
||||
JsonObject myObject = (JsonObject) object.getAsJsonObject("data").getAsJsonObject("viewer")
|
||||
.getAsJsonObject("home").getAsJsonObject("hourly").getAsJsonArray("nodes").get(0);
|
||||
|
||||
String timestampHourlyFrom = myObject.get("from").toString().substring(1, 20);
|
||||
updateState(HOURLY_FROM, new DateTimeType(timestampHourlyFrom));
|
||||
|
||||
String timestampHourlyTo = myObject.get("to").toString().substring(1, 20);
|
||||
updateState(HOURLY_TO, new DateTimeType(timestampHourlyTo));
|
||||
|
||||
updateChannel(HOURLY_COST, myObject.get("cost").toString());
|
||||
updateChannel(HOURLY_CONSUMPTION, myObject.get("consumption").toString());
|
||||
|
||||
} catch (JsonSyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error communicating with Tibber API: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
} else if (jsonResponse.contains("error")) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error in response from Tibber API: " + jsonResponse);
|
||||
try {
|
||||
Thread.sleep(300 * 1000);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Tibber OFFLINE, attempting thread sleep: {}", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Unexpected response from Tibber: " + jsonResponse);
|
||||
try {
|
||||
Thread.sleep(300 * 1000);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Tibber OFFLINE, attempting thread sleep: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startRefresh(int refresh) {
|
||||
if (pollingJob == null) {
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
updateRequest();
|
||||
} catch (IOException e) {
|
||||
logger.warn("IO Exception: {}", e.getMessage());
|
||||
}
|
||||
}, 1, refresh, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRequest() throws IOException {
|
||||
getURLInput(BASE_URL);
|
||||
if ("true".equals(rtEnabled) && !isConnected()) {
|
||||
logger.debug("Attempting to reopen Websocket connection");
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateChannel(String channelID, String channelValue) {
|
||||
if (!channelValue.contains("null")) {
|
||||
if (channelID.contains("consumption") || channelID.contains("Consumption")
|
||||
|| channelID.contains("accumulatedProduction")) {
|
||||
updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), SmartHomeUnits.KILOWATT_HOUR));
|
||||
} else if (channelID.contains("power") || channelID.contains("Power")) {
|
||||
updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), SmartHomeUnits.WATT));
|
||||
} else if (channelID.contains("voltage")) {
|
||||
updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), SmartHomeUnits.VOLT));
|
||||
} else if (channelID.contains("live_current")) {
|
||||
updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), SmartHomeUnits.AMPERE));
|
||||
} else {
|
||||
updateState(channelID, new DecimalType(channelValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void thingStatusChanged(ThingStatusInfo thingStatusInfo) {
|
||||
logger.debug("Thing Status updated to {} for device: {}", thingStatusInfo.getStatus(), getThing().getUID());
|
||||
if (thingStatusInfo.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Unable to communicate with Tibber API");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
this.pollingJob = null;
|
||||
}
|
||||
if (isConnected()) {
|
||||
close();
|
||||
WebSocketClient client = this.client;
|
||||
if (client != null) {
|
||||
try {
|
||||
logger.debug("Stopping and Terminating Websocket connection");
|
||||
client.stop();
|
||||
client.destroy();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
|
||||
}
|
||||
this.client = null;
|
||||
}
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void open() {
|
||||
if (isConnected()) {
|
||||
logger.debug("Open: connection is already open");
|
||||
} else {
|
||||
sslContextFactory.setTrustAll(true);
|
||||
sslContextFactory.setEndpointIdentificationAlgorithm(null);
|
||||
|
||||
WebSocketClient client = this.client;
|
||||
if (client == null) {
|
||||
client = new WebSocketClient(sslContextFactory, websocketExecutor);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
TibberWebSocketListener socket = this.socket;
|
||||
if (socket == null) {
|
||||
socket = new TibberWebSocketListener();
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
ClientUpgradeRequest newRequest = new ClientUpgradeRequest();
|
||||
newRequest.setHeader("Authorization", "Bearer " + tibberConfig.getToken());
|
||||
newRequest.setSubProtocols("graphql-subscriptions");
|
||||
|
||||
try {
|
||||
logger.debug("Starting Websocket connection");
|
||||
client.start();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Websocket Start Exception: {}", e.getMessage());
|
||||
}
|
||||
try {
|
||||
logger.debug("Connecting Websocket connection");
|
||||
sessionFuture = client.connect(socket, new URI(SUBSCRIPTION_URL), newRequest);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Websocket Connect Exception: {}", e.getMessage());
|
||||
} catch (URISyntaxException e) {
|
||||
logger.warn("Websocket URI Exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
Session session = this.session;
|
||||
if (session != null) {
|
||||
String disconnect = "{\"type\":\"connection_terminate\",\"payload\":null}";
|
||||
try {
|
||||
TibberWebSocketListener socket = this.socket;
|
||||
if (socket != null) {
|
||||
logger.debug("Sending websocket disconnect message");
|
||||
socket.sendMessage(disconnect);
|
||||
} else {
|
||||
logger.debug("Socket unable to send disconnect message: Socket is null");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Websocket Close Exception: {}", e.getMessage());
|
||||
}
|
||||
session.close(0, "Tibber websocket disposed");
|
||||
this.session = null;
|
||||
this.socket = null;
|
||||
}
|
||||
Future<?> sessionFuture = this.sessionFuture;
|
||||
if (sessionFuture != null && !sessionFuture.isDone()) {
|
||||
sessionFuture.cancel(true);
|
||||
}
|
||||
WebSocketClient client = this.client;
|
||||
if (client != null) {
|
||||
try {
|
||||
client.stop();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to stop websocket client: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
Session session = this.session;
|
||||
return session != null && session.isOpen();
|
||||
}
|
||||
|
||||
@WebSocket
|
||||
@NonNullByDefault
|
||||
public class TibberWebSocketListener {
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session wssession) {
|
||||
TibberHandler.this.session = wssession;
|
||||
TibberWebSocketListener socket = TibberHandler.this.socket;
|
||||
String connection = "{\"type\":\"connection_init\", \"payload\":\"token=" + tibberConfig.getToken() + "\"}";
|
||||
try {
|
||||
if (socket != null) {
|
||||
logger.debug("Sending websocket connect message");
|
||||
socket.sendMessage(connection);
|
||||
} else {
|
||||
logger.debug("Socket unable to send connect message: Socket is null");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Send Message Exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onClose(int statusCode, String reason) {
|
||||
logger.debug("Closing a WebSocket due to {}", reason);
|
||||
WebSocketClient client = TibberHandler.this.client;
|
||||
if (client != null && client.isRunning()) {
|
||||
try {
|
||||
logger.debug("Stopping and Terminating Websocket connection");
|
||||
client.stop();
|
||||
client.destroy();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
TibberHandler.this.session = null;
|
||||
TibberHandler.this.client = null;
|
||||
TibberHandler.this.socket = null;
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onWebSocketError(Throwable e) {
|
||||
logger.debug("Error during websocket communication: {}", e.getMessage());
|
||||
onClose(0, e.getMessage());
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String message) {
|
||||
if (message.contains("connection_ack")) {
|
||||
logger.debug("Connected to Server");
|
||||
startSubscription();
|
||||
} else if (message.contains("error") || message.contains("terminate")) {
|
||||
logger.debug("Error/terminate received from server: {}", message);
|
||||
close();
|
||||
} else if (message.contains("liveMeasurement")) {
|
||||
JsonObject object = (JsonObject) new JsonParser().parse(message);
|
||||
JsonObject myObject = object.getAsJsonObject("payload").getAsJsonObject("data")
|
||||
.getAsJsonObject("liveMeasurement");
|
||||
if (myObject.has("timestamp")) {
|
||||
String liveTimestamp = myObject.get("timestamp").toString().substring(1, 20);
|
||||
updateState(LIVE_TIMESTAMP, new DateTimeType(liveTimestamp));
|
||||
}
|
||||
if (myObject.has("power")) {
|
||||
updateChannel(LIVE_POWER, myObject.get("power").toString());
|
||||
}
|
||||
if (myObject.has("lastMeterConsumption")) {
|
||||
updateChannel(LIVE_LASTMETERCONSUMPTION, myObject.get("lastMeterConsumption").toString());
|
||||
}
|
||||
if (myObject.has("accumulatedConsumption")) {
|
||||
updateChannel(LIVE_ACCUMULATEDCONSUMPTION, myObject.get("accumulatedConsumption").toString());
|
||||
}
|
||||
if (myObject.has("accumulatedCost")) {
|
||||
updateChannel(LIVE_ACCUMULATEDCOST, myObject.get("accumulatedCost").toString());
|
||||
}
|
||||
if (myObject.has("currency")) {
|
||||
updateState(LIVE_CURRENCY, new StringType(myObject.get("currency").toString()));
|
||||
}
|
||||
if (myObject.has("minPower")) {
|
||||
updateChannel(LIVE_MINPOWER, myObject.get("minPower").toString());
|
||||
}
|
||||
if (myObject.has("averagePower")) {
|
||||
updateChannel(LIVE_AVERAGEPOWER, myObject.get("averagePower").toString());
|
||||
}
|
||||
if (myObject.has("maxPower")) {
|
||||
updateChannel(LIVE_MAXPOWER, myObject.get("maxPower").toString());
|
||||
}
|
||||
if (myObject.has("voltagePhase1")) {
|
||||
updateChannel(LIVE_VOLTAGE1, myObject.get("voltagePhase1").toString());
|
||||
}
|
||||
if (myObject.has("voltagePhase2")) {
|
||||
updateChannel(LIVE_VOLTAGE2, myObject.get("voltagePhase2").toString());
|
||||
}
|
||||
if (myObject.has("voltagePhase3")) {
|
||||
updateChannel(LIVE_VOLTAGE3, myObject.get("voltagePhase3").toString());
|
||||
}
|
||||
if (myObject.has("currentPhase1")) {
|
||||
updateChannel(LIVE_CURRENT1, myObject.get("currentPhase1").toString());
|
||||
}
|
||||
if (myObject.has("currentPhase2")) {
|
||||
updateChannel(LIVE_CURRENT2, myObject.get("currentPhase2").toString());
|
||||
}
|
||||
if (myObject.has("currentPhase3")) {
|
||||
updateChannel(LIVE_CURRENT3, myObject.get("currentPhase3").toString());
|
||||
}
|
||||
if (myObject.has("powerProduction")) {
|
||||
updateChannel(LIVE_POWERPRODUCTION, myObject.get("powerProduction").toString());
|
||||
}
|
||||
if (myObject.has("accumulatedProduction")) {
|
||||
updateChannel(LIVE_ACCUMULATEDPRODUCTION, myObject.get("accumulatedProduction").toString());
|
||||
}
|
||||
if (myObject.has("minPowerProduction")) {
|
||||
updateChannel(LIVE_MINPOWERPRODUCTION, myObject.get("minPowerProduction").toString());
|
||||
}
|
||||
if (myObject.has("maxPowerProduction")) {
|
||||
updateChannel(LIVE_MAXPOWERPRODUCTION, myObject.get("maxPowerProduction").toString());
|
||||
}
|
||||
} else {
|
||||
logger.debug("Unknown live response from Tibber");
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(String message) throws IOException {
|
||||
logger.debug("Send message: {}", message);
|
||||
Session session = TibberHandler.this.session;
|
||||
if (session != null) {
|
||||
session.getRemote().sendString(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void startSubscription() {
|
||||
String query = "{\"id\":\"1\",\"type\":\"start\",\"payload\":{\"variables\":{},\"extensions\":{},\"operationName\":null,\"query\":\"subscription {\\n liveMeasurement(homeId:\\\""
|
||||
+ tibberConfig.getHomeid()
|
||||
+ "\\\") {\\n timestamp\\n power\\n lastMeterConsumption\\n accumulatedConsumption\\n accumulatedCost\\n currency\\n minPower\\n averagePower\\n maxPower\\n"
|
||||
+ "voltagePhase1\\n voltagePhase2\\n voltagePhase3\\n currentPhase1\\n currentPhase2\\n currentPhase3\\n powerProduction\\n accumulatedProduction\\n minPowerProduction\\n maxPowerProduction\\n }\\n }\\n\"}}";
|
||||
try {
|
||||
TibberWebSocketListener socket = TibberHandler.this.socket;
|
||||
if (socket != null) {
|
||||
socket.sendMessage(query);
|
||||
} else {
|
||||
logger.debug("Socket unable to send subscription message: Socket is null");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Send Message Exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.tibber.internal.handler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link TibberPriceConsumptionHandler} class contains fields mapping price info parameters.
|
||||
*
|
||||
* @author Stian Kjoglum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TibberPriceConsumptionHandler {
|
||||
|
||||
public InputStream connectionInputStream(String homeId) {
|
||||
String connectionquery = "{\"query\": \"{viewer {home (id: \\\"" + homeId + "\\\") {id }}}\"}";
|
||||
return new ByteArrayInputStream(connectionquery.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(String homeId) {
|
||||
String Query = "{\"query\": \"{viewer {home (id: \\\"" + homeId
|
||||
+ "\\\") {currentSubscription {priceInfo {current {total startsAt }}} daily: consumption(resolution: DAILY, last: 1) {nodes {from to cost unitPrice consumption consumptionUnit}} hourly: consumption(resolution: HOURLY, last: 1) {nodes {from to cost unitPrice consumption consumptionUnit}}}}}\"}";
|
||||
return new ByteArrayInputStream(Query.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public InputStream getRealtimeInputStream(String homeId) {
|
||||
String realtimeenabledquery = "{\"query\": \"{viewer {home (id: \\\"" + homeId
|
||||
+ "\\\") {features {realTimeConsumptionEnabled }}}}\"}";
|
||||
return new ByteArrayInputStream(realtimeenabledquery.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="tibber" 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>Tibber Binding</name>
|
||||
<description>This binding retrieves price and consumption data from Tibber API using queries and live subscription </description>
|
||||
<author>Stian Kjoglum</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="tibber"
|
||||
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="tibberapi">
|
||||
<label>Tibber API</label>
|
||||
<description>Tibber API connector</description>
|
||||
<channels>
|
||||
<channel id="current_total" typeId="price"/>
|
||||
<channel id="current_startsAt" typeId="timestamp"/>
|
||||
<channel id="daily_from" typeId="timestamp"/>
|
||||
<channel id="daily_to" typeId="timestamp"/>
|
||||
<channel id="daily_cost" typeId="cost"/>
|
||||
<channel id="daily_consumption" typeId="consumption"/>
|
||||
<channel id="hourly_from" typeId="timestamp"/>
|
||||
<channel id="hourly_to" typeId="timestamp"/>
|
||||
<channel id="hourly_cost" typeId="cost"/>
|
||||
<channel id="hourly_consumption" typeId="consumption"/>
|
||||
<channel id="live_timestamp" typeId="timestamp"/>
|
||||
<channel id="live_power" typeId="power"/>
|
||||
<channel id="live_lastMeterConsumption" typeId="consumption"/>
|
||||
<channel id="live_accumulatedConsumption" typeId="consumption"/>
|
||||
<channel id="live_accumulatedCost" typeId="cost"/>
|
||||
<channel id="live_currency" typeId="currency"/>
|
||||
<channel id="live_minPower" typeId="power"/>
|
||||
<channel id="live_averagePower" typeId="power"/>
|
||||
<channel id="live_maxPower" typeId="power"/>
|
||||
<channel id="live_voltage1" typeId="voltage"/>
|
||||
<channel id="live_voltage2" typeId="voltage"/>
|
||||
<channel id="live_voltage3" typeId="voltage"/>
|
||||
<channel id="live_current1" typeId="current"/>
|
||||
<channel id="live_current2" typeId="current"/>
|
||||
<channel id="live_current3" typeId="current"/>
|
||||
<channel id="live_powerProduction" typeId="power"/>
|
||||
<channel id="live_minPowerproduction" typeId="power"/>
|
||||
<channel id="live_maxPowerproduction" typeId="power"/>
|
||||
<channel id="live_accumulatedProduction" typeId="production"/>
|
||||
</channels>
|
||||
<properties>
|
||||
<property name="vendor">Tibber</property>
|
||||
</properties>
|
||||
<config-description>
|
||||
<parameter name="token" type="text" required="true">
|
||||
<label>Token</label>
|
||||
<description>Tibber Personal Token</description>
|
||||
</parameter>
|
||||
<parameter name="homeid" type="text" required="true">
|
||||
<label>HomeID</label>
|
||||
<description>Tibber Home ID</description>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" min="1" required="true">
|
||||
<label>Refresh Interval</label>
|
||||
<default>1</default>
|
||||
<description>Manually Set Polling Frequency (minutes)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<channel-type id="timestamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Timestamp</label>
|
||||
<description>Timestamp for measurement/change</description>
|
||||
</channel-type>
|
||||
<channel-type id="price">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Current Total Price</label>
|
||||
<description>Total Price: Energy + Tax</description>
|
||||
<state pattern="%.3f"></state>
|
||||
</channel-type>
|
||||
<channel-type id="cost">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Total Cost</label>
|
||||
<description>Cost at given time interval (e.g. hourly, daily, accumulated since midnight)</description>
|
||||
<state pattern="%.3f"></state>
|
||||
</channel-type>
|
||||
<channel-type id="consumption">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Consumption</label>
|
||||
<description>Consumption at given time interval (e.g. hourly, daily, last meter reading, accumulated since midnight)</description>
|
||||
<state pattern="%.3f %unit%"></state>
|
||||
</channel-type>
|
||||
<channel-type id="currency">
|
||||
<item-type>String</item-type>
|
||||
<label>Currency</label>
|
||||
<description>Currency of Displayed Cost</description>
|
||||
</channel-type>
|
||||
<channel-type id="power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Power consumption/production</label>
|
||||
<description>Power consumption/production for given time interval</description>
|
||||
<state pattern="%.1f %unit%"></state>
|
||||
</channel-type>
|
||||
<channel-type id="voltage">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Voltage</label>
|
||||
<description>Voltage on given Phase</description>
|
||||
<state pattern="%.1f %unit%"></state>
|
||||
</channel-type>
|
||||
<channel-type id="current">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Current</label>
|
||||
<description>Current on given Phase</description>
|
||||
<state pattern="%.1f %unit%"></state>
|
||||
</channel-type>
|
||||
<channel-type id="production">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Production</label>
|
||||
<description>Accumulated Production since Midnight</description>
|
||||
<state pattern="%.3f %unit%"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user