added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View 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="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" 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>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.tado</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>

View File

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

View File

@@ -0,0 +1,231 @@
# tado° Binding
The tado° binding integrates devices from [tado°](https://www.tado.com).
It requires a fully functional tado° installation.
You can then monitor and control all zone types (Heating, AC, Hot Water) as well as retrieve the HOME/AWAY status of mobile devices.
## `home` Thing (the Bridge)
The binding supports discovery, but a `home` thing type has to be configured first.
It serves as bridge to the tado° cloud services.
Parameter | Required | Description
-|-|-
`username` | yes | Username used to log in at [my.tado](https://my.tado.com)
`password` | yes | Password of the username
Example `tado.things`
```
Bridge tado:home:demo [ username="mail@example.com", password="secret" ]
```
Afterwards the discovery will show all zones and mobile devices associated with the user's home.
## `zone` Thing
A *zone* is an area/room of your home.
You have defined them during installation.
One zone relates to one page in the tado° mobile- or webapp.
Parameter | Required | Description | Default
-|-|-|-
`id` | yes | Zone Id | -
`refreshInterval` | no | Refresh interval of state updates in seconds | 30
`hvacChangeDebounce` | no | Duration in seconds to combine multiple HVAC changes into one | 5
Zones can either be added through discovery or manually. Following up on the above example, a zone configuration could look like this:
Example `tado.things`
```
Bridge tado:home:demo [ username="mail@example.com", password="secret" ] {
zone heating [id=1]
zone ac [id=2]
zone hotwater [id=0]
}
```
Zone id and name can be found in discovery results.
### Channels
A zone is either of type `HEATING`, `AC` or `DHW` (domestic hot water).
The availability of items as well as their allowed values depend on type and capabilities of the HVAC setup.
If you are unsure, have a look at the tado° app and see if the functionality is available and what values are supported.
Name | Type | Description | Read/Write | Zone type
-|-|-|-|-
`currentTemperature` | Number:Temperature | Current inside temperature | R | `HEATING`, `AC`
`humidity` | Number | Current relative inside humidity in percent | R | `HEATING`, `AC`
`heatingPower` | Number | Amount of heating power currently present | R | `HEATING`
`acPower` | Switch | Indicates if the Air-Conditioning is Off or On | R | `AC`
`hvacMode` | String | Active mode, one of `OFF`, `HEAT`, `COOL`, `DRY`, `FAN`, `AUTO` | RW | `HEATING` and `DHW` support `OFF` and `HEAT`, `AC` can support more
`targetTemperature` | Number:Temperature | Set point | RW | `HEATING`, `AC`, `DHW`
`fanspeed` | String | Fan speed, one of `AUTO`, `LOW`, `MIDDLE`, `HIGH` | RW | `AC`
`swing` | Switch | Swing on/off | RW | `AC`
`overlayExpiry` | DateTime | End date and time of a timer | R | `HEATING`, `AC`, `DHW`
`timerDuration` | Number | Timer duration in minutes | RW | `HEATING`, `AC`, `DHW`
`operationMode` | String | Operation mode the zone is currently in. One of `SCHEDULE` (follow smart schedule), `MANUAL` (override until ended manually), `TIMER` (override for a given time), `UNTIL_CHANGE` (active until next smart schedule block or until AWAY mode becomes active) | RW | `HEATING`, `AC`, `DHW`
`batteryLowAlarm` | Switch | A control device in the Zone has a low battery (if applicable) | R | Any Zone
`openWindowDetected` | Switch | An open window has been detected in the Zone | R | Any Zone
The `RW` items are used to either override the schedule or to return to it (if `hvacMode` is set to `SCHEDULE`).
### Item Command Collection
Item changes are not immediately applied, but instead collected and only when no change is done for 5 seconds (by default - see `hvacChangeDebounce` above), the combined HVAC change is sent to the server.
This way, you can for example set a timer for 15 minutes, with target temperature 22° and mode `HEAT` in one go, without intermediate partial overrides.
It is still fine to only change one item, like setting the target temperature to 22°, but you have the opportunity to set more items and have less defaults applied.
### Default Handling
To set an override, the tado° cloud API requires a full setting (`hvacMode`, `targetTemperature`, `fanspeed`, `swing`) and a termination condition (`operationMode`, `timerDuration`).
If only some of the properties are set, the binding fills the missing pieces automatically.
It tries to keep the current state wherever possible.
If parts of the setting are missing, then the currently active zone setting is used to fill the gap. Only if the setting is not compatible with the requested change, then hard-coded defaults are applied.
- `hvacMode` is set to `HEAT` for heating and hot water, and set to `COOL` for AC
- `targetTemperature` for heating is `22°C / 72°F`, AC is set to `20°C / 68°F` and hot water to `50°C / 122°F`
- `fanspeed` is set to first supported value, for example `AUTO`
- `swing` is set to `OFF`, if supported
If the termination condition is missing, the binding first checks if an override is active.
If that is the case, the existing termination condition is used.
An existing timer, for example, just keeps running.
In case the zone is currently in smart-schedule mode and thus doesn't have a termination condition, then the default termination condition is used, as configured in the tado° app (settings -> select zone -> manual control on tado° device).
## `mobiledevice` Thing
The `mobiledevice` thing represents a smart phone that is configured for tado°. It provides access to the geotracking functionality.
Parameter | Required | Description | Default
-|-|-|-
`id` | yes | Mobile Device Id | -
`refreshInterval` | no | Refresh interval of state updates in seconds | 60
Mobile devices are part of discovery, but can also be configured manually.
It is again easiest to refer to discovery in order to find the `id`.
Example `tado.things`:
```
Bridge tado:home:demo [ username="mail@example.com", password="secret" ] {
mobiledevice phone [id=12345]
}
```
### Items
Name | Type | Description | Read/Write
-|-|-|-
`atHome` | Switch | ON if mobile device is in HOME mode, OFF if AWAY | R
Group `OR` can be used to define an item for *'is any device at home'*.
# Full Example
## tado.things
```
Bridge tado:home:demo [ username="mail@example.com", password="secret" ] {
zone heating [id=1]
zone ac [id=2]
zone hotwater [id=0]
mobiledevice phone [id=12345]
}
```
## tado.items
```
Number:Temperature HEAT_inside_temperature "Inside Temperature" { channel="tado:zone:demo:heating:currentTemperature" }
Number HEAT_humidity "Humidity" { channel="tado:zone:demo:heating:humidity" }
Number HEAT_heating_power "Heating Power" { channel="tado:zone:demo:heating:heatingPower" }
String HEAT_hvac_mode "HVAC Mode" { channel="tado:zone:demo:heating:hvacMode" }
Number:Temperature HEAT_target_temperature "Set Point" { channel="tado:zone:demo:heating:targetTemperature" }
DateTime HEAT_overlay_expiry "Overlay Expiry" { channel="tado:zone:demo:heating:overlayExpiry" }
Number HEAT_timer_duration "Timer Duration" { channel="tado:zone:demo:heating:timerDuration" }
String HEAT_operation_mode "Operation Mode" { channel="tado:zone:demo:heating:operationMode" }
Number:Temperature AC_inside_temperature "Inside Temperature" { channel="tado:zone:demo:ac:currentTemperature" }
Number AC_humidity "Humidity" { channel="tado:zone:demo:ac:humidity" }
String AC_hvac_mode "HVMode" { channel="tado:zone:demo:ac:hvacMode" }
Number:Temperature AC_target_temperature "Set Point" { channel="tado:zone:demo:ac:targetTemperature" }
String AC_fanspeed "Fan Speed" { channel="tado:zone:demo:ac:fanspeed" }
Switch AC_swing "Swing" { channel="tado:zone:demo:ac:swing" }
DateTime AC_overlay_expiry "Overlay Expiry" { channel="tado:zone:demo:ac:overlayExpiry" }
Number AC_timer_duration "Timer Duration" { channel="tado:zone:demo:ac:timerDuration" }
String AC_operation_mode "Operation Mode" { channel="tado:zone:demo:ac:operationMode" }
Switch AC_power "Air-Con Power" { channel="tado:zone:demo:ac:acPower" }
String DHW_hvac_mode "HVAC Mode" { channel="tado:zone:demo:hotwater:hvacMode" }
Number:Temperature DHW_target_temperature "Set Point" { channel="tado:zone:demo:hotwater:targetTemperature" }
DateTime DHW_overlay_expiry "Overlay Expiry" { channel="tado:zone:demo:hotwater:overlayExpiry" }
Number DHW_timer_duration "Timer Duration" { channel="tado:zone:demo:hotwater:timerDuration" }
String DHW_operation_mode "Operation Mode" { channel="tado:zone:demo:hotwater:operationMode" }
Switch Battery_Low_Alarm "Battery Low Alarm" { channel="tado:zone:demo:heating:batteryLowAlarm" }
Switch Phone_atHome "Phone location [MAP(presence.map):%s]" { channel="tado:mobiledevice:demo:phone:atHome" }
```
## tado.sitemap
```
sitemap tado label="Tado"
{
Frame label="Heating" {
Text item=HEAT_inside_temperature
Text item=HEAT_humidity
Text item=HEAT_heating_power
Setpoint item=HEAT_target_temperature minValue=5 maxValue=25
Selection item=HEAT_hvac_mode mappings=[OFF=off, HEAT=on]
Selection item=HEAT_operation_mode mappings=[SCHEDULE=schedule, MANUAL=manual, UNTIL_CHANGE="until change", TIMER=timer]
Setpoint item=HEAT_timer_duration minValue=5 maxValue=60 step=1
Text item=HEAT_overlay_expiry
}
Frame label="AC" {
Text item=AC_inside_temperature
Text item=AC_humidity
Setpoint item=AC_target_temperature minValue=16 maxValue=30
Selection item=AC_hvac_mode mappings=[OFF=off, HEAT=heat, COOL=cool, DRY=dry, FAN=fan, AUTO=auto]
Selection item=AC_operation_mode mappings=[SCHEDULE=schedule, MANUAL=manual, UNTIL_CHANGE="until change", TIMER=timer]
Selection item=AC_fanspeed mappings=[AUTO=auto, LOW=low, MIDDLE=middle, HIGH=high]
Switch item=AC_swing
Setpoint item=AC_timer_duration minValue=5 maxValue=60 step=1
Text item=AC_overlay_expiry
}
Frame label="Hot Water" {
Setpoint item=DHW_target_temperature minValue=30 maxValue=65
Selection item=DHW_hvac_mode mappings=[OFF=off, HEAT=on]
Selection item=DHW_operation_mode mappings=[SCHEDULE=schedule, MANUAL=manual, UNTIL_CHANGE="until change", TIMER=timer]
Setpoint item=DHW_timer_duration minValue=5 maxValue=60 step=1
Text item=DHW_overlay_expiry
}
Frame label="Battery Low Alarm" {
Text item=Battery_Low_Alarm valuecolor=[OFF="green", ON="red"]
}
Frame label="Mobile Devices" {
Text item=Phone_atHome
}
}
```
## presence.map
```
ON=at home
OFF=away
NULL=lost
```

View File

@@ -0,0 +1,26 @@
<?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.tado</artifactId>
<name>openHAB Add-ons :: Bundles :: Tado Binding</name>
<dependencies>
<dependency>
<groupId>org.openhab.binding.tado</groupId>
<artifactId>api-client</artifactId>
<version>1.3.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>org.openhab.binding.tado</groupId>
<artifactId>api-client</artifactId>
<version>1.3.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<pluginRepositories>
<pluginRepository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>io.swagger</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/tado-api.yaml</inputSpec>
<language>com.github.dfrommi.swagger.OpenHABClientGenerator</language>
<apiPackage>org.openhab.binding.tado.internal.api.client</apiPackage>
<modelPackage>org.openhab.binding.tado.internal.api.model</modelPackage>
<invokerPackage>org.openhab.binding.tado.internal.api</invokerPackage>
<configOptions>
<artifactId>tado</artifactId>
<artifactVersion>${project.version}</artifactVersion>
</configOptions>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.github.dfrommi</groupId>
<artifactId>swagger-codegen-openhab</artifactId>
<version>0.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>4.1.0</version>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
<outputDirectory>${project.basedir}/../../../lib</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>9.4.12.v20180830</version>
</dependency>
</dependencies>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.tado-${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-tado" description="Tado Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tado/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,109 @@
/**
* 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.tado.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link TadoBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Dennis Frommknecht - Initial contribution
* @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
*
*/
@NonNullByDefault
public class TadoBindingConstants {
public static final String BINDING_ID = "tado";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_HOME = new ThingTypeUID(BINDING_ID, "home");
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
public static final ThingTypeUID THING_TYPE_MOBILE_DEVICE = new ThingTypeUID(BINDING_ID, "mobiledevice");
// List of all Channel IDs
public static final String PROPERTY_HOME_TEMPERATURE_UNIT = "temperatureUnit";
public static enum TemperatureUnit {
CELSIUS,
FAHRENHEIT
}
public static final String CHANNEL_ZONE_CURRENT_TEMPERATURE = "currentTemperature";
public static final String CHANNEL_ZONE_HUMIDITY = "humidity";
public static final String CHANNEL_ZONE_HEATING_POWER = "heatingPower";
// air conditioning power
public static final String CHANNEL_ZONE_AC_POWER = "acPower";
public static final String CHANNEL_ZONE_HVAC_MODE = "hvacMode";
public static enum HvacMode {
OFF,
HEAT,
COOL,
DRY,
FAN,
AUTO
}
public static final String CHANNEL_ZONE_TARGET_TEMPERATURE = "targetTemperature";
public static final String CHANNEL_ZONE_SWING = "swing";
public static final String CHANNEL_ZONE_FAN_SPEED = "fanspeed";
public static enum FanSpeed {
LOW,
MIDDLE,
HIGH,
AUTO
}
public static final String CHANNEL_ZONE_OPERATION_MODE = "operationMode";
public static enum OperationMode {
SCHEDULE,
TIMER,
MANUAL,
UNTIL_CHANGE
}
public static final String CHANNEL_ZONE_TIMER_DURATION = "timerDuration";
public static final String CHANNEL_ZONE_OVERLAY_EXPIRY = "overlayExpiry";
// battery low alarm channel
public static final String CHANNEL_ZONE_BATTERY_LOW_ALARM = "batteryLowAlarm";
// open window detected channel
public static final String CHANNEL_ZONE_OPEN_WINDOW_DETECTED = "openWindowDetected";
public static final String CHANNEL_MOBILE_DEVICE_AT_HOME = "atHome";
// Configuration
public static final String CONFIG_ZONE_ID = "id";
public static final String CONFIG_MOBILE_DEVICE_ID = "id";
// Properties
public static final String PROPERTY_ZONE_NAME = "name";
public static final String PROPERTY_ZONE_TYPE = "type";
public static enum ZoneType {
HEATING,
AIR_CONDITIONING,
HOT_WATER
}
public static final String PROPERTY_MOBILE_DEVICE_NAME = "name";
}

View File

@@ -0,0 +1,138 @@
/**
* 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.tado.internal;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.Overlay;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.builder.TerminationConditionBuilder;
import org.openhab.binding.tado.internal.builder.ZoneSettingsBuilder;
import org.openhab.binding.tado.internal.builder.ZoneStateProvider;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
import org.openhab.core.thing.Thing;
/**
* Builder for incremental creation of zone overlays.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoHvacChange {
private TadoZoneHandler zoneHandler;
private boolean followSchedule = false;
private TerminationConditionBuilder terminationConditionBuilder;
private ZoneSettingsBuilder settingsBuilder;
public TadoHvacChange(Thing zoneThing) {
if (!(zoneThing.getHandler() instanceof TadoZoneHandler)) {
throw new IllegalArgumentException("TadoZoneThing expected, but instead got " + zoneThing);
}
this.zoneHandler = (TadoZoneHandler) zoneThing.getHandler();
this.terminationConditionBuilder = TerminationConditionBuilder.of(zoneHandler);
this.settingsBuilder = ZoneSettingsBuilder.of(zoneHandler);
}
public TadoHvacChange withOperationMode(OperationMode operationMode) {
switch (operationMode) {
case SCHEDULE:
return followSchedule();
case MANUAL:
return activeForever();
case TIMER:
return activeFor(null);
case UNTIL_CHANGE:
return activeUntilChange();
default:
return this;
}
}
public TadoHvacChange followSchedule() {
followSchedule = true;
return this;
}
public TadoHvacChange activeForever() {
terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.MANUAL);
return this;
}
public TadoHvacChange activeUntilChange() {
terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.TADO_MODE);
return this;
}
public TadoHvacChange activeFor(Integer minutes) {
terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.TIMER);
terminationConditionBuilder.withTimerDurationInSeconds(minutes != null ? minutes * 60 : null);
return this;
}
public TadoHvacChange withTemperature(float temperatureValue) {
settingsBuilder.withTemperature(temperatureValue, zoneHandler.getTemperatureUnit());
return this;
}
public TadoHvacChange withHvacMode(HvacMode mode) {
settingsBuilder.withMode(mode);
return this;
}
public TadoHvacChange withHvacMode(String mode) {
return withHvacMode(HvacMode.valueOf(mode.toUpperCase()));
}
public TadoHvacChange withSwing(boolean swingOn) {
settingsBuilder.withSwing(swingOn);
return this;
}
public TadoHvacChange withFanSpeed(FanSpeed fanSpeed) {
settingsBuilder.withFanSpeed(fanSpeed);
return this;
}
public TadoHvacChange withFanSpeed(String fanSpeed) {
withFanSpeed(FanSpeed.valueOf(fanSpeed.toUpperCase()));
return this;
}
public void apply() throws IOException, ApiException {
if (followSchedule) {
zoneHandler.removeOverlay();
} else {
Overlay overlay = buildOverlay();
zoneHandler.setOverlay(overlay);
}
}
private Overlay buildOverlay() throws IOException, ApiException {
ZoneStateProvider zoneStateProvider = new ZoneStateProvider(zoneHandler);
OverlayTerminationCondition terminationCondition = terminationConditionBuilder.build(zoneStateProvider);
GenericZoneSetting setting = settingsBuilder.build(zoneStateProvider, zoneHandler.getZoneCapabilities());
Overlay overlay = new Overlay();
overlay.setTermination(terminationCondition);
overlay.setSetting(setting);
return overlay;
}
}

View File

@@ -0,0 +1,237 @@
/**
* 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.tado.internal.adapter;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.model.AcPowerDataPoint;
import org.openhab.binding.tado.internal.api.model.ActivityDataPoints;
import org.openhab.binding.tado.internal.api.model.CoolingZoneSetting;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.HeatingZoneSetting;
import org.openhab.binding.tado.internal.api.model.HotWaterZoneSetting;
import org.openhab.binding.tado.internal.api.model.Overlay;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.api.model.Power;
import org.openhab.binding.tado.internal.api.model.SensorDataPoints;
import org.openhab.binding.tado.internal.api.model.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureDataPoint;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.api.model.TimerTerminationCondition;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* Adapter from API-level zone state to the binding's item-based zone state.
*
* @author Dennis Frommknecht - Initial contribution
* @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
*
*/
public class TadoZoneStateAdapter {
private ZoneState zoneState;
private TemperatureUnit temperatureUnit;
public TadoZoneStateAdapter(ZoneState zoneState, TemperatureUnit temperatureUnit) {
this.zoneState = zoneState;
this.temperatureUnit = temperatureUnit;
}
public State getInsideTemperature() {
SensorDataPoints sensorDataPoints = zoneState.getSensorDataPoints();
return toTemperatureState(sensorDataPoints.getInsideTemperature(), temperatureUnit);
}
public DecimalType getHumidity() {
SensorDataPoints sensorDataPoints = zoneState.getSensorDataPoints();
return sensorDataPoints.getHumidity() != null ? toDecimalType(sensorDataPoints.getHumidity().getPercentage())
: null;
}
public DecimalType getHeatingPower() {
ActivityDataPoints dataPoints = zoneState.getActivityDataPoints();
return dataPoints.getHeatingPower() != null ? toDecimalType(dataPoints.getHeatingPower().getPercentage())
: DecimalType.ZERO;
}
public OnOffType getAcPower() {
ActivityDataPoints dataPoints = zoneState.getActivityDataPoints();
AcPowerDataPoint acPower = dataPoints.getAcPower();
if (acPower != null) {
String acPowerValue = acPower.getValue();
if (acPowerValue != null) {
return OnOffType.from(acPowerValue);
}
}
return null;
}
public StringType getMode() {
GenericZoneSetting setting = zoneState.getSetting();
if (!isPowerOn()) {
return StringType.valueOf(HvacMode.OFF.name());
} else if (setting.getType() == TadoSystemType.HEATING || setting.getType() == TadoSystemType.HOT_WATER) {
return StringType.valueOf(HvacMode.HEAT.name());
} else {
return StringType.valueOf(((CoolingZoneSetting) setting).getMode().getValue());
}
}
public State getTargetTemperature() {
switch (zoneState.getSetting().getType()) {
case HEATING:
return toTemperatureState(((HeatingZoneSetting) zoneState.getSetting()).getTemperature(),
temperatureUnit);
case AIR_CONDITIONING:
return toTemperatureState(((CoolingZoneSetting) zoneState.getSetting()).getTemperature(),
temperatureUnit);
case HOT_WATER:
return toTemperatureState(((HotWaterZoneSetting) zoneState.getSetting()).getTemperature(),
temperatureUnit);
default:
return UnDefType.UNDEF;
}
}
public State getFanSpeed() {
if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
CoolingZoneSetting setting = (CoolingZoneSetting) zoneState.getSetting();
return setting.getFanSpeed() != null ? StringType.valueOf(setting.getFanSpeed().getValue())
: UnDefType.NULL;
} else {
return UnDefType.UNDEF;
}
}
public State getSwing() {
if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
CoolingZoneSetting setting = (CoolingZoneSetting) zoneState.getSetting();
if (setting.getSwing() == null) {
return UnDefType.NULL;
} else if (setting.getSwing() == Power.ON) {
return OnOffType.ON;
} else {
return OnOffType.OFF;
}
} else {
return UnDefType.UNDEF;
}
}
public StringType getOperationMode() {
Overlay overlay = zoneState.getOverlay();
if (overlay != null) {
switch (overlay.getTermination().getType()) {
case MANUAL:
return new StringType(OperationMode.MANUAL.name());
case TIMER:
return new StringType(OperationMode.TIMER.name());
case TADO_MODE:
return new StringType(OperationMode.UNTIL_CHANGE.name());
}
}
return new StringType(OperationMode.SCHEDULE.name());
}
public DecimalType getRemainingTimerDuration() {
Overlay overlay = zoneState.getOverlay();
if (overlay != null && overlay.getTermination().getType() == OverlayTerminationConditionType.TIMER) {
TimerTerminationCondition timerTerminationCondition = (TimerTerminationCondition) overlay.getTermination();
Integer remainingTimeInSeconds = timerTerminationCondition.getRemainingTimeInSeconds();
// If there's a timer overlay, then the timer should never be smaller than 1
return new DecimalType((int) Math.max(1.0, Math.round(remainingTimeInSeconds / 60.0)));
}
return new DecimalType();
}
public State getOverlayExpiration() {
Overlay overlay = zoneState.getOverlay();
if (overlay == null || overlay.getTermination().getProjectedExpiry() == null) {
return UnDefType.UNDEF;
}
return toDateTimeType(overlay.getTermination().getProjectedExpiry());
}
public boolean isPowerOn() {
Power power = Power.OFF;
GenericZoneSetting setting = zoneState.getSetting();
switch (setting.getType()) {
case HEATING:
power = ((HeatingZoneSetting) setting).getPower();
break;
case AIR_CONDITIONING:
power = ((CoolingZoneSetting) setting).getPower();
break;
case HOT_WATER:
power = ((HotWaterZoneSetting) setting).getPower();
break;
}
return power.getValue().equals("ON");
}
private static DecimalType toDecimalType(double value) {
BigDecimal decimal = new BigDecimal(value).setScale(2, BigDecimal.ROUND_HALF_UP);
return new DecimalType(decimal);
}
private static DateTimeType toDateTimeType(OffsetDateTime offsetDateTime) {
return new DateTimeType(offsetDateTime.toZonedDateTime());
}
private static State toTemperatureState(TemperatureObject temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return UnDefType.NULL;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT
? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT)
: new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS);
}
private static State toTemperatureState(TemperatureDataPoint temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return UnDefType.NULL;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT
? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT)
: new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS);
}
public State getOpenWindowDetected() {
Boolean openWindowDetected = zoneState.isOpenWindowDetected();
if (openWindowDetected != null) {
return OnOffType.from(openWindowDetected);
}
return OnOffType.OFF;
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.tado.internal.api;
import org.openhab.binding.tado.internal.api.auth.Authorizer;
import org.openhab.binding.tado.internal.api.auth.OAuthAuthorizer;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import com.google.gson.Gson;
/**
* Factory to create and configure {@link HomeApi} instances.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class HomeApiFactory {
private static final String OAUTH_SCOPE = "home.user";
private static final String OAUTH_CLIENT_ID = "public-api-preview";
private static final String OAUTH_CLIENT_SECRET = "4HJGRffVR8xb3XdEUQpjgZ1VplJi6Xgw";
public HomeApi create(String username, String password) {
Gson gson = GsonBuilderFactory.defaultGsonBuilder().create();
Authorizer authorizer = new OAuthAuthorizer().passwordFlow(username, password).clientId(OAUTH_CLIENT_ID)
.clientSecret(OAUTH_CLIENT_SECRET).scopes(OAUTH_SCOPE);
return new HomeApi(gson, authorizer);
}
}

View File

@@ -0,0 +1,172 @@
/**
* 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.tado.internal.api;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.model.AcFanSpeed;
import org.openhab.binding.tado.internal.api.model.AcMode;
import org.openhab.binding.tado.internal.api.model.AcModeCapabilities;
import org.openhab.binding.tado.internal.api.model.AirConditioningCapabilities;
import org.openhab.binding.tado.internal.api.model.ManualTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionTemplate;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.api.model.TadoModeTerminationCondition;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.api.model.TimerTerminationCondition;
import org.openhab.binding.tado.internal.api.model.TimerTerminationConditionTemplate;
/**
* Utility methods for the conversion of API types.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoApiTypeUtils {
public static OverlayTerminationCondition getTerminationCondition(OverlayTerminationConditionType type,
Integer timerDurationInSeconds) {
switch (type) {
case TIMER:
return timerTermination(timerDurationInSeconds);
case MANUAL:
return manualTermination();
case TADO_MODE:
return tadoModeTermination();
default:
return null;
}
}
public static OverlayTerminationCondition cleanTerminationCondition(
OverlayTerminationCondition terminationCondition) {
Integer timerDuration = terminationCondition.getType() == OverlayTerminationConditionType.TIMER
? ((TimerTerminationCondition) terminationCondition).getRemainingTimeInSeconds()
: null;
return getTerminationCondition(terminationCondition.getType(), timerDuration);
}
public static OverlayTerminationCondition terminationConditionTemplateToTerminationCondition(
OverlayTerminationConditionTemplate template) {
Integer timerDuration = template.getType() == OverlayTerminationConditionType.TIMER
? ((TimerTerminationConditionTemplate) template).getDurationInSeconds()
: null;
return getTerminationCondition(template.getType(), timerDuration);
}
public static TimerTerminationCondition timerTermination(int durationInSeconds) {
TimerTerminationCondition terminationCondition = new TimerTerminationCondition();
terminationCondition.setType(OverlayTerminationConditionType.TIMER);
terminationCondition.setDurationInSeconds(durationInSeconds);
return terminationCondition;
}
public static ManualTerminationCondition manualTermination() {
ManualTerminationCondition terminationCondition = new ManualTerminationCondition();
terminationCondition.setType(OverlayTerminationConditionType.MANUAL);
return terminationCondition;
}
public static TadoModeTerminationCondition tadoModeTermination() {
TadoModeTerminationCondition terminationCondition = new TadoModeTerminationCondition();
terminationCondition.setType(OverlayTerminationConditionType.TADO_MODE);
return terminationCondition;
}
public static TemperatureObject temperature(float degree, TemperatureUnit temperatureUnit) {
TemperatureObject temperature = new TemperatureObject();
if (temperatureUnit == TemperatureUnit.FAHRENHEIT) {
temperature.setFahrenheit(degree);
} else {
temperature.setCelsius(degree);
}
return temperature;
}
public static Float getTemperatureInUnit(TemperatureObject temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return null;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT ? temperature.getFahrenheit() : temperature.getCelsius();
}
public static AcMode getAcMode(HvacMode mode) {
if (mode == null) {
return null;
}
switch (mode) {
case HEAT:
return AcMode.HEAT;
case COOL:
return AcMode.COOL;
case FAN:
return AcMode.FAN;
case DRY:
return AcMode.DRY;
case AUTO:
return AcMode.AUTO;
default:
return null;
}
}
public static AcFanSpeed getAcFanSpeed(FanSpeed fanSpeed) {
if (fanSpeed == null) {
return null;
}
switch (fanSpeed) {
case AUTO:
return AcFanSpeed.AUTO;
case HIGH:
return AcFanSpeed.HIGH;
case MIDDLE:
return AcFanSpeed.MIDDLE;
case LOW:
return AcFanSpeed.LOW;
}
return null;
}
public static AcModeCapabilities getModeCapabilities(AirConditioningCapabilities capabilities, AcMode mode) {
AcModeCapabilities modeCapabilities = null;
if (mode != null) {
switch (mode) {
case COOL:
modeCapabilities = capabilities.getCOOL();
break;
case HEAT:
modeCapabilities = capabilities.getHEAT();
break;
case DRY:
modeCapabilities = capabilities.getDRY();
break;
case AUTO:
modeCapabilities = capabilities.getAUTO();
break;
case FAN:
modeCapabilities = capabilities.getFAN();
break;
}
}
return modeCapabilities != null ? modeCapabilities : new AcModeCapabilities();
}
}

View File

@@ -0,0 +1,153 @@
/**
* 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.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*;
import java.io.IOException;
import java.util.List;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.AcFanSpeed;
import org.openhab.binding.tado.internal.api.model.AcMode;
import org.openhab.binding.tado.internal.api.model.AcModeCapabilities;
import org.openhab.binding.tado.internal.api.model.AirConditioningCapabilities;
import org.openhab.binding.tado.internal.api.model.CoolingZoneSetting;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.IntRange;
import org.openhab.binding.tado.internal.api.model.Power;
import org.openhab.binding.tado.internal.api.model.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.api.model.TemperatureRange;
/**
*
*
* @author Dennis Frommknecht - Initial contribution
*/
public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final AcMode DEFAULT_MODE = AcMode.COOL;
private static final float DEFAULT_TEMPERATURE_C = 20.0f;
private static final float DEFAULT_TEMPERATURE_F = 68.0f;
@Override
public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities genericCapabilities)
throws IOException, ApiException {
if (mode == HvacMode.OFF) {
return coolingSetting(false);
}
CoolingZoneSetting setting = coolingSetting(true);
setting.setMode(getAcMode(mode));
if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit));
}
if (swing != null) {
setting.setSwing(swing ? Power.ON : Power.OFF);
}
if (fanSpeed != null) {
setting.setFanSpeed(getAcFanSpeed(fanSpeed));
}
addMissingSettingParts(zoneStateProvider, genericCapabilities, setting);
return setting;
}
private void addMissingSettingParts(ZoneStateProvider zoneStateProvider,
GenericZoneCapabilities genericCapabilities, CoolingZoneSetting setting) throws IOException, ApiException {
if (setting.getMode() == null) {
AcMode targetMode = getCurrentOrDefaultAcMode(zoneStateProvider);
setting.setMode(targetMode);
}
AcModeCapabilities capabilities = getModeCapabilities((AirConditioningCapabilities) genericCapabilities,
setting.getMode());
if (capabilities.getTemperatures() != null && setting.getTemperature() == null) {
TemperatureObject targetTemperature = getCurrentOrDefaultTemperature(zoneStateProvider,
capabilities.getTemperatures());
setting.setTemperature(targetTemperature);
}
if (capabilities.getFanSpeeds() != null && !capabilities.getFanSpeeds().isEmpty()
&& setting.getFanSpeed() == null) {
AcFanSpeed fanSpeed = getCurrentOrDefaultFanSpeed(zoneStateProvider, capabilities.getFanSpeeds());
setting.setFanSpeed(fanSpeed);
}
if (capabilities.getSwings() != null && !capabilities.getSwings().isEmpty() && setting.getSwing() == null) {
Power swing = getCurrentOrDefaultSwing(zoneStateProvider, capabilities.getSwings());
setting.setSwing(swing);
}
}
private AcMode getCurrentOrDefaultAcMode(ZoneStateProvider zoneStateProvider) throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
return zoneSetting.getMode() != null ? zoneSetting.getMode() : DEFAULT_MODE;
}
private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider,
TemperatureRange temperatureRanges) throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
Float defaultTemperature = temperatureUnit == TemperatureUnit.FAHRENHEIT ? DEFAULT_TEMPERATURE_F
: DEFAULT_TEMPERATURE_C;
Float temperature = (zoneSetting != null && zoneSetting.getTemperature() != null)
? getTemperatureInUnit(zoneSetting.getTemperature(), temperatureUnit)
: defaultTemperature;
IntRange temperatureRange = temperatureUnit == TemperatureUnit.FAHRENHEIT ? temperatureRanges.getFahrenheit()
: temperatureRanges.getCelsius();
Float finalTemperature = temperatureRange.getMax() >= temperature && temperatureRange.getMin() <= temperature
? temperature
: temperatureRange.getMax();
return temperature(finalTemperature, temperatureUnit);
}
private AcFanSpeed getCurrentOrDefaultFanSpeed(ZoneStateProvider zoneStateProvider, List<AcFanSpeed> fanSpeeds)
throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting.getFanSpeed() != null && fanSpeeds.contains(zoneSetting.getFanSpeed())) {
return zoneSetting.getFanSpeed();
}
return fanSpeeds.get(0);
}
private Power getCurrentOrDefaultSwing(ZoneStateProvider zoneStateProvider, List<Power> swings)
throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting.getSwing() != null && swings.contains(zoneSetting.getSwing())) {
return zoneSetting.getSwing();
}
return swings.get(0);
}
private CoolingZoneSetting coolingSetting(boolean powerOn) {
CoolingZoneSetting setting = new CoolingZoneSetting();
setting.setType(TadoSystemType.AIR_CONDITIONING);
setting.setPower(powerOn ? Power.ON : Power.OFF);
return setting;
}
}

