added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.tado/.classpath
Normal file
32
bundles/org.openhab.binding.tado/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="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>
|
||||
23
bundles/org.openhab.binding.tado/.project
Normal file
23
bundles/org.openhab.binding.tado/.project
Normal 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>
|
||||
13
bundles/org.openhab.binding.tado/NOTICE
Normal file
13
bundles/org.openhab.binding.tado/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
231
bundles/org.openhab.binding.tado/README.md
Normal file
231
bundles/org.openhab.binding.tado/README.md
Normal 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
|
||||
```
|
||||
26
bundles/org.openhab.binding.tado/pom.xml
Normal file
26
bundles/org.openhab.binding.tado/pom.xml
Normal 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>
|
||||
101
bundles/org.openhab.binding.tado/src/main/api/pom.xml
Normal file
101
bundles/org.openhab.binding.tado/src/main/api/pom.xml
Normal 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>
|
||||
|
||||
1006
bundles/org.openhab.binding.tado/src/main/api/tado-api.yaml
Normal file
1006
bundles/org.openhab.binding.tado/src/main/api/tado-api.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user