mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-31 13:34:22 +02:00
[tado] OAuth RFC-8628 authentication (deadline 2025-03-15) (#18354)
* add oauth2 authentication Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
@@ -1,265 +1,272 @@
|
||||
# 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, and setting the HOME/AWAY status of your home.
|
||||
|
||||
**WARNING**: This binding doesn't work together with new tado X radiator thermostats, as they are using a different API (see [here](https://app.swaggerhub.com/apis/JPlenert/TadoX/0.8.0) ).
|
||||
|
||||
## `home` Thing (the Bridge)
|
||||
|
||||
The `home` thing serves as bridge to the tado° cloud services.
|
||||
The binding will automatically discover this thing and place it in the Inbox.
|
||||
It has to be manually configured with its `username` and `password` before it will actually go onlime.
|
||||
|
||||
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`
|
||||
|
||||
```java
|
||||
Bridge tado:home:demo [ username="mail@example.com", password="secret" ]
|
||||
```
|
||||
|
||||
Once the `home` thing is online, the binding will discover all its respective zones and mobile devices, and place them in the Inbox.
|
||||
|
||||
### Channels
|
||||
|
||||
Name | Type | Description | Read/Write
|
||||
-|-|-|-
|
||||
`homePresence` | String | Current presence value of the tado home. `HOME` and `AWAY` can be set | RW
|
||||
|
||||
## `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`
|
||||
|
||||
```java
|
||||
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:Dimensionless | Current relative inside humidity in percent | R | `HEATING`, `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`
|
||||
`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`
|
||||
`overlayExpiry` | DateTime | End date and time of a timer | R | `HEATING`, `AC`, `DHW`
|
||||
`timerDuration` | Number | Timer duration in minutes | RW | `HEATING`, `AC`, `DHW`
|
||||
`heatingPower` | Number:Dimensionless | Amount of heating power currently present | R | `HEATING`
|
||||
`acPower` | Switch | Indicates if the Air-Conditioning is Off or On | R | `AC`
|
||||
`fanspeed`<sup>1)</sup> | String | Fan speed, one of `AUTO`, `LOW`, `MIDDLE`, `HIGH` | RW | `AC`
|
||||
`fanLevel`<sup>1)</sup> | String | Fan speed, one of <sup>3)</sup> `AUTO`, `SILENT`, `LEVEL1`, `LEVEL2`, `LEVEL3`, `LEVEL4`, `LEVEL5` | RW | `AC`
|
||||
`swing`<sup>2)</sup> | Switch | Swing on/off | RW | `AC`
|
||||
`verticalSwing`<sup>2)</sup> | String | Vertical swing state, one of <sup>3)</sup> `OFF`, `ON`, `UP`, `MID_UP`, `MID`, `MID_DOWN`, `DOWN`, `AUTO` | RW | `AC`
|
||||
`horizontalSwing`<sup>2)</sup> | String | Horizontal swing state, one of <sup>3)</sup> `OFF`, `ON`, `LEFT`, `MID_LEFT`, `MID`, `MID_RIGHT`, `RIGHT`, `AUTO` | RW | `AC`
|
||||
`batteryLowAlarm` | Switch | A control device in the Zone has a low battery | R | Any Zone
|
||||
`openWindowDetected` | Switch | An open window has been detected in the Zone | R | `HEATING`, `AC`
|
||||
`openWindowRemainingTime` | Number:Time | The remaining Open Window heating/cooling Override time in the Zone | R | `HEATING`, `AC`
|
||||
`light` | Switch | State (`ON`, `OFF`) of the control panel light | RW | `AC`
|
||||
|
||||
You will see some of the above mentioned Channels only if your tado° device supports the respective function.
|
||||
|
||||
The `RW` items are used to either override the schedule or to return to it (if `hvacMode` is set to `SCHEDULE`).
|
||||
|
||||
<sup>1)</sup> Simpler A/C units have fan speed settings in the range [`LOW`, `MIDDLE`, `HIGH`].
|
||||
However, more sophisticated devices have settings in the range [`SILENT`, `LEVEL1`, `LEVEL2`, `LEVEL3`, `LEVEL4`].
|
||||
So you need to choose the respective channel type name that matches the features of your device.
|
||||
|
||||
<sup>2)</sup> Simpler A/C units have a single switch type swing function that is either `ON` or `OFF`.
|
||||
However, more sophisticated devices may have either a vertical swing, a horizontal swing, or both, which could also have more complex settings.
|
||||
For example the horizontal swing function could simply be `ON` or it could have more complex settings in the range [`LEFT`, `MID_LEFT`, `MID`, `MID_RIGHT`, `RIGHT`].
|
||||
So you need to choose the respective channel type name that matches the features of your device.
|
||||
|
||||
<sup>3)</sup> The _'one of'_ list contains all possible state values supported within the tado° binding.
|
||||
However, in reality different A/C units might only support a **_subset_** of those values.
|
||||
And indeed the subset of supported values might depend on the current state of the `acPower` and `hvacMode` channels.
|
||||
In that case, if you send a channel command value to an A/C unit which does not (currently) support that particular state value, then openHAB will report a '422' run-time error in the log.
|
||||
|
||||
### 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`:
|
||||
|
||||
```java
|
||||
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
|
||||
|
||||
```java
|
||||
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
|
||||
|
||||
```java
|
||||
Switch TADO_PRESENCE_home "Tado Presence: [MAP(presence.map):%s]" { channel="tado:home:demo:homePresence" }
|
||||
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
|
||||
|
||||
```perl
|
||||
sitemap tado label="Tado"
|
||||
{
|
||||
Frame label="Status" {
|
||||
Switch item=TADO_PRESENCE_home icon="presence"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
```text
|
||||
ON=at home
|
||||
OFF=away
|
||||
NULL=lost
|
||||
```
|
||||
# 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, and setting the HOME/AWAY status of your home.
|
||||
|
||||
**WARNING**: This binding doesn't work together with new tado X radiator thermostats, as they are using a different API (see [here](https://app.swaggerhub.com/apis/JPlenert/TadoX/0.8.0) ).
|
||||
|
||||
## `home` Thing (the Bridge)
|
||||
|
||||
The `home` thing serves as bridge to the tado° cloud services.
|
||||
The binding will automatically discover this thing and place it in the Inbox.
|
||||
It must be authenticated before it will actually go online.
|
||||
There are two ways to authenticate it as follows:
|
||||
|
||||
1. Online via the OAuth Device Code Grant Flow (RFC-8628) authentication process through the link provided at `http://[openhab-ip-address]:8080/tado`.
|
||||
1. Enter `username` and `password` credentials in the thing configuration parameters as shown in the table below.
|
||||
|
||||
Note: after March 15th, 2025 online authentication is the tado° preferred method.
|
||||
It is possible that the `username` and `password` method may cease to work some time after this date.
|
||||
|
||||
| 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`
|
||||
|
||||
```java
|
||||
Bridge tado:home:demo [ username="mail@example.com", password="secret" ]
|
||||
```
|
||||
|
||||
Once the `home` thing is online, the binding will discover all its respective zones and mobile devices, and place them in the Inbox.
|
||||
|
||||
### Channels
|
||||
|
||||
| Name | Type | Description | Read/Write |
|
||||
|----------------|--------|-----------------------------------------------------------------------|------------|
|
||||
| `homePresence` | String | Current presence value of the tado home. `HOME` and `AWAY` can be set | RW |
|
||||
|
||||
## `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`
|
||||
|
||||
```java
|
||||
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:Dimensionless | Current relative inside humidity in percent | R | `HEATING`, `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` |
|
||||
| `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` |
|
||||
| `overlayExpiry` | DateTime | End date and time of a timer | R | `HEATING`, `AC`, `DHW` |
|
||||
| `timerDuration` | Number | Timer duration in minutes | RW | `HEATING`, `AC`, `DHW` |
|
||||
| `heatingPower` | Number:Dimensionless | Amount of heating power currently present | R | `HEATING` |
|
||||
| `acPower` | Switch | Indicates if the Air-Conditioning is Off or On | R | `AC` |
|
||||
| `fanspeed`<sup>1)</sup> | String | Fan speed, one of `AUTO`, `LOW`, `MIDDLE`, `HIGH` | RW | `AC` |
|
||||
| `fanLevel`<sup>1)</sup> | String | Fan speed, one of <sup>3)</sup> `AUTO`, `SILENT`, `LEVEL1`, `LEVEL2`, `LEVEL3`, `LEVEL4`, `LEVEL5` | RW | `AC` |
|
||||
| `swing`<sup>2)</sup> | Switch | Swing on/off | RW | `AC` |
|
||||
| `verticalSwing`<sup>2)</sup> | String | Vertical swing state, one of <sup>3)</sup> `OFF`, `ON`, `UP`, `MID_UP`, `MID`, `MID_DOWN`, `DOWN`, `AUTO` | RW | `AC` |
|
||||
| `horizontalSwing`<sup>2)</sup> | String | Horizontal swing state, one of <sup>3)</sup> `OFF`, `ON`, `LEFT`, `MID_LEFT`, `MID`, `MID_RIGHT`, `RIGHT`, `AUTO` | RW | `AC` |
|
||||
| `batteryLowAlarm` | Switch | A control device in the Zone has a low battery | R | Any Zone |
|
||||
| `openWindowDetected` | Switch | An open window has been detected in the Zone | R | `HEATING`, `AC` |
|
||||
| `openWindowRemainingTime` | Number:Time | The remaining Open Window heating/cooling Override time in the Zone | R | `HEATING`, `AC` |
|
||||
| `light` | Switch | State (`ON`, `OFF`) of the control panel light | RW | `AC` |
|
||||
|
||||
You will see some of the above mentioned Channels only if your tado° device supports the respective function.
|
||||
|
||||
The `RW` items are used to either override the schedule or to return to it (if `hvacMode` is set to `SCHEDULE`).
|
||||
|
||||
<sup>1)</sup> Simpler A/C units have fan speed settings in the range [`LOW`, `MIDDLE`, `HIGH`].
|
||||
However, more sophisticated devices have settings in the range [`SILENT`, `LEVEL1`, `LEVEL2`, `LEVEL3`, `LEVEL4`].
|
||||
So you need to choose the respective channel type name that matches the features of your device.
|
||||
|
||||
<sup>2)</sup> Simpler A/C units have a single switch type swing function that is either `ON` or `OFF`.
|
||||
However, more sophisticated devices may have either a vertical swing, a horizontal swing, or both, which could also have more complex settings.
|
||||
For example the horizontal swing function could simply be `ON` or it could have more complex settings in the range [`LEFT`, `MID_LEFT`, `MID`, `MID_RIGHT`, `RIGHT`].
|
||||
So you need to choose the respective channel type name that matches the features of your device.
|
||||
|
||||
<sup>3)</sup> The _'one of'_ list contains all possible state values supported within the tado° binding.
|
||||
However, in reality different A/C units might only support a **_subset_** of those values.
|
||||
And indeed the subset of supported values might depend on the current state of the `acPower` and `hvacMode` channels.
|
||||
In that case, if you send a channel command value to an A/C unit which does not (currently) support that particular state value, then openHAB will report a '422' run-time error in the log.
|
||||
|
||||
### 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`:
|
||||
|
||||
```java
|
||||
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` Example
|
||||
|
||||
```java
|
||||
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` Example
|
||||
|
||||
```java
|
||||
Switch TADO_PRESENCE_home "Tado Presence: [MAP(presence.map):%s]" { channel="tado:home:demo:homePresence" }
|
||||
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` Example
|
||||
|
||||
```perl
|
||||
sitemap tado label="Tado"
|
||||
{
|
||||
Frame label="Status" {
|
||||
Switch item=TADO_PRESENCE_home icon="presence"
|
||||
}
|
||||
|
||||
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` Example
|
||||
|
||||
```text
|
||||
ON=at home
|
||||
OFF=away
|
||||
NULL=lost
|
||||
```
|
||||
|
||||
+9
@@ -13,10 +13,12 @@
|
||||
package org.openhab.binding.tado.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tado.internal.auth.OAuthorizerV2;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.GsonBuilderFactory;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.auth.Authorizer;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.auth.OAuthAuthorizer;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.client.HomeApi;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
@@ -24,6 +26,7 @@ import com.google.gson.Gson;
|
||||
* Factory to create and configure {@link HomeApi} instances.
|
||||
*
|
||||
* @author Dennis Frommknecht - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Use OAuthAuthorizerV2
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomeApiFactory {
|
||||
@@ -37,4 +40,10 @@ public class HomeApiFactory {
|
||||
.clientSecret(OAUTH_CLIENT_SECRET).scopes(OAUTH_SCOPE);
|
||||
return new HomeApi(gson, authorizer);
|
||||
}
|
||||
|
||||
public HomeApi create(OAuthClientService oAuthClientService) {
|
||||
Gson gson = GsonBuilderFactory.defaultGsonBuilder().create();
|
||||
Authorizer authorizer = new OAuthorizerV2(oAuthClientService);
|
||||
return new HomeApi(gson, authorizer);
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.auth;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.auth.Authorizer;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a new {@link Authorizer} that is mandated by Tado after March 15 2025.
|
||||
*
|
||||
* @see <a href="https://support.tado.com/en/articles/8565472-how-do-i-authenticate-to-access-the-rest-api">Tado Support
|
||||
* Article</a>
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8628">RFC-8628</a>
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
public class OAuthorizerV2 implements Authorizer {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OAuthorizerV2.class);
|
||||
|
||||
private final OAuthClientService oAuthService;
|
||||
|
||||
public OAuthorizerV2(OAuthClientService oAuthService) {
|
||||
this.oAuthService = oAuthService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAuthorization(Request request) {
|
||||
try {
|
||||
AccessTokenResponse token = oAuthService.getAccessTokenResponse();
|
||||
if (token != null) {
|
||||
request.header(HttpHeader.AUTHORIZATION,
|
||||
String.format("%s %s", token.getTokenType(), token.getAccessToken()));
|
||||
return;
|
||||
}
|
||||
} catch (OAuthException | IOException | OAuthResponseException e) {
|
||||
logger.debug("addAuthorization() => getAccessTokenResponse() error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -13,6 +13,7 @@
|
||||
package org.openhab.binding.tado.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Holder-object for home configuration
|
||||
@@ -21,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TadoHomeConfig {
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
public @Nullable String username;
|
||||
public @Nullable String password;
|
||||
public @Nullable Boolean useRfc8628;
|
||||
}
|
||||
|
||||
+124
-2
@@ -14,14 +14,25 @@ package org.openhab.binding.tado.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tado.internal.TadoBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tado.internal.discovery.TadoDiscoveryService;
|
||||
import org.openhab.binding.tado.internal.servlet.TadoAuthenticationServlet;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.DeviceCodeResponseDTO;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
@@ -34,6 +45,10 @@ import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link TadoHandlerFactory} is responsible for creating things and thing
|
||||
@@ -48,13 +63,30 @@ public class TadoHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_HOME, THING_TYPE_ZONE,
|
||||
THING_TYPE_MOBILE_DEVICE);
|
||||
|
||||
// tado specific RFC-8628 oAuth authentication parameters
|
||||
private static final String OAUTH_DEVICE_URL = "https://login.tado.com/oauth2/device_authorize";
|
||||
private static final String OAUTH_TOKEN_URL = "https://login.tado.com/oauth2/token";
|
||||
private static final String OAUTH_CLIENT_ID = "1bb50063-6b0c-4d11-bd99-387f4a91cc46";
|
||||
private static final String OAUTH_SCOPE = "offline_access";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TadoHandlerFactory.class);
|
||||
private final Set<TadoHomeHandler> oAuthClientServiceSubscribers = new HashSet<>();
|
||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
private final TadoStateDescriptionProvider stateDescriptionProvider;
|
||||
private final HttpService httpService;
|
||||
private final OAuthFactory oAuthFactory;
|
||||
private final TadoAuthenticationServlet httpServlet;
|
||||
|
||||
private @Nullable OAuthClientService oAuthClientService;
|
||||
|
||||
@Activate
|
||||
public TadoHandlerFactory(final @Reference TadoStateDescriptionProvider stateDescriptionProvider) {
|
||||
public TadoHandlerFactory(@Reference TadoStateDescriptionProvider stateDescriptionProvider,
|
||||
@Reference HttpService httpService, @Reference OAuthFactory oAuthFactory) {
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
this.httpService = httpService;
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.httpServlet = new TadoAuthenticationServlet(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,7 +99,7 @@ public class TadoHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_HOME)) {
|
||||
TadoHomeHandler tadoHomeHandler = new TadoHomeHandler((Bridge) thing);
|
||||
TadoHomeHandler tadoHomeHandler = new TadoHomeHandler((Bridge) thing, this, oAuthFactory);
|
||||
registerTadoDiscoveryService(tadoHomeHandler);
|
||||
return tadoHomeHandler;
|
||||
} else if (thingTypeUID.equals(THING_TYPE_ZONE)) {
|
||||
@@ -101,4 +133,94 @@ public class TadoHandlerFactory extends BaseThingHandlerFactory {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link TadoHomeHandler} calls this to subscribe to an {@link OAuthClientService}.
|
||||
* Note: that TadoHomeHandlers which do not require a OAuthClientService do not call this.
|
||||
* Retrieves the pre-existing {@link OAuthClientService} if present, or creates a new one.
|
||||
* If necessary also registers the {@link TadoAuthenticationServlet}.
|
||||
*
|
||||
* @param tadoHomeHandler
|
||||
* @return an {@link OAuthClientService}
|
||||
*/
|
||||
public OAuthClientService subscribeOAuthClientService(TadoHomeHandler tadoHomeHandler) {
|
||||
if (oAuthClientServiceSubscribers.isEmpty()) {
|
||||
try {
|
||||
httpService.registerServlet(TadoAuthenticationServlet.PATH, httpServlet, null, null);
|
||||
} catch (ServletException | NamespaceException e) {
|
||||
logger.debug("subscribeOAuthClientService() error {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
oAuthClientServiceSubscribers.add(tadoHomeHandler);
|
||||
|
||||
OAuthClientService oAuthClientService = this.oAuthClientService;
|
||||
if (oAuthClientService == null) {
|
||||
oAuthClientService = oAuthFactory.getOAuthClientService(THING_TYPE_HOME.toString());
|
||||
this.oAuthClientService = oAuthClientService;
|
||||
}
|
||||
|
||||
if (oAuthClientService == null) {
|
||||
oAuthClientService = oAuthFactory.createOAuthClientService(THING_TYPE_HOME.toString(), //
|
||||
OAUTH_TOKEN_URL, OAUTH_DEVICE_URL, OAUTH_CLIENT_ID, null, OAUTH_SCOPE, false);
|
||||
this.oAuthClientService = oAuthClientService;
|
||||
}
|
||||
|
||||
return oAuthClientService;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link TadoHomeHandler} calls this to unsubscribe from the {@link OAuthClientService}.
|
||||
* If it is the last one then it clears the {@link OAuthClientService} and unregisters the
|
||||
* {@link TadoAuthenticationServlet}
|
||||
*
|
||||
* @param tadoHomeHandler
|
||||
*/
|
||||
public void unsubscribeOAuthClientService(TadoHomeHandler tadoHomeHandler) {
|
||||
if (oAuthClientServiceSubscribers.remove(tadoHomeHandler) && oAuthClientServiceSubscribers.isEmpty()) {
|
||||
httpService.unregister(TadoAuthenticationServlet.PATH);
|
||||
oAuthFactory.ungetOAuthService(THING_TYPE_HOME.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a nullable {@link AccessTokenResponse} if the OAuthClientService exists.
|
||||
*
|
||||
* @return a nullable {@link AccessTokenResponse}.
|
||||
* @throws OAuthException on any error
|
||||
*/
|
||||
public @Nullable AccessTokenResponse getAccessTokenResponse() throws OAuthException {
|
||||
OAuthClientService oAuthClientService = this.oAuthClientService;
|
||||
if (oAuthClientService == null) {
|
||||
throw new OAuthException("Missing OAuthClientService");
|
||||
}
|
||||
try {
|
||||
return oAuthClientService.getAccessTokenResponse();
|
||||
} catch (OAuthException | IOException | OAuthResponseException e) {
|
||||
logger.debug("getAccessTokenResponse() error {}", e.getMessage(), e);
|
||||
throw new OAuthException("OAuthClientService error" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a non null DeviceCodeResponse from the OAuthClientService if it exists.
|
||||
*
|
||||
* @return a {@link DeviceCodeResponseDTO}
|
||||
* @throws OAuthException if it cannot return a non null result
|
||||
*/
|
||||
public DeviceCodeResponseDTO getDeviceCodeResponse() throws OAuthException {
|
||||
OAuthClientService oAuthClientService = this.oAuthClientService;
|
||||
if (oAuthClientService == null) {
|
||||
throw new OAuthException("Missing OAuthClientService");
|
||||
}
|
||||
DeviceCodeResponseDTO result = oAuthClientService.getDeviceCodeResponse();
|
||||
if (result == null) {
|
||||
throw new OAuthException("Expecting non null DeviceCodeResponse");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean hasOAuthClientService() {
|
||||
return oAuthClientService != null;
|
||||
}
|
||||
}
|
||||
|
||||
+74
-25
@@ -12,17 +12,21 @@
|
||||
*/
|
||||
package org.openhab.binding.tado.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tado.internal.TadoBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tado.internal.TadoBindingConstants;
|
||||
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
|
||||
import org.openhab.binding.tado.internal.api.HomeApiFactory;
|
||||
import org.openhab.binding.tado.internal.config.TadoHomeConfig;
|
||||
import org.openhab.binding.tado.internal.servlet.TadoAuthenticationServlet;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.ApiException;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.client.HomeApi;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.model.HomeInfo;
|
||||
@@ -31,6 +35,10 @@ import org.openhab.binding.tado.swagger.codegen.api.model.HomeState;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.model.PresenceState;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.model.User;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.model.UserHomes;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -50,33 +58,68 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Dennis Frommknecht - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
public class TadoHomeHandler extends BaseBridgeHandler implements AccessTokenRefreshListener {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(TadoHomeHandler.class);
|
||||
// thing status description i18n text pointers
|
||||
private static final String CONF_ERROR_NO_HOME = "@text/tado.home.status.nohome";
|
||||
private static final String CONF_ERROR_NO_HOME_ID = "@text/tado.home.status.nohomeid";
|
||||
private static final String CONF_PENDING_USER_CREDS = "@text/tado.home.status.username";
|
||||
private static final String CONF_PENDING_OAUTH_CREDS = "@text/tado.home.status.oauth [\"http(s)://<YOUROPENHAB>:<YOURPORT>%s\"]";
|
||||
|
||||
private TadoHomeConfig configuration;
|
||||
private final HomeApi api;
|
||||
// tado specific RFC-8628 oAuth authentication parameters
|
||||
private static final ZonedDateTime OAUTH_MANDATORY_FROM_DATE = ZonedDateTime.parse("2025-03-15T00:00:00Z");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TadoHomeHandler.class);
|
||||
|
||||
private final TadoBatteryChecker batteryChecker;
|
||||
private final TadoHandlerFactory tadoHandlerFactory;
|
||||
|
||||
private @NonNullByDefault({}) TadoHomeConfig configuration;
|
||||
private @NonNullByDefault({}) String confPendingText;
|
||||
private @NonNullByDefault({}) HomeApi api;
|
||||
|
||||
private @Nullable Long homeId;
|
||||
private final TadoBatteryChecker batteryChecker;
|
||||
private @Nullable ScheduledFuture<?> initializationFuture;
|
||||
private @Nullable OAuthClientService oAuthClientService;
|
||||
|
||||
public TadoHomeHandler(Bridge bridge) {
|
||||
public TadoHomeHandler(Bridge bridge, TadoHandlerFactory tadoHandlerFactory, OAuthFactory oAuthFactory) {
|
||||
super(bridge);
|
||||
batteryChecker = new TadoBatteryChecker(this);
|
||||
configuration = getConfigAs(TadoHomeConfig.class);
|
||||
api = new HomeApiFactory().create(configuration.username, configuration.password);
|
||||
this.batteryChecker = new TadoBatteryChecker(this);
|
||||
this.configuration = getConfigAs(TadoHomeConfig.class);
|
||||
this.tadoHandlerFactory = tadoHandlerFactory;
|
||||
}
|
||||
|
||||
public TemperatureUnit getTemperatureUnit() {
|
||||
String temperatureUnitStr = this.thing.getProperties()
|
||||
.getOrDefault(TadoBindingConstants.PROPERTY_HOME_TEMPERATURE_UNIT, "CELSIUS");
|
||||
String temperatureUnitStr = this.thing.getProperties().getOrDefault(PROPERTY_HOME_TEMPERATURE_UNIT, "CELSIUS");
|
||||
return TemperatureUnit.valueOf(temperatureUnitStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
configuration = getConfigAs(TadoHomeConfig.class);
|
||||
|
||||
String userName = configuration.username;
|
||||
String password = configuration.password;
|
||||
boolean v1CredentialsMissing = userName == null || userName.isBlank() || password == null || password.isBlank();
|
||||
|
||||
boolean suggestRfc8628 = false;
|
||||
suggestRfc8628 |= Boolean.TRUE.equals(configuration.useRfc8628);
|
||||
suggestRfc8628 |= v1CredentialsMissing;
|
||||
suggestRfc8628 |= ZonedDateTime.now().isAfter(OAUTH_MANDATORY_FROM_DATE);
|
||||
|
||||
if (suggestRfc8628) {
|
||||
OAuthClientService oAuthClientService = tadoHandlerFactory.subscribeOAuthClientService(this);
|
||||
oAuthClientService.addAccessTokenRefreshListener(this);
|
||||
this.api = new HomeApiFactory().create(oAuthClientService);
|
||||
this.oAuthClientService = oAuthClientService;
|
||||
logger.trace("initialize() api v2 created");
|
||||
confPendingText = CONF_PENDING_OAUTH_CREDS.formatted(TadoAuthenticationServlet.PATH);
|
||||
} else {
|
||||
api = new HomeApiFactory().create(Objects.requireNonNull(userName), Objects.requireNonNull(password));
|
||||
logger.trace("initialize() api v1 created");
|
||||
confPendingText = CONF_PENDING_USER_CREDS;
|
||||
}
|
||||
|
||||
ScheduledFuture<?> initializationFuture = this.initializationFuture;
|
||||
if (initializationFuture == null || initializationFuture.isDone()) {
|
||||
this.initializationFuture = scheduler.scheduleWithFixedDelay(
|
||||
@@ -84,7 +127,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBridgeStatusAndPropertiesIfOffline() {
|
||||
private synchronized void initializeBridgeStatusAndPropertiesIfOffline() {
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
ThingHandler handler = thing.getHandler();
|
||||
@@ -101,21 +144,19 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
// 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");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, confPendingText);
|
||||
return;
|
||||
}
|
||||
|
||||
List<UserHomes> homes = user.getHomes();
|
||||
if (homes == null || homes.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"User does not have access to any home");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, CONF_ERROR_NO_HOME);
|
||||
return;
|
||||
}
|
||||
|
||||
Integer firstHomeId = homes.get(0).getId();
|
||||
if (firstHomeId == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Missing Home Id");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, CONF_ERROR_NO_HOME_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,11 +167,10 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
HomeInfo homeInfo = api.showHome(homeId);
|
||||
TemperatureUnit temperatureUnit = org.openhab.binding.tado.swagger.codegen.api.model.TemperatureUnit.FAHRENHEIT == homeInfo
|
||||
.getTemperatureUnit() ? TemperatureUnit.FAHRENHEIT : TemperatureUnit.CELSIUS;
|
||||
updateProperty(TadoBindingConstants.PROPERTY_HOME_TEMPERATURE_UNIT, temperatureUnit.name());
|
||||
updateProperty(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());
|
||||
logger.debug("Error accessing tado server: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, confPendingText);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,6 +180,11 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
OAuthClientService oAuthClientService = this.oAuthClientService;
|
||||
if (oAuthClientService != null) {
|
||||
tadoHandlerFactory.unsubscribeOAuthClientService(this);
|
||||
oAuthClientService.removeAccessTokenRefreshListener(this);
|
||||
}
|
||||
ScheduledFuture<?> initializationFuture = this.initializationFuture;
|
||||
if (initializationFuture != null && !initializationFuture.isCancelled()) {
|
||||
initializationFuture.cancel(true);
|
||||
@@ -160,8 +205,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
|
||||
public void updateHomeState() {
|
||||
try {
|
||||
updateState(TadoBindingConstants.CHANNEL_HOME_PRESENCE_MODE,
|
||||
OnOffType.from(getHomeState().getPresence() == PresenceState.HOME));
|
||||
updateState(CHANNEL_HOME_PRESENCE_MODE, OnOffType.from(getHomeState().getPresence() == PresenceState.HOME));
|
||||
} catch (IOException | ApiException e) {
|
||||
logger.debug("Error accessing tado server: {}", e.getMessage(), e);
|
||||
}
|
||||
@@ -177,7 +221,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case TadoBindingConstants.CHANNEL_HOME_PRESENCE_MODE:
|
||||
case CHANNEL_HOME_PRESENCE_MODE:
|
||||
HomePresence presence = new HomePresence();
|
||||
presence.setHomePresence("ON".equals(command.toFullString().toUpperCase())
|
||||
|| "HOME".equals(command.toFullString().toUpperCase()) ? PresenceState.HOME
|
||||
@@ -195,4 +239,9 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
||||
public TadoBatteryChecker getBatteryChecker() {
|
||||
return this.batteryChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccessTokenResponse(AccessTokenResponse atr) {
|
||||
initializeBridgeStatusAndPropertiesIfOffline();
|
||||
}
|
||||
}
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tado.internal.handler.TadoHandlerFactory;
|
||||
import org.openhab.core.auth.client.oauth2.DeviceCodeResponseDTO;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||
|
||||
/**
|
||||
* The {@link TadoAuthenticationServlet} manages the authorization with the Tado API.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TadoAuthenticationServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 19530226123451L;
|
||||
|
||||
public static final String PATH = "/tado";
|
||||
|
||||
private static final String REPLACE_TAG = "$REPLACE$";
|
||||
|
||||
private static final String HTML_AUTH_PASSED = "<span style=\"color: #00cc00\">authenicated</span>";
|
||||
private static final String HTML_AUTH_NOT_REQUIRED = "<span style=\"color: #808080\">authentication not required</span>";
|
||||
private static final String HTML_AUTH_ERROR_TEMPLATE = "<span style=\"color: #ff0000\">$REPLACE$</span>";
|
||||
private static final String HTML_AUTH_START_TEMPLATE = "<a href=\"$REPLACE$\"><span style=\"color: #cc3300\">click to authenticate</span></a>";
|
||||
|
||||
private static final String PARAM_NAME = "oauth";
|
||||
private static final String PARAM_VALUE = "start";
|
||||
|
||||
private static final String ERROR_BAD_URL = "no verification uri";
|
||||
|
||||
private static final String HTML_STATUS_PAGE_TEMPLATE = """
|
||||
<html>
|
||||
<body>
|
||||
<h2 style="font-family: Arial; text-align: center">tado°</h2>
|
||||
<p style="font-family: Arial; text-align: center">Status: $REPLACE$</p>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
private final TadoHandlerFactory tadoHandlerFactory;
|
||||
|
||||
public TadoAuthenticationServlet(TadoHandlerFactory tadoHandlerFactory) {
|
||||
this.tadoHandlerFactory = tadoHandlerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fulfils HTTP GET requests.
|
||||
*
|
||||
* Depending on whether the request has a query string, the behavior differs as follows:
|
||||
*
|
||||
* <ul>
|
||||
* <li>If there is no query string, it serves the status page</li>
|
||||
* <li>If there is a given query string, it serves the user authentication page</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
// if the query string is "?oauth=start" then serve the user authentication page
|
||||
if (PARAM_VALUE.equals(request.getParameter(PARAM_NAME)) && tadoHandlerFactory.hasOAuthClientService()) {
|
||||
serveUserAuthenticationPage(request, response);
|
||||
} else {
|
||||
serveStatusPage(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves the status page.
|
||||
*
|
||||
* Depending on the type of authentication, it serves three flavours of page:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A page saying that authentication is not required</li>
|
||||
* <li>A page saying that authentication is already done</li>
|
||||
* <li>A page saying that authentication is required with a link to start the process</li>
|
||||
* </ul>
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void serveStatusPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String dynamicHtml = null;
|
||||
|
||||
if (!tadoHandlerFactory.hasOAuthClientService()) {
|
||||
dynamicHtml = HTML_AUTH_NOT_REQUIRED;
|
||||
}
|
||||
|
||||
if (dynamicHtml == null) {
|
||||
try {
|
||||
if (tadoHandlerFactory.getAccessTokenResponse() != null) {
|
||||
dynamicHtml = HTML_AUTH_PASSED;
|
||||
}
|
||||
} catch (OAuthException e) {
|
||||
dynamicHtml = HTML_AUTH_ERROR_TEMPLATE.replace(REPLACE_TAG,
|
||||
e.getMessage() instanceof String exception ? exception : e.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicHtml == null) {
|
||||
if (request.getRequestURL() instanceof StringBuffer baseUrl) {
|
||||
String dynamicUrl = baseUrl.append("?").append(PARAM_NAME).append("=").append(PARAM_VALUE).toString();
|
||||
dynamicHtml = HTML_AUTH_START_TEMPLATE.replace(REPLACE_TAG, dynamicUrl);
|
||||
} else {
|
||||
dynamicHtml = HTML_AUTH_ERROR_TEMPLATE.replace(REPLACE_TAG, ERROR_BAD_URL);
|
||||
}
|
||||
}
|
||||
|
||||
String content = HTML_STATUS_PAGE_TEMPLATE.replace(REPLACE_TAG, dynamicHtml);
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType(MediaType.TEXT_HTML);
|
||||
response.getWriter().write(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves the user authentication page.
|
||||
*
|
||||
* Tries to get the Tado remote RFC-8628 Authentication User URI from the authentication
|
||||
* service, which if valid is opened via an HTTP redirect. Or if the URI is empty, or the
|
||||
* authentication failed, it serves a local error page instead.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void serveUserAuthenticationPage(HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
String dynamicHtml = null;
|
||||
|
||||
try {
|
||||
DeviceCodeResponseDTO deviceCodeResponse = tadoHandlerFactory.getDeviceCodeResponse();
|
||||
String userVerificationUri = deviceCodeResponse.getVerificationUriComplete();
|
||||
if (userVerificationUri != null && !userVerificationUri.isBlank()) {
|
||||
response.sendRedirect(userVerificationUri);
|
||||
} else {
|
||||
dynamicHtml = HTML_AUTH_ERROR_TEMPLATE.replace(REPLACE_TAG, ERROR_BAD_URL);
|
||||
}
|
||||
} catch (OAuthException e) {
|
||||
dynamicHtml = HTML_AUTH_ERROR_TEMPLATE.replace(REPLACE_TAG,
|
||||
e.getMessage() instanceof String message ? message : e.getClass().getName());
|
||||
}
|
||||
|
||||
if (dynamicHtml != null) {
|
||||
String content = HTML_STATUS_PAGE_TEMPLATE.replace(REPLACE_TAG, dynamicHtml);
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType(MediaType.TEXT_HTML);
|
||||
response.getWriter().write(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -36,6 +36,10 @@ public class ApiException extends Exception {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ApiException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public ApiException(ContentResponse response, String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
this.code = response.getStatus();
|
||||
|
||||
+6
@@ -17,6 +17,7 @@ import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
@@ -25,6 +26,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.tado.internal.auth.OAuthorizerV2;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.ApiException;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.auth.Authorizer;
|
||||
import org.openhab.binding.tado.swagger.codegen.api.model.GenericZoneCapabilities;
|
||||
@@ -614,4 +616,8 @@ public class HomeApi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable OAuthorizerV2 getAuthorizerV2() {
|
||||
return authorizer instanceof OAuthorizerV2 v2 ? v2 : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +1,126 @@
|
||||
# add-on
|
||||
|
||||
addon.tado.name = tado° Binding
|
||||
addon.tado.description = Binding for tado° devices
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.tado.home.label = Tado Home
|
||||
thing-type.tado.home.description = The user's tado home
|
||||
thing-type.tado.mobiledevice.label = Mobile Device
|
||||
thing-type.tado.mobiledevice.description = Mobile device of a home
|
||||
thing-type.tado.zone.label = Zone
|
||||
thing-type.tado.zone.description = A zone of a home
|
||||
thing-type.tado.zone.channel.batteryLowAlarm.label = Battery Low Alarm
|
||||
thing-type.tado.zone.channel.batteryLowAlarm.description = ON if one or more devices in the zone have a low battery
|
||||
thing-type.tado.zone.channel.humidity.label = Humidity
|
||||
thing-type.tado.zone.channel.humidity.description = Current humidity in %
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.tado.home.password.label = Password
|
||||
thing-type.config.tado.home.password.description = Password of tado login used for API access
|
||||
thing-type.config.tado.home.username.label = User Name
|
||||
thing-type.config.tado.home.username.description = User name of tado login used for API access
|
||||
thing-type.config.tado.mobiledevice.id.label = Mobile Device Id
|
||||
thing-type.config.tado.mobiledevice.id.description = Id of the mobile device
|
||||
thing-type.config.tado.mobiledevice.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.tado.mobiledevice.refreshInterval.description = Refresh interval of location state
|
||||
thing-type.config.tado.zone.fallbackTimerDuration.label = Fallback Timer Duration
|
||||
thing-type.config.tado.zone.fallbackTimerDuration.description = Timer duration used if no other duration can be determined
|
||||
thing-type.config.tado.zone.hvacChangeDebounce.label = HVAC Change Debounce Delay
|
||||
thing-type.config.tado.zone.hvacChangeDebounce.description = Duration in seconds to combine multiple HVAC changes into one.
|
||||
thing-type.config.tado.zone.id.label = Zone Id
|
||||
thing-type.config.tado.zone.id.description = Id of the zone
|
||||
thing-type.config.tado.zone.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.tado.zone.refreshInterval.description = Refresh interval of home data
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.tado.acPower.label = Air-conditioning Power
|
||||
channel-type.tado.acPower.description = Current power state of the air-conditioning
|
||||
channel-type.tado.atHome.label = At Home
|
||||
channel-type.tado.atHome.description = ON if at home, OFF if away
|
||||
channel-type.tado.currentTemperature.label = Temperature
|
||||
channel-type.tado.currentTemperature.description = Current temperature
|
||||
channel-type.tado.fanLevel.label = Fan Speed
|
||||
channel-type.tado.fanLevel.description = AC fan level (only if supported by AC)
|
||||
channel-type.tado.fanLevel.state.option.SILENT = SILENT
|
||||
channel-type.tado.fanLevel.state.option.LEVEL1 = LEVEL1
|
||||
channel-type.tado.fanLevel.state.option.LEVEL2 = LEVEL2
|
||||
channel-type.tado.fanLevel.state.option.LEVEL3 = LEVEL3
|
||||
channel-type.tado.fanLevel.state.option.LEVEL4 = LEVEL4
|
||||
channel-type.tado.fanLevel.state.option.LEVEL5 = LEVEL5
|
||||
channel-type.tado.fanLevel.state.option.AUTO = AUTO
|
||||
channel-type.tado.fanspeed.label = Fan Speed
|
||||
channel-type.tado.fanspeed.description = AC fan speed (only if supported by AC)
|
||||
channel-type.tado.fanspeed.state.option.LOW = Low
|
||||
channel-type.tado.fanspeed.state.option.MIDDLE = Middle
|
||||
channel-type.tado.fanspeed.state.option.HIGH = High
|
||||
channel-type.tado.fanspeed.state.option.AUTO = Auto
|
||||
channel-type.tado.heatingPower.label = Heating Power
|
||||
channel-type.tado.heatingPower.description = Current heating power
|
||||
channel-type.tado.homePresence.label = At Home
|
||||
channel-type.tado.homePresence.description = ON if at home, OFF if away
|
||||
channel-type.tado.horizontalSwing.label = Horizontal Swing
|
||||
channel-type.tado.horizontalSwing.description = State of AC horizontal swing (only if supported by AC)
|
||||
channel-type.tado.horizontalSwing.state.option.AUTO = AUTO
|
||||
channel-type.tado.horizontalSwing.state.option.LEFT = LEFT
|
||||
channel-type.tado.horizontalSwing.state.option.MID_LEFT = MID-LEFT
|
||||
channel-type.tado.horizontalSwing.state.option.MID = MID
|
||||
channel-type.tado.horizontalSwing.state.option.MID_RIGHT = MID-RIGHT
|
||||
channel-type.tado.horizontalSwing.state.option.RIGHT = RIGHT
|
||||
channel-type.tado.horizontalSwing.state.option.ON = ON
|
||||
channel-type.tado.horizontalSwing.state.option.OFF = OFF
|
||||
channel-type.tado.hvacMode.label = HVAC Mode
|
||||
channel-type.tado.hvacMode.description = Mode of the device (OFF, HEAT, COOL, DRY, FAN, AUTO - if supported)
|
||||
channel-type.tado.hvacMode.state.option.OFF = Off
|
||||
channel-type.tado.hvacMode.state.option.HEAT = Heat
|
||||
channel-type.tado.hvacMode.state.option.COOL = Cool
|
||||
channel-type.tado.hvacMode.state.option.DRY = Dry
|
||||
channel-type.tado.hvacMode.state.option.FAN = Fan
|
||||
channel-type.tado.hvacMode.state.option.AUTO = Auto
|
||||
channel-type.tado.light.label = Light
|
||||
channel-type.tado.light.description = State of control panel light (only if supported by AC)
|
||||
channel-type.tado.openWindowDetected.label = Open Window Detected
|
||||
channel-type.tado.openWindowDetected.description = Indicates if an open window has been detected
|
||||
channel-type.tado.openWindowRemainingTime.label = Override Remaining Time
|
||||
channel-type.tado.openWindowRemainingTime.description = The remaining Open Window heating/cooling Override time in the Zone
|
||||
channel-type.tado.operationMode.label = Zone Operation Mode
|
||||
channel-type.tado.operationMode.description = Active operation mode (schedule, manual, timer or until next change)
|
||||
channel-type.tado.operationMode.state.option.SCHEDULE = Schedule
|
||||
channel-type.tado.operationMode.state.option.MANUAL = Manual
|
||||
channel-type.tado.operationMode.state.option.UNTIL_CHANGE = Until change
|
||||
channel-type.tado.operationMode.state.option.TIMER = Timer
|
||||
channel-type.tado.overlayExpiry.label = Overlay End Time
|
||||
channel-type.tado.overlayExpiry.description = Time until when the overlay is active. Null if no overlay is set or overlay type is manual.
|
||||
channel-type.tado.overlayExpiry.state.pattern = %1$tF %1$tR
|
||||
channel-type.tado.swing.label = Swing
|
||||
channel-type.tado.swing.description = State of AC swing (only if supported by AC)
|
||||
channel-type.tado.targetTemperature.label = Target Temperature
|
||||
channel-type.tado.targetTemperature.description = Thermostat temperature setpoint
|
||||
channel-type.tado.timerDuration.label = Timer Duration
|
||||
channel-type.tado.timerDuration.description = Total duration of a timer
|
||||
channel-type.tado.verticalSwing.label = Vertical Swing
|
||||
channel-type.tado.verticalSwing.description = State of AC vertical swing (only if supported by AC)
|
||||
channel-type.tado.verticalSwing.state.option.AUTO = AUTO
|
||||
channel-type.tado.verticalSwing.state.option.UP = UP
|
||||
channel-type.tado.verticalSwing.state.option.MID_UP = MID-UP
|
||||
channel-type.tado.verticalSwing.state.option.MID = MID
|
||||
channel-type.tado.verticalSwing.state.option.MID_DOWN = MID-DOWN
|
||||
channel-type.tado.verticalSwing.state.option.DOWN = DOWN
|
||||
channel-type.tado.verticalSwing.state.option.ON = ON
|
||||
channel-type.tado.verticalSwing.state.option.OFF = OFF
|
||||
|
||||
# discovery
|
||||
|
||||
discovery.bridge.label = tado° Internet Bridge ({0})
|
||||
# add-on
|
||||
|
||||
addon.tado.name = tado° Binding
|
||||
addon.tado.description = Binding for tado° devices
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.tado.home.label = Tado Home
|
||||
thing-type.tado.home.description = The user's tado home
|
||||
thing-type.tado.mobiledevice.label = Mobile Device
|
||||
thing-type.tado.mobiledevice.description = Mobile device of a home
|
||||
thing-type.tado.zone.label = Zone
|
||||
thing-type.tado.zone.description = A zone of a home
|
||||
thing-type.tado.zone.channel.batteryLowAlarm.label = Battery Low Alarm
|
||||
thing-type.tado.zone.channel.batteryLowAlarm.description = ON if one or more devices in the zone have a low battery
|
||||
thing-type.tado.zone.channel.humidity.label = Humidity
|
||||
thing-type.tado.zone.channel.humidity.description = Current humidity in %
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.tado.home.password.label = Password
|
||||
thing-type.config.tado.home.password.description = Password of tado login used for API access
|
||||
thing-type.config.tado.home.useRfc8628.label = Use oAuth RFC-8628
|
||||
thing-type.config.tado.home.useRfc8628.description = Determines if the binding shall use oAuth RFC-8628 authentication
|
||||
thing-type.config.tado.home.username.label = User Name
|
||||
thing-type.config.tado.home.username.description = User name of tado login used for API access
|
||||
thing-type.config.tado.mobiledevice.id.label = Mobile Device Id
|
||||
thing-type.config.tado.mobiledevice.id.description = Id of the mobile device
|
||||
thing-type.config.tado.mobiledevice.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.tado.mobiledevice.refreshInterval.description = Refresh interval of location state
|
||||
thing-type.config.tado.zone.fallbackTimerDuration.label = Fallback Timer Duration
|
||||
thing-type.config.tado.zone.fallbackTimerDuration.description = Timer duration used if no other duration can be determined
|
||||
thing-type.config.tado.zone.hvacChangeDebounce.label = HVAC Change Debounce Delay
|
||||
thing-type.config.tado.zone.hvacChangeDebounce.description = Duration in seconds to combine multiple HVAC changes into one.
|
||||
thing-type.config.tado.zone.id.label = Zone Id
|
||||
thing-type.config.tado.zone.id.description = Id of the zone
|
||||
thing-type.config.tado.zone.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.tado.zone.refreshInterval.description = Refresh interval of home data
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.tado.acPower.label = Air-conditioning Power
|
||||
channel-type.tado.acPower.description = Current power state of the air-conditioning
|
||||
channel-type.tado.atHome.label = At Home
|
||||
channel-type.tado.atHome.description = ON if at home, OFF if away
|
||||
channel-type.tado.currentTemperature.label = Temperature
|
||||
channel-type.tado.currentTemperature.description = Current temperature
|
||||
channel-type.tado.fanLevel.label = Fan Speed
|
||||
channel-type.tado.fanLevel.description = AC fan level (only if supported by AC)
|
||||
channel-type.tado.fanLevel.state.option.SILENT = SILENT
|
||||
channel-type.tado.fanLevel.state.option.LEVEL1 = LEVEL1
|
||||
channel-type.tado.fanLevel.state.option.LEVEL2 = LEVEL2
|
||||
channel-type.tado.fanLevel.state.option.LEVEL3 = LEVEL3
|
||||
channel-type.tado.fanLevel.state.option.LEVEL4 = LEVEL4
|
||||
channel-type.tado.fanLevel.state.option.LEVEL5 = LEVEL5
|
||||
channel-type.tado.fanLevel.state.option.AUTO = AUTO
|
||||
channel-type.tado.fanspeed.label = Fan Speed
|
||||
channel-type.tado.fanspeed.description = AC fan speed (only if supported by AC)
|
||||
channel-type.tado.fanspeed.state.option.LOW = Low
|
||||
channel-type.tado.fanspeed.state.option.MIDDLE = Middle
|
||||
channel-type.tado.fanspeed.state.option.HIGH = High
|
||||
channel-type.tado.fanspeed.state.option.AUTO = Auto
|
||||
channel-type.tado.heatingPower.label = Heating Power
|
||||
channel-type.tado.heatingPower.description = Current heating power
|
||||
channel-type.tado.homePresence.label = At Home
|
||||
channel-type.tado.homePresence.description = ON if at home, OFF if away
|
||||
channel-type.tado.horizontalSwing.label = Horizontal Swing
|
||||
channel-type.tado.horizontalSwing.description = State of AC horizontal swing (only if supported by AC)
|
||||
channel-type.tado.horizontalSwing.state.option.AUTO = AUTO
|
||||
channel-type.tado.horizontalSwing.state.option.LEFT = LEFT
|
||||
channel-type.tado.horizontalSwing.state.option.MID_LEFT = MID-LEFT
|
||||
channel-type.tado.horizontalSwing.state.option.MID = MID
|
||||
channel-type.tado.horizontalSwing.state.option.MID_RIGHT = MID-RIGHT
|
||||
channel-type.tado.horizontalSwing.state.option.RIGHT = RIGHT
|
||||
channel-type.tado.horizontalSwing.state.option.ON = ON
|
||||
channel-type.tado.horizontalSwing.state.option.OFF = OFF
|
||||
channel-type.tado.hvacMode.label = HVAC Mode
|
||||
channel-type.tado.hvacMode.description = Mode of the device (OFF, HEAT, COOL, DRY, FAN, AUTO - if supported)
|
||||
channel-type.tado.hvacMode.state.option.OFF = Off
|
||||
channel-type.tado.hvacMode.state.option.HEAT = Heat
|
||||
channel-type.tado.hvacMode.state.option.COOL = Cool
|
||||
channel-type.tado.hvacMode.state.option.DRY = Dry
|
||||
channel-type.tado.hvacMode.state.option.FAN = Fan
|
||||
channel-type.tado.hvacMode.state.option.AUTO = Auto
|
||||
channel-type.tado.light.label = Light
|
||||
channel-type.tado.light.description = State of control panel light (only if supported by AC)
|
||||
channel-type.tado.openWindowDetected.label = Open Window Detected
|
||||
channel-type.tado.openWindowDetected.description = Indicates if an open window has been detected
|
||||
channel-type.tado.openWindowRemainingTime.label = Override Remaining Time
|
||||
channel-type.tado.openWindowRemainingTime.description = The remaining Open Window heating/cooling Override time in the Zone
|
||||
channel-type.tado.operationMode.label = Zone Operation Mode
|
||||
channel-type.tado.operationMode.description = Active operation mode (schedule, manual, timer or until next change)
|
||||
channel-type.tado.operationMode.state.option.SCHEDULE = Schedule
|
||||
channel-type.tado.operationMode.state.option.MANUAL = Manual
|
||||
channel-type.tado.operationMode.state.option.UNTIL_CHANGE = Until change
|
||||
channel-type.tado.operationMode.state.option.TIMER = Timer
|
||||
channel-type.tado.overlayExpiry.label = Overlay End Time
|
||||
channel-type.tado.overlayExpiry.description = Time until when the overlay is active. Null if no overlay is set or overlay type is manual.
|
||||
channel-type.tado.overlayExpiry.state.pattern = %1$tF %1$tR
|
||||
channel-type.tado.swing.label = Swing
|
||||
channel-type.tado.swing.description = State of AC swing (only if supported by AC)
|
||||
channel-type.tado.targetTemperature.label = Target Temperature
|
||||
channel-type.tado.targetTemperature.description = Thermostat temperature setpoint
|
||||
channel-type.tado.timerDuration.label = Timer Duration
|
||||
channel-type.tado.timerDuration.description = Total duration of a timer
|
||||
channel-type.tado.verticalSwing.label = Vertical Swing
|
||||
channel-type.tado.verticalSwing.description = State of AC vertical swing (only if supported by AC)
|
||||
channel-type.tado.verticalSwing.state.option.AUTO = AUTO
|
||||
channel-type.tado.verticalSwing.state.option.UP = UP
|
||||
channel-type.tado.verticalSwing.state.option.MID_UP = MID-UP
|
||||
channel-type.tado.verticalSwing.state.option.MID = MID
|
||||
channel-type.tado.verticalSwing.state.option.MID_DOWN = MID-DOWN
|
||||
channel-type.tado.verticalSwing.state.option.DOWN = DOWN
|
||||
channel-type.tado.verticalSwing.state.option.ON = ON
|
||||
channel-type.tado.verticalSwing.state.option.OFF = OFF
|
||||
|
||||
# tado home thing status messages
|
||||
|
||||
tado.home.status.oauth = Try authenticating at {0}
|
||||
tado.home.status.username = Username and/or password might be invalid
|
||||
tado.home.status.nohome = User does not have access to any home
|
||||
tado.home.status.nohomeid = Missing Home Id
|
||||
|
||||
# discovery
|
||||
|
||||
discovery.bridge.label = tado° Internet Bridge ({0})
|
||||
|
||||
@@ -14,15 +14,22 @@
|
||||
</channels>
|
||||
|
||||
<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 name="useRfc8628" type="boolean" required="false">
|
||||
<label>Use oAuth RFC-8628</label>
|
||||
<description>Determines if the binding shall use oAuth RFC-8628 authentication</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="password" type="text" required="true">
|
||||
<parameter name="username" type="text" required="false">
|
||||
<label>User Name</label>
|
||||
<description>User name of tado login used for API access</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password of tado login used for API access</description>
|
||||
<context>password</context>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
Reference in New Issue
Block a user