View File

@@ -0,0 +1,91 @@
/**
* 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.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.HeatingZoneSetting;
import org.openhab.binding.tado.internal.api.model.Power;
import org.openhab.binding.tado.internal.api.model.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
/**
* Builder for incremental creation of heating zone settings.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final float DEFAULT_TEMPERATURE_C = 22.0f;
private static final float DEFAULT_TEMPERATURE_F = 72.0f;
@Override
public ZoneSettingsBuilder withSwing(boolean swingOn) {
throw new IllegalArgumentException("Heating zones don't support SWING");
}
@Override
public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
throw new IllegalArgumentException("Heating zones don't support FAN SPEED");
}
@Override
public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
throws IOException, ApiException {
if (mode == HvacMode.OFF) {
return heatingSetting(false);
}
HeatingZoneSetting setting = heatingSetting(true);
if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit));
}
addMissingSettingParts(setting, zoneStateProvider);
return setting;
}
private void addMissingSettingParts(HeatingZoneSetting setting, ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
if (setting.getTemperature() == null) {
TemperatureObject temperatureObject = getCurrentOrDefaultTemperature(zoneStateProvider);
setting.setTemperature(temperatureObject);
}
}
private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
HeatingZoneSetting zoneSetting = (HeatingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting != null && zoneSetting.getTemperature() != null) {
return truncateTemperature(zoneSetting.getTemperature());
}
return buildDefaultTemperatureObject(DEFAULT_TEMPERATURE_C, DEFAULT_TEMPERATURE_F);
}
private HeatingZoneSetting heatingSetting(boolean powerOn) {
HeatingZoneSetting setting = new HeatingZoneSetting();
setting.setType(TadoSystemType.HEATING);
setting.setPower(powerOn ? Power.ON : Power.OFF);
return setting;
}
}

View File

@@ -0,0 +1,92 @@
/**
* 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.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.HotWaterCapabilities;
import org.openhab.binding.tado.internal.api.model.HotWaterZoneSetting;
import org.openhab.binding.tado.internal.api.model.Power;
import org.openhab.binding.tado.internal.api.model.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
/**
* Builder for incremental creation of hot water zone settings.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final float DEFAULT_TEMPERATURE_C = 50.0f;
private static final float DEFAULT_TEMPERATURE_F = 122.0f;
@Override
public ZoneSettingsBuilder withSwing(boolean swingOn) {
throw new IllegalArgumentException("Hot Water zones don't support SWING");
}
@Override
public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
throw new IllegalArgumentException("Hot Water zones don't support FAN SPEED");
}
@Override
public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
throws IOException, ApiException {
if (mode == HvacMode.OFF) {
return hotWaterSetting(false);
}
HotWaterZoneSetting setting = hotWaterSetting(true);
if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit));
}
addMissingSettingParts(setting, zoneStateProvider, (HotWaterCapabilities) capabilities);
return setting;
}
private void addMissingSettingParts(HotWaterZoneSetting setting, ZoneStateProvider zoneStateProvider,
HotWaterCapabilities capabilities) throws IOException, ApiException {
if (capabilities.isCanSetTemperature() && setting.getTemperature() == null) {
TemperatureObject temperatureObject = getCurrentOrDefaultTemperature(zoneStateProvider);
setting.setTemperature(temperatureObject);
}
}
private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
HotWaterZoneSetting zoneSetting = (HotWaterZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting != null && zoneSetting.getTemperature() != null) {
return truncateTemperature(zoneSetting.getTemperature());
}
return buildDefaultTemperatureObject(DEFAULT_TEMPERATURE_C, DEFAULT_TEMPERATURE_F);
}
private HotWaterZoneSetting hotWaterSetting(boolean powerOn) {
HotWaterZoneSetting setting = new HotWaterZoneSetting();
setting.setType(TadoSystemType.HOT_WATER);
setting.setPower(powerOn ? Power.ON : Power.OFF);
return setting;
}
}

View File

@@ -0,0 +1,101 @@
/**
* 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.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*;
import java.io.IOException;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.api.model.TimerTerminationCondition;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
/**
* Builder for creation of overlay termination conditions.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TerminationConditionBuilder {
private TadoZoneHandler zoneHandler;
private OverlayTerminationConditionType terminationType = null;
private Integer timerDurationInSeconds = null;
protected TerminationConditionBuilder(TadoZoneHandler zoneHandler) {
this.zoneHandler = zoneHandler;
}
public static TerminationConditionBuilder of(TadoZoneHandler zoneHandler) {
return new TerminationConditionBuilder(zoneHandler);
}
public TerminationConditionBuilder withTerminationType(OverlayTerminationConditionType terminationType) {
this.terminationType = terminationType;
if (terminationType != OverlayTerminationConditionType.TIMER) {
timerDurationInSeconds = null;
}
return this;
}
public TerminationConditionBuilder withTimerDurationInSeconds(Integer timerDurationInSeconds) {
this.terminationType = OverlayTerminationConditionType.TIMER;
this.timerDurationInSeconds = timerDurationInSeconds;
return this;
}
public OverlayTerminationCondition build(ZoneStateProvider zoneStateProvider) throws IOException, ApiException {
OverlayTerminationCondition terminationCondition = null;
if (terminationType != null) {
if (terminationType != OverlayTerminationConditionType.TIMER || timerDurationInSeconds != null) {
terminationCondition = getTerminationCondition(terminationType, timerDurationInSeconds);
} else {
terminationCondition = getCurrentOrDefaultTimerTermination(zoneStateProvider);
}
} else {
ZoneState zoneState = zoneStateProvider.getZoneState();
if (zoneState.getOverlay() != null) {
terminationCondition = cleanTerminationCondition(zoneState.getOverlay().getTermination());
} else {
// Default zone termination condition
terminationCondition = getDefaultTerminationCondition();
}
}
return terminationCondition;
}
private OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
OverlayTerminationCondition defaultTerminationCondition = zoneHandler.getDefaultTerminationCondition();
return defaultTerminationCondition != null ? defaultTerminationCondition : manualTermination();
}
private TimerTerminationCondition getCurrentOrDefaultTimerTermination(ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
// Timer without duration
int duration = zoneHandler.getFallbackTimerDuration() * 60;
ZoneState zoneState = zoneStateProvider.getZoneState();
// If timer is currently running, use its time
if (zoneState.getOverlay() != null
&& zoneState.getOverlay().getTermination().getType() == OverlayTerminationConditionType.TIMER) {
duration = ((TimerTerminationCondition) zoneState.getOverlay().getTermination()).getDurationInSeconds();
}
return timerTermination(duration);
}
}

View File

@@ -0,0 +1,101 @@
/**
* 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.tado.internal.builder;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
/**
* Base class for zone settings builder.
*
* @author Dennis Frommknecht - Initial contribution
*/
public abstract class ZoneSettingsBuilder {
public static ZoneSettingsBuilder of(TadoZoneHandler zoneHandler) {
switch (zoneHandler.getZoneType()) {
case HEATING:
return new HeatingZoneSettingsBuilder();
case AIR_CONDITIONING:
return new AirConditioningZoneSettingsBuilder();
case HOT_WATER:
return new HotWaterZoneSettingsBuilder();
default:
throw new IllegalArgumentException("Zone type " + zoneHandler.getZoneType() + " unknown");
}
}
protected HvacMode mode = null;
protected Float temperature = null;
protected TemperatureUnit temperatureUnit = TemperatureUnit.CELSIUS;
protected Boolean swing = null;
protected FanSpeed fanSpeed = null;
public ZoneSettingsBuilder withMode(HvacMode mode) {
this.mode = mode;
return this;
}
public ZoneSettingsBuilder withTemperature(Float temperature, TemperatureUnit temperatureUnit) {
this.temperature = temperature;
this.temperatureUnit = temperatureUnit;
return this;
}
public ZoneSettingsBuilder withSwing(boolean swingOn) {
this.swing = swingOn;
return this;
}
public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
this.fanSpeed = fanSpeed;
return this;
}
public abstract GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
throws IOException, ApiException;
protected TemperatureObject truncateTemperature(TemperatureObject temperature) {
if (temperature == null) {
return null;
}
TemperatureObject temperatureObject = new TemperatureObject();
if (temperatureUnit == TemperatureUnit.FAHRENHEIT) {
temperatureObject.setFahrenheit(temperature.getFahrenheit());
} else {
temperatureObject.setCelsius(temperature.getCelsius());
}
return temperatureObject;
}
protected TemperatureObject buildDefaultTemperatureObject(float temperatureCelsius, float temperatureFahrenheit) {
TemperatureObject temperatureObject = new TemperatureObject();
if (temperatureUnit == TemperatureUnit.FAHRENHEIT) {
temperatureObject.setFahrenheit(temperatureFahrenheit);
} else {
temperatureObject.setCelsius(temperatureCelsius);
}
return temperatureObject;
}
}

