]> git.basschouten.com Git - openhab-addons.git/commitdiff
[tado] Add support for fanLevel, verticalSwing, horizontalSwing, light API (#12470)
authorAndrew Fiddian-Green <software@whitebear.ch>
Sat, 2 Apr 2022 07:59:13 +0000 (08:59 +0100)
committerGitHub <noreply@github.com>
Sat, 2 Apr 2022 07:59:13 +0000 (09:59 +0200)
* [tado] fix issue #12160 (first attempt)
* [tado] code style
* [tado] option descriptors
* [tado] add level5
* [tado] fan level and swing not allowed on heating or hot water
* [tado] warn about possible un-supported state values
* [tado] minor formatting
* [tado] harmonise getter methods for fan and swing state
* [tado] include all options in xml
* [tado] remove 's' from capabilities list names (go figure..)
* [tado] add OFF option
* [tado] add support for light channel, and dynamic channel decriptions
* [tado] tweak ReadMe for more clarity
* [tado] adopt reviewer suggestions

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
15 files changed:
bundles/org.openhab.binding.tado/README.md
bundles/org.openhab.binding.tado/src/main/api/tado-api.yaml
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/TadoBindingConstants.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/TadoHvacChange.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/adapter/TadoZoneStateAdapter.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/TadoApiTypeUtils.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/AirConditioningZoneSettingsBuilder.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HeatingZoneSettingsBuilder.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HotWaterZoneSettingsBuilder.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneSettingsBuilder.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHandlerFactory.java
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoStateDescriptionProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoZoneHandler.java
bundles/org.openhab.binding.tado/src/main/resources/OH-INF/i18n/tado.properties
bundles/org.openhab.binding.tado/src/main/resources/OH-INF/thing/thing-types.xml

index 69c03afdac7c84def4e9f53e9b90acccdd5906ef..b33aac7a0a809a06135197cb8c8dc3dc7da7a764 100644 (file)
@@ -27,7 +27,7 @@ Afterwards the discovery will show all zones and mobile devices associated with
 ### Channels
 
 Name | Type | Description | Read/Write
--|-|-|-|-
+-|-|-|-
 `homePresence` | String | Current presence value of the tado home. `HOME` and `AWAY` can be set | RW
 
 ## `zone` Thing
@@ -70,16 +70,34 @@ Name | Type | Description | Read/Write | Zone type
 `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`
+`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`
 `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
+`light` | Switch | State (`ON`, `OFF`) of the control panel light (if applicable) | RW | `AC`
 
 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.
index 53d21c9579dabaa0958b3b1932c1ebf2e3fd9075..05226ae6ce71522c911412ffc06773c96194acb0 100644 (file)
@@ -552,6 +552,44 @@ definitions:
     - HIGH
     - AUTO
 
+  ACFanLevel:
+    type: string
+    description: Cooling system fan speed.
+    enum:
+    - SILENT
+    - LEVEL1
+    - LEVEL2
+    - LEVEL3
+    - LEVEL4
+    - LEVEL5
+    - AUTO
+
+  ACHorizontalSwing: 
+    type: string
+    description: Horizontal swing.
+    enum:
+    - 'OFF'
+    - 'ON'
+    - LEFT
+    - MID_LEFT
+    - MID
+    - MID_RIGHT
+    - RIGHT
+    - AUTO
+
+  ACVerticalSwing:
+    type: string
+    description: Vertical swing.
+    enum:
+    - 'OFF'
+    - 'ON'
+    - UP
+    - MID_UP
+    - MID
+    - MID_DOWN
+    - DOWN
+    - AUTO
   AcMode:
     type: string
     description: Cooling system mode.
@@ -582,6 +620,18 @@ definitions:
         swing:
           description: Whether the angle of the air stream should be fixed or not, if power is `ON` and configuring this is supported in this AC mode.
           $ref: "#/definitions/Power"
+        light:
+          description: State of the control panel light.
+          $ref: "#/definitions/Power"
+        fanLevel:
+          description: The desired fan speed level, if power is `ON` and fan speeds are supported in this AC mode.
+          $ref: "#/definitions/ACFanLevel"
+        verticalSwing:
+          description: Whether the angle of the vertical air stream should be fixed or not, if power is `ON` and configuring this is supported in this AC mode. And if it is fixed, determines the respective position.
+          $ref: "#/definitions/ACVerticalSwing"
+        horizontalSwing:
+          description: Whether the angle of the horizontal air stream should be fixed or not, if power is `ON` and configuring this is supported in this AC mode. And if it is fixed, determines the respective position.
+          $ref: "#/definitions/ACHorizontalSwing"
       required:
       - power
 
@@ -981,15 +1031,35 @@ definitions:
       temperatures:
         $ref: "#/definitions/TemperatureRange"
       fanSpeeds:
-        description: Cooling system fan speed.
+        description: Cooling system fan speeds.
         type: array
         items:
           $ref: "#/definitions/AcFanSpeed"
       swings:
-        description: Cooling system swing mode.
+        description: Cooling system swing modes.
+        type: array
+        items:
+          $ref: "#/definitions/Power"
+      light:
+        description: Control panel light state. (Tado confusingly names this array without an 's')
         type: array
         items:
           $ref: "#/definitions/Power"
+      fanLevel:
+        description: Cooling system fan speeds. (Tado confusingly names this array without an 's')
+        type: array
+        items:
+          $ref: "#/definitions/ACFanLevel"
+      horizontalSwing:
+        description: Cooling system horizontal swing modes. (Tado confusingly names this array without an 's')
+        type: array
+        items:
+          $ref: "#/definitions/ACHorizontalSwing"
+      verticalSwing:
+        description: Cooling system vertical swing modes. (Tado confusingly names this array without an 's')
+        type: array
+        items:
+          $ref: "#/definitions/ACVerticalSwing"
 
   HeatingCapabilities:
     x-discriminator-value: HEATING
index e52f3578ab1b01b7819f2f1dfa4e975bb1807361..e1fa6e0391633f53323c3e356361fc71068461ca 100644 (file)
@@ -65,6 +65,8 @@ public class TadoBindingConstants {
 
     public static final String CHANNEL_ZONE_SWING = "swing";
 
+    public static final String CHANNEL_ZONE_LIGHT = "light";
+
     public static final String CHANNEL_ZONE_FAN_SPEED = "fanspeed";
 
     public static enum FanSpeed {
@@ -74,6 +76,44 @@ public class TadoBindingConstants {
         AUTO
     }
 
+    public static final String CHANNEL_ZONE_FAN_LEVEL = "fanLevel";
+
+    public static enum FanLevel {
+        SILENT,
+        LEVEL1,
+        LEVEL2,
+        LEVEL3,
+        LEVEL4,
+        LEVEL5,
+        AUTO
+    }
+
+    public static final String CHANNEL_ZONE_HORIZONTAL_SWING = "horizontalSwing";
+
+    public static enum HorizontalSwing {
+        OFF,
+        ON,
+        LEFT,
+        MID_LEFT,
+        MID,
+        MID_RIGHT,
+        RIGHT,
+        AUTO
+    }
+
+    public static final String CHANNEL_ZONE_VERTICAL_SWING = "verticalSwing";
+
+    public static enum VerticalSwing {
+        OFF,
+        ON,
+        UP,
+        MID_UP,
+        MID,
+        MID_DOWN,
+        DOWN,
+        AUTO
+    }
+
     public static final String CHANNEL_ZONE_OPERATION_MODE = "operationMode";
 
     public static enum OperationMode {
index cc9f509edccb735052edd538539d51c83376534a..de47df19fcb0b8ccfe7a9bb68b7b8eaddb131b0c 100644 (file)
@@ -14,9 +14,12 @@ package org.openhab.binding.tado.internal;
 
 import java.io.IOException;
 
+import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
 import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
+import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
 import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
 import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
+import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing;
 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;
@@ -115,6 +118,21 @@ public class TadoHvacChange {
         return this;
     }
 
+    public TadoHvacChange withFanLevel(FanLevel fanLevel) {
+        settingsBuilder.withFanLevel(fanLevel);
+        return this;
+    }
+
+    public TadoHvacChange withHorizontalSwing(HorizontalSwing horizontalSwing) {
+        settingsBuilder.withHorizontalSwing(horizontalSwing);
+        return this;
+    }
+
+    public TadoHvacChange withVerticalSwing(VerticalSwing verticalSwing) {
+        settingsBuilder.withVerticalSwing(verticalSwing);
+        return this;
+    }
+
     public void apply() throws IOException, ApiException {
         if (followSchedule) {
             zoneHandler.removeOverlay();
@@ -135,4 +153,9 @@ public class TadoHvacChange {
 
         return overlay;
     }
+
+    public TadoHvacChange withLight(boolean lightOn) {
+        settingsBuilder.withLight(lightOn);
+        return this;
+    }
 }
index 7c6148a1b66edaf2b998babe304204f9cd9aac9d..4e80dc9f08a2655ff820957e4d9ddafd4ce87515 100644 (file)
@@ -19,6 +19,10 @@ 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.ACFanLevel;
+import org.openhab.binding.tado.internal.api.model.ACHorizontalSwing;
+import org.openhab.binding.tado.internal.api.model.ACVerticalSwing;
+import org.openhab.binding.tado.internal.api.model.AcFanSpeed;
 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;
@@ -119,27 +123,42 @@ public class TadoZoneStateAdapter {
 
     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;
+            AcFanSpeed result = ((CoolingZoneSetting) zoneState.getSetting()).getFanSpeed();
+            return result != null ? StringType.valueOf(result.getValue()) : UnDefType.NULL;
         }
+        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;
+            Power result = ((CoolingZoneSetting) zoneState.getSetting()).getSwing();
+            return result != null ? OnOffType.from(result == Power.ON) : UnDefType.NULL;
         }
+        return UnDefType.UNDEF;
+    }
+
+    public State getFanLevel() {
+        if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
+            ACFanLevel result = ((CoolingZoneSetting) zoneState.getSetting()).getFanLevel();
+            return result != null ? StringType.valueOf(result.getValue()) : UnDefType.NULL;
+        }
+        return UnDefType.UNDEF;
+    }
+
+    public State getHorizontalSwing() {
+        if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
+            ACHorizontalSwing result = ((CoolingZoneSetting) zoneState.getSetting()).getHorizontalSwing();
+            return result != null ? StringType.valueOf(result.getValue()) : UnDefType.NULL;
+        }
+        return UnDefType.UNDEF;
+    }
+
+    public State getVerticalSwing() {
+        if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
+            ACVerticalSwing result = ((CoolingZoneSetting) zoneState.getSetting()).getVerticalSwing();
+            return result != null ? StringType.valueOf(result.getValue()) : UnDefType.NULL;
+        }
+        return UnDefType.UNDEF;
     }
 
     public StringType getOperationMode() {
@@ -235,4 +254,12 @@ public class TadoZoneStateAdapter {
         }
         return OnOffType.OFF;
     }
+
+    public State getLight() {
+        if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
+            Power result = ((CoolingZoneSetting) zoneState.getSetting()).getLight();
+            return result != null ? OnOffType.from(result == Power.ON) : UnDefType.NULL;
+        }
+        return UnDefType.UNDEF;
+    }
 }
index 72d2d3045cc76564f085460a1eb5c848f969075d..909648f9b34b98627f905da9efceb0235d07d681 100644 (file)
  */
 package org.openhab.binding.tado.internal.api;
 
+import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
 import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
+import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
 import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
 import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
+import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing;
+import org.openhab.binding.tado.internal.api.model.ACFanLevel;
+import org.openhab.binding.tado.internal.api.model.ACHorizontalSwing;
+import org.openhab.binding.tado.internal.api.model.ACVerticalSwing;
 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;
@@ -144,6 +150,85 @@ public class TadoApiTypeUtils {
         return null;
     }
 
+    public static ACFanLevel getFanLevel(FanLevel fanLevel) {
+        if (fanLevel == null) {
+            return null;
+        }
+
+        switch (fanLevel) {
+            case AUTO:
+                return ACFanLevel.AUTO;
+            case LEVEL1:
+                return ACFanLevel.LEVEL1;
+            case LEVEL2:
+                return ACFanLevel.LEVEL2;
+            case LEVEL3:
+                return ACFanLevel.LEVEL3;
+            case LEVEL4:
+                return ACFanLevel.LEVEL4;
+            case LEVEL5:
+                return ACFanLevel.LEVEL5;
+            case SILENT:
+                return ACFanLevel.SILENT;
+        }
+
+        return null;
+    }
+
+    public static ACHorizontalSwing getHorizontalSwing(HorizontalSwing horizontalSwing) {
+        if (horizontalSwing == null) {
+            return null;
+        }
+
+        switch (horizontalSwing) {
+            case LEFT:
+                return ACHorizontalSwing.LEFT;
+            case MID_LEFT:
+                return ACHorizontalSwing.MID_LEFT;
+            case MID:
+                return ACHorizontalSwing.MID;
+            case MID_RIGHT:
+                return ACHorizontalSwing.MID_RIGHT;
+            case RIGHT:
+                return ACHorizontalSwing.RIGHT;
+            case ON:
+                return ACHorizontalSwing.ON;
+            case OFF:
+                return ACHorizontalSwing.OFF;
+            case AUTO:
+                return ACHorizontalSwing.AUTO;
+        }
+
+        return null;
+    }
+
+    public static ACVerticalSwing getVerticalSwing(VerticalSwing verticalSwing) {
+        if (verticalSwing == null) {
+            return null;
+        }
+
+        switch (verticalSwing) {
+            case AUTO:
+                return ACVerticalSwing.AUTO;
+            case UP:
+                return ACVerticalSwing.UP;
+            case MID_UP:
+                return ACVerticalSwing.MID_UP;
+            case MID:
+                return ACVerticalSwing.MID;
+            case MID_DOWN:
+                return ACVerticalSwing.MID_DOWN;
+            case DOWN:
+                return ACVerticalSwing.DOWN;
+            case ON:
+                return ACVerticalSwing.ON;
+            case OFF:
+                return ACVerticalSwing.OFF;
+        }
+
+        return null;
+    }
+
     public static AcModeCapabilities getModeCapabilities(AirConditioningCapabilities capabilities, AcMode mode) {
         AcModeCapabilities modeCapabilities = null;
 
index 9c0b4d64219fb3901b9e77de593fbd67a15b3d0a..0a5a3c605148f38a008782c43ba206ea4efb4bbb 100644 (file)
@@ -20,6 +20,9 @@ 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.ACFanLevel;
+import org.openhab.binding.tado.internal.api.model.ACHorizontalSwing;
+import org.openhab.binding.tado.internal.api.model.ACVerticalSwing;
 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;
@@ -60,10 +63,26 @@ public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
             setting.setSwing(swing ? Power.ON : Power.OFF);
         }
 
+        if (light != null) {
+            setting.setLight(light ? Power.ON : Power.OFF);
+        }
+
         if (fanSpeed != null) {
             setting.setFanSpeed(getAcFanSpeed(fanSpeed));
         }
 
+        if (fanLevel != null) {
+            setting.setFanLevel(getFanLevel(fanLevel));
+        }
+
+        if (horizontalSwing != null) {
+            setting.setHorizontalSwing(getHorizontalSwing(horizontalSwing));
+        }
+
+        if (verticalSwing != null) {
+            setting.setVerticalSwing(getVerticalSwing(verticalSwing));
+        }
+
         addMissingSettingParts(zoneStateProvider, genericCapabilities, setting);
 
         return setting;
@@ -79,21 +98,43 @@ public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
         AcModeCapabilities capabilities = getModeCapabilities((AirConditioningCapabilities) genericCapabilities,
                 setting.getMode());
 
-        if (capabilities.getTemperatures() != null && setting.getTemperature() == null) {
-            TemperatureObject targetTemperature = getCurrentOrDefaultTemperature(zoneStateProvider,
-                    capabilities.getTemperatures());
-            setting.setTemperature(targetTemperature);
+        TemperatureRange temperatures = capabilities.getTemperatures();
+        if (temperatures != null && setting.getTemperature() == null) {
+            setting.setTemperature(getCurrentOrDefaultTemperature(zoneStateProvider, temperatures));
         }
 
-        if (capabilities.getFanSpeeds() != null && !capabilities.getFanSpeeds().isEmpty()
-                && setting.getFanSpeed() == null) {
-            AcFanSpeed fanSpeed = getCurrentOrDefaultFanSpeed(zoneStateProvider, capabilities.getFanSpeeds());
-            setting.setFanSpeed(fanSpeed);
+        List<AcFanSpeed> fanSpeeds = capabilities.getFanSpeeds();
+        if (fanSpeeds != null && !fanSpeeds.isEmpty() && setting.getFanSpeed() == null) {
+            setting.setFanSpeed(getCurrentOrDefaultFanSpeed(zoneStateProvider, fanSpeeds));
         }
 
-        if (capabilities.getSwings() != null && !capabilities.getSwings().isEmpty() && setting.getSwing() == null) {
-            Power swing = getCurrentOrDefaultSwing(zoneStateProvider, capabilities.getSwings());
-            setting.setSwing(swing);
+        List<Power> swings = capabilities.getSwings();
+        if (swings != null && !swings.isEmpty() && setting.getSwing() == null) {
+            setting.setSwing(getCurrentOrDefaultSwing(zoneStateProvider, swings));
+        }
+
+        // Tado confusingly calls the List / getter method 'fanLevel' / 'getFanLevel()' without 's'
+        List<ACFanLevel> fanLevels = capabilities.getFanLevel();
+        if (fanLevels != null && !fanLevels.isEmpty() && setting.getFanLevel() == null) {
+            setting.setFanLevel(getCurrentOrDefaultFanLevel(zoneStateProvider, fanLevels));
+        }
+
+        // Tado confusingly calls the List / getter method 'horizontalSwing' / 'getHorizontalSwing()' without 's'
+        List<ACHorizontalSwing> horizontalSwings = capabilities.getHorizontalSwing();
+        if (horizontalSwings != null && !horizontalSwings.isEmpty() && setting.getHorizontalSwing() == null) {
+            setting.setHorizontalSwing(getCurrentOrDefaultHorizontalSwing(zoneStateProvider, horizontalSwings));
+        }
+
+        // Tado confusingly calls the List / getter method 'verticalSwing' / 'getVerticalSwing()' without 's'
+        List<ACVerticalSwing> verticalSwings = capabilities.getVerticalSwing();
+        if (verticalSwings != null && !verticalSwings.isEmpty() && setting.getVerticalSwing() == null) {
+            setting.setVerticalSwing(getCurrentOrDefaultVerticalSwing(zoneStateProvider, verticalSwings));
+        }
+
+        // Tado confusingly calls the List / getter method 'light' / 'getLight()' without 's'
+        List<Power> lights = capabilities.getLight();
+        if (lights != null && !lights.isEmpty() && setting.getLight() == null) {
+            setting.setLight(getCurrentOrDefaultLight(zoneStateProvider, lights));
         }
     }
 
@@ -144,6 +185,50 @@ public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
         return swings.get(0);
     }
 
+    private Power getCurrentOrDefaultLight(ZoneStateProvider zoneStateProvider, List<Power> lights)
+            throws IOException, ApiException {
+        CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
+
+        if (zoneSetting.getLight() != null && lights.contains(zoneSetting.getLight())) {
+            return zoneSetting.getLight();
+        }
+
+        return lights.get(0);
+    }
+
+    private ACFanLevel getCurrentOrDefaultFanLevel(ZoneStateProvider zoneStateProvider, List<ACFanLevel> fanLevels)
+            throws IOException, ApiException {
+        CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
+
+        if (zoneSetting.getFanLevel() != null && fanLevels.contains(zoneSetting.getFanLevel())) {
+            return zoneSetting.getFanLevel();
+        }
+
+        return fanLevels.get(0);
+    }
+
+    private ACHorizontalSwing getCurrentOrDefaultHorizontalSwing(ZoneStateProvider zoneStateProvider,
+            List<ACHorizontalSwing> horizontalSwings) throws IOException, ApiException {
+        CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
+
+        if (zoneSetting.getHorizontalSwing() != null && horizontalSwings.contains(zoneSetting.getHorizontalSwing())) {
+            return zoneSetting.getHorizontalSwing();
+        }
+
+        return horizontalSwings.get(0);
+    }
+
+    private ACVerticalSwing getCurrentOrDefaultVerticalSwing(ZoneStateProvider zoneStateProvider,
+            List<ACVerticalSwing> verticalSwings) throws IOException, ApiException {
+        CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
+
+        if (zoneSetting.getVerticalSwing() != null && verticalSwings.contains(zoneSetting.getVerticalSwing())) {
+            return zoneSetting.getVerticalSwing();
+        }
+
+        return verticalSwings.get(0);
+    }
+
     private CoolingZoneSetting coolingSetting(boolean powerOn) {
         CoolingZoneSetting setting = new CoolingZoneSetting();
         setting.setType(TadoSystemType.AIR_CONDITIONING);
index 13c64d93219fd846a4737e2cf9a25308ffc5f466..d6d35896571c9e1190246460d4b96919f81b6ccf 100644 (file)
@@ -16,8 +16,11 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature
 
 import java.io.IOException;
 
+import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
 import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
+import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
 import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
+import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing;
 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;
@@ -40,11 +43,31 @@ public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder {
         throw new IllegalArgumentException("Heating zones don't support SWING");
     }
 
+    @Override
+    public ZoneSettingsBuilder withLight(boolean lightOn) {
+        throw new IllegalArgumentException("Heating zones don't support LIGHT");
+    }
+
     @Override
     public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
         throw new IllegalArgumentException("Heating zones don't support FAN SPEED");
     }
 
+    @Override
+    public ZoneSettingsBuilder withFanLevel(FanLevel fanLevel) {
+        throw new IllegalArgumentException("Heating zones don't support FAN LEVEL");
+    }
+
+    @Override
+    public ZoneSettingsBuilder withHorizontalSwing(HorizontalSwing horizontalSwing) {
+        throw new IllegalArgumentException("Heating zones don't support HORIZONTAL SWING");
+    }
+
+    @Override
+    public ZoneSettingsBuilder withVerticalSwing(VerticalSwing verticalSwing) {
+        throw new IllegalArgumentException("Heating zones don't support VERTICAL SWING");
+    }
+
     @Override
     public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
             throws IOException, ApiException {
index df3f0e34aee01693cb3ba5064818cef1e104454c..b4e9d7c77af302eced0080dd2674e4f1db7d0fb5 100644 (file)
@@ -16,8 +16,11 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature
 
 import java.io.IOException;
 
+import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
 import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
+import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
 import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
+import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing;
 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;
@@ -41,11 +44,31 @@ public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder {
         throw new IllegalArgumentException("Hot Water zones don't support SWING");
     }
 
+    @Override
+    public ZoneSettingsBuilder withLight(boolean lightOn) {
+        throw new IllegalArgumentException("Hot Water zones don't support LIGHT");
+    }
+
     @Override
     public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
         throw new IllegalArgumentException("Hot Water zones don't support FAN SPEED");
     }
 
+    @Override
+    public ZoneSettingsBuilder withFanLevel(FanLevel fanLevel) {
+        throw new IllegalArgumentException("Hot Water zones don't support FAN LEVEL");
+    }
+
+    @Override
+    public ZoneSettingsBuilder withHorizontalSwing(HorizontalSwing horizontalSwing) {
+        throw new IllegalArgumentException("Hot Water zones don't support HORIZONTAL SWING");
+    }
+
+    @Override
+    public ZoneSettingsBuilder withVerticalSwing(VerticalSwing verticalSwing) {
+        throw new IllegalArgumentException("Hot Water zones don't support VERTICAL SWING");
+    }
+
     @Override
     public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
             throws IOException, ApiException {
index 803c0aa0db1897a95c7e88a60482e3e53c287b84..43d7efd9991643cdd3585fa3b93ce391f5014df7 100644 (file)
@@ -15,9 +15,12 @@ package org.openhab.binding.tado.internal.builder;
 import java.io.IOException;
 
 import org.openhab.binding.tado.internal.TadoBindingConstants;
+import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
 import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
+import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
 import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
 import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
+import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing;
 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;
@@ -51,7 +54,11 @@ public abstract class ZoneSettingsBuilder {
     protected Float temperature = null;
     protected TemperatureUnit temperatureUnit = TemperatureUnit.CELSIUS;
     protected Boolean swing = null;
+    protected Boolean light = null;
     protected FanSpeed fanSpeed = null;
+    protected FanLevel fanLevel = null;
+    protected HorizontalSwing horizontalSwing = null;
+    protected VerticalSwing verticalSwing = null;
 
     public ZoneSettingsBuilder withMode(HvacMode mode) {
         this.mode = mode;
@@ -74,6 +81,21 @@ public abstract class ZoneSettingsBuilder {
         return this;
     }
 
+    public ZoneSettingsBuilder withFanLevel(FanLevel fanLevel) {
+        this.fanLevel = fanLevel;
+        return this;
+    }
+
+    public ZoneSettingsBuilder withHorizontalSwing(HorizontalSwing horizontalSwing) {
+        this.horizontalSwing = horizontalSwing;
+        return this;
+    }
+
+    public ZoneSettingsBuilder withVerticalSwing(VerticalSwing verticalSwing) {
+        this.verticalSwing = verticalSwing;
+        return this;
+    }
+
     public abstract GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
             throws IOException, ApiException;
 
@@ -103,4 +125,9 @@ public abstract class ZoneSettingsBuilder {
 
         return temperatureObject;
     }
+
+    public ZoneSettingsBuilder withLight(boolean lightOn) {
+        this.light = lightOn;
+        return this;
+    }
 }
index e6603b4bfa64805ed6841f6a441f087b1b68a59f..f7ac2d6752dace4f0c58969ce55b26920d09b15f 100644 (file)
@@ -32,7 +32,9 @@ 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.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 
 /**
  * The {@link TadoHandlerFactory} is responsible for creating things and thing
@@ -48,6 +50,13 @@ public class TadoHandlerFactory extends BaseThingHandlerFactory {
 
     private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
 
+    private final TadoStateDescriptionProvider stateDescriptionProvider;
+
+    @Activate
+    public TadoHandlerFactory(final @Reference TadoStateDescriptionProvider stateDescriptionProvider) {
+        this.stateDescriptionProvider = stateDescriptionProvider;
+    }
+
     @Override
     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
         return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
@@ -62,7 +71,7 @@ public class TadoHandlerFactory extends BaseThingHandlerFactory {
             registerTadoDiscoveryService(tadoHomeHandler);
             return tadoHomeHandler;
         } else if (thingTypeUID.equals(THING_TYPE_ZONE)) {
-            return new TadoZoneHandler(thing);
+            return new TadoZoneHandler(thing, stateDescriptionProvider);
         } else if (thingTypeUID.equals(THING_TYPE_MOBILE_DEVICE)) {
             return new TadoMobileDeviceHandler(thing);
         }
diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoStateDescriptionProvider.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoStateDescriptionProvider.java
new file mode 100644 (file)
index 0000000..02da72e
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tado.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.events.EventPublisher;
+import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
+import org.openhab.core.thing.link.ItemChannelLinkRegistry;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link TadoStateDescriptionProvider} is responsible for providing a dynamic state description of channels whose
+ * capabilities are not hard coded in the API.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ *
+ */
+@NonNullByDefault
+@Component(service = { DynamicStateDescriptionProvider.class, TadoStateDescriptionProvider.class })
+public class TadoStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
+
+    @Activate
+    public TadoStateDescriptionProvider(final @Reference EventPublisher eventPublisher,
+            final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
+            final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
+        this.eventPublisher = eventPublisher;
+        this.itemChannelLinkRegistry = itemChannelLinkRegistry;
+        this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
+    }
+}
index 1c035f75da46671f2acd39cd3b591fcdb063ab19..e76aa515eadbdb87d5a4ef8a4d6551e9bea6cf40 100644 (file)
@@ -15,24 +15,38 @@ package org.openhab.binding.tado.internal.handler;
 import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.terminationConditionTemplateToTerminationCondition;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import javax.measure.quantity.Temperature;
 
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.tado.internal.TadoBindingConstants;
+import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
+import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
 import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
 import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
+import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing;
 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.TadoApiTypeUtils;
 import org.openhab.binding.tado.internal.api.client.HomeApi;
+import org.openhab.binding.tado.internal.api.model.ACFanLevel;
+import org.openhab.binding.tado.internal.api.model.ACHorizontalSwing;
+import org.openhab.binding.tado.internal.api.model.ACVerticalSwing;
+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.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.TadoSystemType;
 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;
@@ -43,6 +57,7 @@ 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.Channel;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
@@ -51,6 +66,7 @@ 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.openhab.core.types.StateOption;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -64,14 +80,17 @@ import org.slf4j.LoggerFactory;
 public class TadoZoneHandler extends BaseHomeThingHandler {
     private Logger logger = LoggerFactory.getLogger(TadoZoneHandler.class);
 
+    private final TadoStateDescriptionProvider stateDescriptionProvider;
+
     private TadoZoneConfig configuration;
     private ScheduledFuture<?> refreshTimer;
     private ScheduledFuture<?> scheduledHvacChange;
     private GenericZoneCapabilities capabilities;
     TadoHvacChange pendingHvacChange;
 
-    public TadoZoneHandler(Thing thing) {
+    public TadoZoneHandler(Thing thing, TadoStateDescriptionProvider stateDescriptionProvider) {
         super(thing);
+        this.stateDescriptionProvider = stateDescriptionProvider;
     }
 
     public long getZoneId() {
@@ -106,7 +125,8 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
     }
 
     public Overlay setOverlay(Overlay overlay) throws IOException, ApiException {
-        logger.debug("Setting overlay of home {} and zone {}", getHomeId(), getZoneId());
+        logger.debug("Setting overlay of home {} and zone {} with overlay: {}", getHomeId(), getZoneId(),
+                overlay.toString());
         return getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay);
     }
 
@@ -145,10 +165,28 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
                 pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON);
                 scheduleHvacChange();
                 break;
+            case TadoBindingConstants.CHANNEL_ZONE_LIGHT:
+                pendingHvacChange.withLight(((OnOffType) command) == OnOffType.ON);
+                scheduleHvacChange();
+                break;
             case TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED:
                 pendingHvacChange.withFanSpeed(((StringType) command).toFullString());
                 scheduleHvacChange();
                 break;
+            case TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL:
+                String fanLevelString = ((StringType) command).toFullString();
+                pendingHvacChange.withFanLevel(FanLevel.valueOf(fanLevelString.toUpperCase()));
+                break;
+            case TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING:
+                String horizontalSwingString = ((StringType) command).toFullString();
+                pendingHvacChange.withHorizontalSwing(HorizontalSwing.valueOf(horizontalSwingString.toUpperCase()));
+                scheduleHvacChange();
+                break;
+            case TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING:
+                String verticalSwingString = ((StringType) command).toFullString();
+                pendingHvacChange.withVerticalSwing(VerticalSwing.valueOf(verticalSwingString.toUpperCase()));
+                scheduleHvacChange();
+                break;
             case TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE:
                 String operationMode = ((StringType) command).toFullString();
                 pendingHvacChange.withOperationMode(OperationMode.valueOf(operationMode));
@@ -206,6 +244,7 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_NAME, zoneDetails.getName());
                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
                 this.capabilities = capabilities;
+                logger.debug("Got capabilities: {}", capabilities.toString());
             } catch (IOException | ApiException e) {
                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                         "Could not connect to server due to " + e.getMessage());
@@ -256,6 +295,10 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
             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_LIGHT, state.getLight());
+            updateState(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL, state.getFanLevel());
+            updateState(TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING, state.getHorizontalSwing());
+            updateState(TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING, state.getVerticalSwing());
 
             updateState(TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION, state.getRemainingTimerDuration());
 
@@ -263,6 +306,8 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
 
             updateState(TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED, state.getOpenWindowDetected());
 
+            updateDynamicStateDescriptions(zoneState);
+
             onSuccessfulOperation();
         } catch (IOException | ApiException e) {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
@@ -274,6 +319,52 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
         }
     }
 
+    /**
+     * Update the dynamic state descriptions for any channels which support an unknown sub- range of enumerator setting
+     * values, based on the list of capabilities reported by the respective zone.
+     *
+     * Note: currently this only applies to A/C devices that support fanLevel, horizontalSwing, or verticalSwing.
+     *
+     * @param zoneState the current zone Thing's state
+     */
+    private void updateDynamicStateDescriptions(ZoneState zoneState) {
+        GenericZoneSetting setting = zoneState.getSetting();
+        if (setting.getType() != TadoSystemType.AIR_CONDITIONING) {
+            return;
+        }
+
+        AcModeCapabilities acCapabilities = TadoApiTypeUtils.getModeCapabilities(
+                (AirConditioningCapabilities) capabilities, ((CoolingZoneSetting) setting).getMode());
+
+        if (acCapabilities != null) {
+            Channel channel;
+
+            // update the options list of supported fan levels
+            channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL);
+            List<ACFanLevel> fanLevels = acCapabilities.getFanLevel();
+            if (channel != null && fanLevels != null) {
+                stateDescriptionProvider.setStateOptions(channel.getUID(),
+                        fanLevels.stream().map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
+            }
+
+            // update the options list of supported horizontal swing settings
+            channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING);
+            List<ACHorizontalSwing> horizontalSwings = acCapabilities.getHorizontalSwing();
+            if (channel != null && horizontalSwings != null) {
+                stateDescriptionProvider.setStateOptions(channel.getUID(), horizontalSwings.stream()
+                        .map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
+            }
+
+            // update the options list of supported vertical swing settings
+            channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING);
+            List<ACVerticalSwing> verticalSwings = acCapabilities.getVerticalSwing();
+            if (channel != null && verticalSwings != null) {
+                stateDescriptionProvider.setStateOptions(channel.getUID(), verticalSwings.stream()
+                        .map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
+            }
+        }
+    }
+
     private void scheduleZoneStateUpdate() {
         if (refreshTimer == null || refreshTimer.isCancelled()) {
             refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() {
index 887555f05a7ead7c59b5b4035d40792b995717fd..3cf80d0521b50ffa8385c862fe5c243341641e11 100644 (file)
@@ -41,12 +41,24 @@ 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.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.fanLevel.label = Fan Level
+channel-type.tado.fanLevel.description = AC fan speed (only if supported by AC)
+channel-type.tado.fanLevel.state.option.SILENT = Silent fan
+channel-type.tado.fanLevel.state.option.LEVEL1 = Fan level 1
+channel-type.tado.fanLevel.state.option.LEVEL2 = Fan level 2
+channel-type.tado.fanLevel.state.option.LEVEL3 = Fan level 3
+channel-type.tado.fanLevel.state.option.LEVEL4 = Fan level 4
+channel-type.tado.fanLevel.state.option.LEVEL5 = Fan level 5
+channel-type.tado.fanLevel.state.option.AUTO = Auto fan speed
+
 channel-type.tado.heatingPower.label = Heating Power
 channel-type.tado.heatingPower.description = Current heating power
 channel-type.tado.homePresence.label = At Home
@@ -74,6 +86,32 @@ channel-type.tado.overlayExpiry.description = Time until when the overlay is act
 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.light.label = Light
+channel-type.tado.light.description = State of control panel light (only if supported by AC)
+
+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.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
+
 channel-type.tado.targetTemperature.label = Target Temperature
 channel-type.tado.targetTemperature.description = Thermostat temperature setpoint
 channel-type.tado.timerDuration.label = Timer Duration
index d2e17a7fc3d2d1980d325cb6b33c899b062b7159..58c940d21ba3ca8639845505c70fd8389e255405 100644 (file)
                        <channel typeId="targetTemperature" id="targetTemperature"></channel>
                        <channel typeId="fanspeed" id="fanspeed"></channel>
                        <channel typeId="swing" id="swing"></channel>
+                       <channel typeId="light" id="light"></channel>
+                       <channel typeId="fanLevel" id="fanLevel"></channel>
+                       <channel typeId="horizontalSwing" id="horizontalSwing"></channel>
+                       <channel typeId="verticalSwing" id="verticalSwing"></channel>
 
                        <channel typeId="overlayExpiry" id="overlayExpiry"></channel>
                        <channel typeId="timerDuration" id="timerDuration"></channel>
                <description>State of AC swing (only if supported by AC)</description>
        </channel-type>
 
+       <channel-type id="light">
+               <item-type>Switch</item-type>
+               <label>Light</label>
+               <description>State of control panel light (only if supported by AC)</description>
+       </channel-type>
+
+       <channel-type id="fanLevel">
+               <item-type>String</item-type>
+               <label>Fan Speed</label>
+               <description>AC fan level (only if supported by AC)</description>
+               <state readOnly="false">
+                       <options>
+                               <option value="SILENT">SILENT</option>
+                               <option value="LEVEL1">LEVEL1</option>
+                               <option value="LEVEL2">LEVEL2</option>
+                               <option value="LEVEL3">LEVEL3</option>
+                               <option value="LEVEL4">LEVEL4</option>
+                               <option value="LEVEL5">LEVEL5</option>
+                               <option value="AUTO">AUTO</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="horizontalSwing">
+               <item-type>String</item-type>
+               <label>Horizontal Swing</label>
+               <description>State of AC horizontal swing (only if supported by AC)</description>
+               <state readOnly="false">
+                       <options>
+                               <option value="AUTO">AUTO</option>
+                               <option value="LEFT">LEFT</option>
+                               <option value="MID_LEFT">MID-LEFT</option>
+                               <option value="MID">MID</option>
+                               <option value="MID_RIGHT">MID-RIGHT</option>
+                               <option value="RIGHT">RIGHT</option>
+                               <option value="ON">ON</option>
+                               <option value="OFF">OFF</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="verticalSwing">
+               <item-type>String</item-type>
+               <label>Vertical Swing</label>
+               <description>State of AC vertical swing (only if supported by AC)</description>
+               <state readOnly="false">
+                       <options>
+                               <option value="AUTO">AUTO</option>
+                               <option value="UP">UP</option>
+                               <option value="MID_UP">UP</option>
+                               <option value="MID">MID</option>
+                               <option value="MID_DOWN">MID-DOWN</option>
+                               <option value="DOWN">DOWN</option>
+                               <option value="ON">ON</option>
+                               <option value="OFF">OFF</option>
+                       </options>
+               </state>
+       </channel-type>
+
        <channel-type id="operationMode">
                <item-type>String</item-type>
                <label>Zone Operation Mode</label>