View File

@@ -0,0 +1,43 @@
/**
* 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.tado.internal.builder;
import java.io.IOException;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
/**
* Wrapper for zone state to support lazy loading.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class ZoneStateProvider {
private TadoZoneHandler zoneHandler;
private ZoneState zoneState;
public ZoneStateProvider(TadoZoneHandler zoneHandler) {
this.zoneHandler = zoneHandler;
}
ZoneState getZoneState() throws IOException, ApiException {
if (this.zoneState == null) {
ZoneState retrievedZoneState = zoneHandler.getZoneState();
// empty zone state behaves like a NULL object
this.zoneState = retrievedZoneState != null ? retrievedZoneState : new ZoneState();
}
return this.zoneState;
}
}

View File

@@ -0,0 +1,23 @@
/**
* 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.tado.internal.config;
/**
* Holder-object for home configuration
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoHomeConfig {
public String username;
public String password;
}

View File

@@ -0,0 +1,23 @@
/**
* 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.tado.internal.config;
/**
* Holder-object for mobile device configuration
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoMobileDeviceConfig {
public int id;
public int refreshInterval;
}

View File

@@ -0,0 +1,25 @@
/**
* 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.tado.internal.config;
/**
* Holder-object for zone configuration
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoZoneConfig {
public long id;
public int refreshInterval;
public int fallbackTimerDuration;
public int hvacChangeDebounce;
}

View File

@@ -0,0 +1,165 @@
/**
* 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.tado.internal.discovery;
import static org.openhab.binding.tado.internal.TadoBindingConstants.*;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.MobileDevice;
import org.openhab.binding.tado.internal.api.model.Zone;
import org.openhab.binding.tado.internal.handler.TadoHomeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery service for zones and mobile devices.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoDiscoveryService extends AbstractDiscoveryService {
private static final int TIMEOUT = 5;
private static final long REFRESH = 600;
private final Logger logger = LoggerFactory.getLogger(TadoDiscoveryService.class);
private ScheduledFuture<?> discoveryFuture;
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_ZONE, THING_TYPE_MOBILE_DEVICE).collect(Collectors.toSet()));
private TadoHomeHandler homeHandler;
public TadoDiscoveryService(TadoHomeHandler tadoHomeHandler) {
super(DISCOVERABLE_THING_TYPES_UIDS, TIMEOUT);
this.homeHandler = tadoHomeHandler;
}
public void activate() {
super.activate(null);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
protected void startScan() {
if (homeHandler.getHomeId() == null) {
return;
}
discoverZones();
discoverMobileDevices();
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Start Tado background discovery");
if (discoveryFuture == null || discoveryFuture.isCancelled()) {
logger.debug("Start Scan");
discoveryFuture = scheduler.scheduleWithFixedDelay(this::startScan, 30, REFRESH, TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stop Tado background discovery");
if (discoveryFuture != null && !discoveryFuture.isCancelled()) {
discoveryFuture.cancel(true);
discoveryFuture = null;
}
}
private void discoverZones() {
Long homeId = homeHandler.getHomeId();
try {
List<Zone> zoneList = homeHandler.getApi().listZones(homeId);
if (zoneList != null) {
for (Zone zone : zoneList) {
notifyZoneDiscovery(homeId, zone);
}
}
} catch (IOException | ApiException e) {
logger.debug("Could not discover tado zones: {}", e.getMessage(), e);
}
}
private void notifyZoneDiscovery(Long homeId, Zone zone) {
Integer zoneId = zone.getId();
ThingUID bridgeUID = this.homeHandler.getThing().getUID();
ThingUID uid = new ThingUID(TadoBindingConstants.THING_TYPE_ZONE, bridgeUID, zoneId.toString());
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_ZONE_ID, zoneId);
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(zone.getName())
.withProperties(properties).build();
thingDiscovered(result);
logger.debug("Discovered zone '{}' with id {} ({})", zone.getName(), zoneId.toString(), uid);
}
private void discoverMobileDevices() {
Long homeId = homeHandler.getHomeId();
try {
List<MobileDevice> mobileDeviceList = homeHandler.getApi().listMobileDevices(homeId);
if (mobileDeviceList != null) {
for (MobileDevice mobileDevice : mobileDeviceList) {
if (mobileDevice.getSettings().isGeoTrackingEnabled()) {
notifyMobileDeviceDiscovery(homeId, mobileDevice);
}
}
}
} catch (IOException | ApiException e) {
logger.debug("Could not discover tado zones: {}", e.getMessage(), e);
}
}
private void notifyMobileDeviceDiscovery(Long homeId, MobileDevice device) {
ThingUID bridgeUID = this.homeHandler.getThing().getUID();
ThingUID uid = new ThingUID(TadoBindingConstants.THING_TYPE_MOBILE_DEVICE, bridgeUID,
device.getId().toString());
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_MOBILE_DEVICE_ID, device.getId());
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(device.getName())
.withProperties(properties).build();
thingDiscovered(result);
logger.debug("Discovered mobile device '{}' with id {} ({})", device.getName(), device.getId().toString(), uid);
}
}

View File

@@ -0,0 +1,53 @@
/**
* 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.tado.internal.handler;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
/**
* Common base class for home-based thing-handler.
*
* @author Dennis Frommknecht - Initial contribution
*/
public abstract class BaseHomeThingHandler extends BaseThingHandler {
public BaseHomeThingHandler(Thing thing) {
super(thing);
}
public Long getHomeId() {
TadoHomeHandler handler = getHomeHandler();
return handler != null ? handler.getHomeId() : new Long(0);
}
protected TadoHomeHandler getHomeHandler() {
Bridge bridge = getBridge();
return bridge != null ? (TadoHomeHandler) bridge.getHandler() : null;
}
protected HomeApi getApi() {
TadoHomeHandler handler = getHomeHandler();
return handler != null ? handler.getApi() : null;
}
protected void onSuccessfulOperation() {
// update without error -> we're back online
if (getThing().getStatus() == ThingStatus.OFFLINE) {
updateStatus(ThingStatus.ONLINE);
}
}
}

View File

@@ -0,0 +1,78 @@
/**
* 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.tado.internal.handler;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.ControlDevice;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoBatteryChecker} checks the battery state of Tado control
* devices.
*
* @author Andrew Fiddian-Green - Initial contribution
*
*/
public class TadoBatteryChecker {
private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class);
private Map<Long, State> zoneList = new HashMap<>();
private Date refreshTime = new Date();
private TadoHomeHandler homeHandler;
public TadoBatteryChecker(TadoHomeHandler homeHandler) {
this.homeHandler = homeHandler;
}
private synchronized void refreshZoneList() {
Date now = new Date();
if (homeHandler != null && (now.after(refreshTime) || zoneList.isEmpty())) {
// be frugal, we only need to refresh the battery state hourly
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.HOUR, 1);
refreshTime = calendar.getTime();
Long homeId = homeHandler.getHomeId();
if (homeId != null) {
logger.debug("Fetching (battery state) zone list for HomeId {}", homeId);
zoneList.clear();
try {
homeHandler.getApi().listZones(homeId).forEach(zone -> {
boolean batteryLow = !zone.getDevices().stream().map(ControlDevice::getBatteryState)
.filter(Objects::nonNull).allMatch(s -> s.equals("NORMAL"));
zoneList.put(Long.valueOf(zone.getId()), OnOffType.from(batteryLow));
});
} catch (IOException | ApiException e) {
logger.debug("Fetch (battery state) zone list exception");
}
}
}
}
public State getBatteryLowAlarm(long zoneId) {
refreshZoneList();
return zoneList.getOrDefault(zoneId, UnDefType.UNDEF);
}
}

View File

@@ -0,0 +1,95 @@
/**
* 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.tado.internal.handler;
import static org.openhab.binding.tado.internal.TadoBindingConstants.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.openhab.binding.tado.internal.discovery.TadoDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
/**
* The {@link TadoHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Dennis Frommknecht - Initial contribution
*/
@Component(configurationPid = "binding.tado", service = ThingHandlerFactory.class)
public class TadoHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(THING_TYPE_HOME, THING_TYPE_ZONE, THING_TYPE_MOBILE_DEVICE)));
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_HOME)) {
TadoHomeHandler tadoHomeHandler = new TadoHomeHandler((Bridge) thing);
registerTadoDiscoveryService(tadoHomeHandler);
return tadoHomeHandler;
} else if (thingTypeUID.equals(THING_TYPE_ZONE)) {
return new TadoZoneHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_MOBILE_DEVICE)) {
return new TadoMobileDeviceHandler(thing);
}
return null;
}
private synchronized void registerTadoDiscoveryService(TadoHomeHandler tadoHomeHandler) {
TadoDiscoveryService discoveryService = new TadoDiscoveryService(tadoHomeHandler);
ServiceRegistration<?> serviceRegistration = bundleContext.registerService(DiscoveryService.class.getName(),
discoveryService, new Hashtable<>());
discoveryService.activate();
this.discoveryServiceRegs.put(tadoHomeHandler.getThing().getUID(), serviceRegistration);
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof TadoHomeHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
TadoDiscoveryService service = (TadoDiscoveryService) bundleContext
.getService(serviceReg.getReference());
serviceReg.unregister();
if (service != null) {
service.deactivate();
}
}
}
}
}

View File

@@ -0,0 +1,137 @@
/**
* 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.tado.internal.handler;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.HomeApiFactory;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import org.openhab.binding.tado.internal.api.model.HomeInfo;
import org.openhab.binding.tado.internal.api.model.User;
import org.openhab.binding.tado.internal.config.TadoHomeConfig;
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.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoHomeHandler} is the bridge of all home-based things.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoHomeHandler extends BaseBridgeHandler {
private Logger logger = LoggerFactory.getLogger(TadoHomeHandler.class);
private TadoHomeConfig configuration;
private HomeApi api;
private Long homeId;
private TadoBatteryChecker batteryChecker;
private ScheduledFuture<?> initializationFuture;
public TadoHomeHandler(Bridge bridge) {
super(bridge);
batteryChecker = new TadoBatteryChecker(this);
}
public TemperatureUnit getTemperatureUnit() {
String temperatureUnitStr = this.thing.getProperties().get(TadoBindingConstants.PROPERTY_HOME_TEMPERATURE_UNIT);
return TemperatureUnit.valueOf(temperatureUnitStr);
}
@Override
public void initialize() {
configuration = getConfigAs(TadoHomeConfig.class);
api = new HomeApiFactory().create(configuration.username, configuration.password);
if (this.initializationFuture == null || this.initializationFuture.isDone()) {
initializationFuture = scheduler.scheduleWithFixedDelay(this::initializeBridgeStatusAndPropertiesIfOffline,
0, 300, TimeUnit.SECONDS);
}
}
private void initializeBridgeStatusAndPropertiesIfOffline() {
Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
return;
}
try {
// Get user info to verify successful authentication and connection to server
User user = api.showUser();
if (user == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Cannot connect to server. Username and/or password might be invalid");
return;
}
if (user.getHomes().isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"User does not have access to any home");
return;
}
homeId = user.getHomes().get(0).getId().longValue();
HomeInfo homeInfo = api.showHome(homeId);
TemperatureUnit temperatureUnit = org.openhab.binding.tado.internal.api.model.TemperatureUnit.FAHRENHEIT == homeInfo
.getTemperatureUnit() ? TemperatureUnit.FAHRENHEIT : TemperatureUnit.CELSIUS;
updateProperty(TadoBindingConstants.PROPERTY_HOME_TEMPERATURE_UNIT, temperatureUnit.name());
} catch (IOException | ApiException e) {
logger.debug("Error accessing tado server: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
return;
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void dispose() {
super.dispose();
if (this.initializationFuture != null || !this.initializationFuture.isDone()) {
this.initializationFuture.cancel(true);
this.initializationFuture = null;
}
}
public HomeApi getApi() {
return api;
}
public Long getHomeId() {
return homeId;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Nothing to do for a bridge
}
public State getBatteryLowAlarm(long zoneId) {
return batteryChecker.getBatteryLowAlarm(zoneId);
}
}

View File

@@ -0,0 +1,149 @@
/**
* 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.tado.internal.handler;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.MobileDevice;
import org.openhab.binding.tado.internal.config.TadoMobileDeviceConfig;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoMobileDeviceHandler} is responsible for handling commands of mobile devices and update their state.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoMobileDeviceHandler extends BaseHomeThingHandler {
private Logger logger = LoggerFactory.getLogger(TadoMobileDeviceHandler.class);
private TadoMobileDeviceConfig configuration;
private ScheduledFuture<?> refreshTimer;
public TadoMobileDeviceHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == RefreshType.REFRESH) {
logger.debug("Refreshing {}", channelUID);
updateState();
} else {
logger.warn("This Thing is read-only and can only handle REFRESH command");
}
}
@Override
public void initialize() {
configuration = getConfigAs(TadoMobileDeviceConfig.class);
if (configuration.refreshInterval <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
+ configuration.id + " of home " + getHomeId() + " must be greater than zero");
return;
}
Bridge bridge = getBridge();
if (bridge != null) {
bridgeStatusChanged(bridge.getStatusInfo());
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
try {
MobileDevice device = getMobileDevice();
updateProperty(TadoBindingConstants.PROPERTY_MOBILE_DEVICE_NAME, device.getName());
if (!device.getSettings().isGeoTrackingEnabled()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Geotracking is disabled on mobile device " + device.getName());
return;
}
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
cancelScheduledStateUpdate();
return;
}
scheduleZoneStateUpdate();
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
cancelScheduledStateUpdate();
}
}
private void updateState() {
try {
MobileDevice device = getMobileDevice();
updateState(TadoBindingConstants.CHANNEL_MOBILE_DEVICE_AT_HOME,
device.getLocation().isAtHome() ? OnOffType.ON : OnOffType.OFF);
} catch (IOException | ApiException e) {
logger.debug("Status update of mobile device with id {} failed: {}", configuration.id, e.getMessage());
}
}
private MobileDevice getMobileDevice() throws IOException, ApiException {
MobileDevice device = null;
try {
device = getApi().listMobileDevices(getHomeId()).stream().filter(m -> m.getId() == configuration.id)
.findFirst().orElse(null);
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
throw e;
}
if (device == null) {
String message = "Mobile device with id " + configuration.id + " unknown or does not belong to home "
+ getHomeId();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
throw new IOException(message);
}
onSuccessfulOperation();
return device;
}
private void scheduleZoneStateUpdate() {
if (refreshTimer == null || refreshTimer.isCancelled()) {
refreshTimer = scheduler.scheduleWithFixedDelay(this::updateState, 5, configuration.refreshInterval,
TimeUnit.SECONDS);
}
}
private void cancelScheduledStateUpdate() {
if (refreshTimer != null) {
refreshTimer.cancel(false);
}
}
}

View File

@@ -0,0 +1,310 @@
/**
* 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.tado.internal.handler;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.terminationConditionTemplateToTerminationCondition;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Temperature;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.TadoBindingConstants.ZoneType;
import org.openhab.binding.tado.internal.TadoHvacChange;
import org.openhab.binding.tado.internal.adapter.TadoZoneStateAdapter;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.Overlay;
import org.openhab.binding.tado.internal.api.model.OverlayTemplate;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.Zone;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.binding.tado.internal.config.TadoZoneConfig;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Bridge;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoZoneHandler} is responsible for handling commands of zones and update their state.
*
* @author Dennis Frommknecht - Initial contribution
* @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
*
*/
public class TadoZoneHandler extends BaseHomeThingHandler {
private Logger logger = LoggerFactory.getLogger(TadoZoneHandler.class);
private TadoZoneConfig configuration;
private ScheduledFuture<?> refreshTimer;
private ScheduledFuture<?> scheduledHvacChange;
private GenericZoneCapabilities capabilities;
TadoHvacChange pendingHvacChange;
public TadoZoneHandler(Thing thing) {
super(thing);
}
public long getZoneId() {
return this.configuration.id;
}
public int getFallbackTimerDuration() {
return this.configuration.fallbackTimerDuration;
}
public ZoneType getZoneType() {
String zoneTypeStr = this.thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE);
return ZoneType.valueOf(zoneTypeStr);
}
public OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
OverlayTemplate overlayTemplate = getApi().showZoneDefaultOverlay(getHomeId(), getZoneId());
return terminationConditionTemplateToTerminationCondition(overlayTemplate.getTerminationCondition());
}
public ZoneState getZoneState() throws IOException, ApiException {
HomeApi api = getApi();
return api != null ? api.showZoneState(getHomeId(), getZoneId()) : null;
}
public GenericZoneCapabilities getZoneCapabilities() {
return this.capabilities;
}
public TemperatureUnit getTemperatureUnit() {
return getHomeHandler().getTemperatureUnit();
}
public Overlay setOverlay(Overlay overlay) throws IOException, ApiException {
logger.debug("Setting overlay of home {} and zone {}", getHomeId(), getZoneId());
return getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay);
}
public void removeOverlay() throws IOException, ApiException {
logger.debug("Removing overlay of home {} and zone {}", getHomeId(), getZoneId());
getApi().deleteZoneOverlay(getHomeId(), getZoneId());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String id = channelUID.getId();
if (command == RefreshType.REFRESH) {
updateZoneState(false);
return;
}
switch (id) {
case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE:
pendingHvacChange.withHvacMode(((StringType) command).toFullString());
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE:
QuantityType<Temperature> state = (QuantityType<Temperature>) command;
QuantityType<Temperature> stateInTargetUnit = getTemperatureUnit() == TemperatureUnit.FAHRENHEIT
? state.toUnit(ImperialUnits.FAHRENHEIT)
: state.toUnit(SIUnits.CELSIUS);
if (stateInTargetUnit != null) {
pendingHvacChange.withTemperature(stateInTargetUnit.floatValue());
scheduleHvacChange();
}
break;
case TadoBindingConstants.CHANNEL_ZONE_SWING:
pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON);
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED:
pendingHvacChange.withFanSpeed(((StringType) command).toFullString());
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE:
String operationMode = ((StringType) command).toFullString();
pendingHvacChange.withOperationMode(OperationMode.valueOf(operationMode));
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION:
pendingHvacChange.activeFor(((DecimalType) command).intValue());
scheduleHvacChange();
break;
}
}
@Override
public void initialize() {
configuration = getConfigAs(TadoZoneConfig.class);
if (configuration.refreshInterval <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
+ getZoneId() + " of home " + getHomeId() + " must be greater than zero");
return;
} else if (configuration.fallbackTimerDuration <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Fallback timer duration of zone "
+ getZoneId() + " of home " + getHomeId() + " must be greater than zero");
return;
} else if (configuration.hvacChangeDebounce <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "HVAC change debounce of zone "
+ getZoneId() + " of home " + getHomeId() + " must be greater than zero");
return;
}
Bridge bridge = getBridge();
if (bridge != null) {
bridgeStatusChanged(bridge.getStatusInfo());
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
try {
Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId());
GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId());
if (zoneDetails == null || capabilities == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Can not access zone " + getZoneId() + " of home " + getHomeId());
return;
}
updateProperty(TadoBindingConstants.PROPERTY_ZONE_NAME, zoneDetails.getName());
updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
this.capabilities = capabilities;
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
cancelScheduledZoneStateUpdate();
return;
}
scheduleZoneStateUpdate();
pendingHvacChange = new TadoHvacChange(getThing());
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
cancelScheduledZoneStateUpdate();
}
}
private void updateZoneState(boolean forceUpdate) {
// No update during HVAC change debounce
if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) {
return;
}
try {
ZoneState zoneState = getZoneState();
if (zoneState == null) {
return;
}
logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId());
TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower());
updateState(TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE, state.getOperationMode());
updateState(TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE, state.getMode());
updateState(TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE, state.getTargetTemperature());
updateState(TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED, state.getFanSpeed());
updateState(TadoBindingConstants.CHANNEL_ZONE_SWING, state.getSwing());
updateState(TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION, state.getRemainingTimerDuration());
updateState(TadoBindingConstants.CHANNEL_ZONE_OVERLAY_EXPIRY, state.getOverlayExpiration());
updateState(TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED, state.getOpenWindowDetected());
onSuccessfulOperation();
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
}
TadoHomeHandler home = getHomeHandler();
if (home != null) {
updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM, home.getBatteryLowAlarm(getZoneId()));
}
}
private void scheduleZoneStateUpdate() {
if (refreshTimer == null || refreshTimer.isCancelled()) {
refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
updateZoneState(false);
}
}, 5, configuration.refreshInterval, TimeUnit.SECONDS);
}
}
private void cancelScheduledZoneStateUpdate() {
if (refreshTimer != null) {
refreshTimer.cancel(false);
}
}
private void scheduleHvacChange() {
if (scheduledHvacChange != null) {
scheduledHvacChange.cancel(false);
}
scheduledHvacChange = scheduler.schedule(() -> {
try {
TadoHvacChange change = this.pendingHvacChange;
this.pendingHvacChange = new TadoHvacChange(getThing());
change.apply();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (ApiException e) {
logger.warn("Could not apply HVAC change on home {} and zone {}: {}", getHomeId(), getZoneId(),
e.getMessage(), e);
} finally {
updateZoneState(true);
}
}, configuration.hvacChangeDebounce, TimeUnit.SECONDS);
}
private void updateStateIfNotNull(String channelID, State state) {
if (state != null) {
updateState(channelID, state);
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="tado" 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>tado° Binding</name>
<description>Binding for tado° devices</description>
<author>Dennis Frommknecht</author>
</binding:binding>

View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tado"
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">
<!-- my.tado API Gateway -->
<bridge-type id="home">
<label>Tado Home</label>
<description>The user's tado home</description>
<config-description>
<parameter name="username" type="text" required="true">
<label>User Name</label>
<description>User name of tado login used for API access</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>Password of tado login used for API access</description>
<context>password</context>
</parameter>
</config-description>
</bridge-type>
<thing-type id="zone">
<supported-bridge-type-refs>
<bridge-type-ref id="home"/>
</supported-bridge-type-refs>
<label>Zone</label>
<description>A zone of a home</description>
<channels>
<channel typeId="currentTemperature" id="currentTemperature"></channel>
<channel typeId="humidity" id="humidity"></channel>
<channel typeId="heatingPower" id="heatingPower"></channel>
<channel typeId="hvacMode" id="hvacMode"></channel>
<channel typeId="targetTemperature" id="targetTemperature"></channel>
<channel typeId="fanspeed" id="fanspeed"></channel>
<channel typeId="swing" id="swing"></channel>
<channel typeId="overlayExpiry" id="overlayExpiry"></channel>
<channel typeId="timerDuration" id="timerDuration"></channel>
<channel typeId="operationMode" id="operationMode"></channel>
<channel typeId="system.low-battery" id="batteryLowAlarm">
<label>Battery Low Alarm</label>
<description>ON if one or more devices in the zone have a low battery</description>
</channel>
<channel typeId="acPower" id="acPower"></channel>
<channel typeId="openWindowDetected" id="openWindowDetected"></channel>
</channels>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Zone Id</label>
<description>Id of the zone</description>
</parameter>
<parameter name="refreshInterval" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval of home data</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="fallbackTimerDuration" type="integer">
<label>Fallback Timer Duration</label>
<description>Timer duration used if no other duration can be determined</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="hvacChangeDebounce" type="integer">
<label>HVAC Change Debounce Delay</label>
<description>Duration in seconds to combine multiple HVAC changes into one.</description>
<default>5</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="mobiledevice">
<supported-bridge-type-refs>
<bridge-type-ref id="home"/>
</supported-bridge-type-refs>
<label>Mobile Device</label>
<description>Mobile device of a home</description>
<channels>
<channel typeId="atHome" id="atHome"></channel>
</channels>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Mobile Device Id</label>
<description>Id of the mobile device</description>
</parameter>
<parameter name="refreshInterval" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval of location state</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="currentTemperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"></state>
</channel-type>
<channel-type id="humidity">
<item-type>Number</item-type>
<label>Humidity</label>
<description>Current humidity in %</description>
<category>Humidity</category>
<state readOnly="true" pattern="%.1f %%"></state>
</channel-type>
<channel-type id="heatingPower">
<item-type>Number</item-type>
<label>Heating Power</label>
<description>Current heating power</description>
<state readOnly="true" pattern="%.0f %%"></state>
</channel-type>
<channel-type id="hvacMode">
<item-type>String</item-type>
<label>HVAC Mode</label>
<description>Mode of the device (OFF, HEAT, COOL, DRY, FAN, AUTO - if supported)</description>
<state readOnly="false">
<options>
<option value="OFF">Off</option>
<option value="HEAT">Heat</option>
<option value="COOL">Cool</option>
<option value="DRY">Dry</option>
<option value="FAN">Fan</option>
<option value="AUTO">Auto</option>
</options>
</state>
</channel-type>
<channel-type id="targetTemperature">
<item-type>Number:Temperature</item-type>
<label>Target Temperature</label>
<description>Thermostat temperature setpoint</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%" readOnly="false"></state>
</channel-type>
<channel-type id="fanspeed">
<item-type>String</item-type>
<label>Fan Speed</label>
<description>AC fan speed (only if supported by AC)</description>
<state readOnly="false">
<options>
<option value="LOW">Low</option>
<option value="MIDDLE">Middle</option>
<option value="HIGH">High</option>
<option value="AUTO">Auto</option>
</options>
</state>
</channel-type>
<channel-type id="swing">
<item-type>Switch</item-type>
<label>Swing</label>
<description>State of AC swing (only if supported by AC)</description>
</channel-type>
<channel-type id="operationMode">
<item-type>String</item-type>
<label>Zone Operation Mode</label>
<description>Active operation mode (schedule, manual, timer or until next change)</description>
<state readOnly="false">
<options>
<option value="SCHEDULE">Schedule</option>
<option value="MANUAL">Manual</option>
<option value="UNTIL_CHANGE">Until change</option>
<option value="TIMER">Timer</option>
</options>
</state>
</channel-type>
<channel-type id="timerDuration">
<item-type>Number</item-type>
<label>Timer Duration</label>
<description>Total duration of a timer</description>
<state min="0" step="1" pattern="%d min" readOnly="false"></state>
</channel-type>
<channel-type id="overlayExpiry">
<item-type>DateTime</item-type>
<label>Overlay End Time</label>
<description>Time until when the overlay is active. Null if no overlay is set or overlay type is manual.</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="atHome">
<item-type>Switch</item-type>
<label>At Home</label>
<description>ON if at home, OFF if away</description>
</channel-type>
<channel-type id="acPower">
<item-type>Switch</item-type>
<label>AirCon Power State</label>
<description>Indicates if the air-conditioning is Off or On</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="openWindowDetected">
<item-type>Switch</item-type>
<label>Open Window Detected</label>
<description>Indicates if an open window has been detected</description>
<category>window</category>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>