diff --git a/CODEOWNERS b/CODEOWNERS
index cf6bc84ef17..b556fa80c41 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -319,7 +319,7 @@
/bundles/org.openhab.binding.synopanalyzer/ @clinique
/bundles/org.openhab.binding.systeminfo/ @svilenvul
/bundles/org.openhab.binding.tacmi/ @twendt @Wolfgang1966 @marvkis
-/bundles/org.openhab.binding.tado/ @dfrommi
+/bundles/org.openhab.binding.tado/ @dfrommi @andrewfg
/bundles/org.openhab.binding.tankerkoenig/ @dolic @JueBag
/bundles/org.openhab.binding.tapocontrol/ @wildcs
/bundles/org.openhab.binding.telegram/ @ZzetT
diff --git a/bundles/org.openhab.binding.tado/README.md b/bundles/org.openhab.binding.tado/README.md
index b33aac7a0a8..2f7a70f1669 100644
--- a/bundles/org.openhab.binding.tado/README.md
+++ b/bundles/org.openhab.binding.tado/README.md
@@ -66,21 +66,23 @@ Name | Type | Description | Read/Write | Zone type
-|-|-|-|-
`currentTemperature` | Number:Temperature | Current inside temperature | R | `HEATING`, `AC`
`humidity` | Number | Current relative inside humidity in percent | R | `HEATING`, `AC`
-`heatingPower` | Number | Amount of heating power currently present | R | `HEATING`
-`acPower` | Switch | Indicates if the Air-Conditioning is Off or On | R | `AC`
`hvacMode` | String | Active mode, one of `OFF`, `HEAT`, `COOL`, `DRY`, `FAN`, `AUTO` | RW | `HEATING` and `DHW` support `OFF` and `HEAT`, `AC` can support more
`targetTemperature` | Number:Temperature | Set point | RW | `HEATING`, `AC`, `DHW`
+`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 | Amount of heating power currently present | R | `HEATING`
+`acPower` | Switch | Indicates if the Air-Conditioning is Off or On | R | `AC`
`fanspeed`1) | String | Fan speed, one of `AUTO`, `LOW`, `MIDDLE`, `HIGH` | RW | `AC`
`fanLevel`1) | String | Fan speed, one of 3) `AUTO`, `SILENT`, `LEVEL1`, `LEVEL2`, `LEVEL3`, `LEVEL4`, `LEVEL5` | RW | `AC`
`swing`2) | Switch | Swing on/off | RW | `AC`
`verticalSwing`2) | String | Vertical swing state, one of 3) `OFF`, `ON`, `UP`, `MID_UP`, `MID`, `MID_DOWN`, `DOWN`, `AUTO` | RW | `AC`
`horizontalSwing`2) | String | Horizontal swing state, one of 3) `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
+`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 | Any Zone
-`light` | Switch | State (`ON`, `OFF`) of the control panel light (if applicable) | RW | `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`).
diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/CapabilitiesSupport.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/CapabilitiesSupport.java
new file mode 100644
index 00000000000..d2e5f823c9c
--- /dev/null
+++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/CapabilitiesSupport.java
@@ -0,0 +1,133 @@
+/**
+ * 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;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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.ControlDevice;
+import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
+import org.openhab.binding.tado.internal.api.model.TadoSystemType;
+import org.openhab.binding.tado.internal.api.model.Zone;
+
+/**
+ * The {@link CapabilitiesSupport} class checks which type of channels are needed in a thing that is to be built around
+ * the given capabilities argument, and the (optional) zone argument. It iterates over each of the capabilities
+ * argument's mode specific sub-capabilities to determine the maximum super set of all sub-capabilities. And it checks
+ * the capabilities of the optional zone argument too.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public class CapabilitiesSupport {
+ private final TadoSystemType type;
+ private boolean light;
+ private boolean swing;
+ private boolean fanLevel;
+ private boolean fanSpeed;
+ private boolean verticalSwing;
+ private boolean horizontalSwing;
+ private boolean batteryLowAlarm;
+
+ public CapabilitiesSupport(GenericZoneCapabilities capabilities, Optional zoneOptional) {
+ type = capabilities.getType();
+
+ if (zoneOptional.isPresent()) {
+ Zone zone = zoneOptional.get();
+ if (zone.getDevices() != null) {
+ batteryLowAlarm = zone.getDevices().stream().map(ControlDevice::getBatteryState)
+ .filter(Objects::nonNull).count() > 0;
+ }
+ }
+
+ if (!(capabilities instanceof AirConditioningCapabilities)) {
+ return;
+ }
+
+ AirConditioningCapabilities acCapabilities = (AirConditioningCapabilities) capabilities;
+
+ // @formatter:off
+ Stream<@Nullable AcModeCapabilities> allCapabilities = Stream.of(
+ acCapabilities.getCOOL(),
+ acCapabilities.getDRY(),
+ acCapabilities.getHEAT(),
+ acCapabilities.getFAN(),
+ acCapabilities.getAUTO());
+ // @formatter:on
+
+ // iterate over all mode capability elements and build the superset of their inner capabilities
+ allCapabilities.forEach(e -> {
+ if (e != null) {
+ light |= e.getLight() != null ? e.getLight().size() > 0 : false;
+ swing |= e.getSwings() != null ? e.getSwings().size() > 0 : false;
+ fanLevel |= e.getFanLevel() != null ? e.getFanLevel().size() > 0 : false;
+ fanSpeed |= e.getFanSpeeds() != null ? e.getFanSpeeds().size() > 0 : false;
+ verticalSwing |= e.getVerticalSwing() != null ? e.getVerticalSwing().size() > 0 : false;
+ horizontalSwing |= e.getHorizontalSwing() != null ? e.getHorizontalSwing().size() > 0 : false;
+ }
+ });
+ }
+
+ public boolean fanLevel() {
+ return fanLevel;
+ }
+
+ public boolean fanSpeed() {
+ return fanSpeed;
+ }
+
+ public boolean horizontalSwing() {
+ return horizontalSwing;
+ }
+
+ public boolean light() {
+ return light;
+ }
+
+ public boolean swing() {
+ return swing;
+ }
+
+ public boolean verticalSwing() {
+ return verticalSwing;
+ }
+
+ public boolean acPower() {
+ return type == TadoSystemType.AIR_CONDITIONING;
+ }
+
+ public boolean heatingPower() {
+ return type == TadoSystemType.HEATING;
+ }
+
+ public boolean currentTemperature() {
+ return (type == TadoSystemType.AIR_CONDITIONING) || (type == TadoSystemType.HEATING);
+ }
+
+ public boolean humidity() {
+ return (type == TadoSystemType.AIR_CONDITIONING) || (type == TadoSystemType.HEATING);
+ }
+
+ public boolean batteryLowAlarm() {
+ return batteryLowAlarm;
+ }
+
+ public boolean openWindow() {
+ return (type == TadoSystemType.AIR_CONDITIONING) || (type == TadoSystemType.HEATING);
+ }
+}
diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoBatteryChecker.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoBatteryChecker.java
index d2edfad2272..23f7fe73f7c 100644
--- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoBatteryChecker.java
+++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoBatteryChecker.java
@@ -13,15 +13,17 @@
package org.openhab.binding.tado.internal.handler;
import java.io.IOException;
-import java.util.Calendar;
-import java.util.Date;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.ControlDevice;
+import org.openhab.binding.tado.internal.api.model.Zone;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
@@ -39,43 +41,46 @@ import org.slf4j.LoggerFactory;
public class TadoBatteryChecker {
private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class);
- private final Map zoneList = new HashMap<>();
private final TadoHomeHandler homeHandler;
-
- private Date refreshTime = new Date();
+ private Map zones = new HashMap<>();
+ private Instant refreshTime = Instant.MIN;
public TadoBatteryChecker(TadoHomeHandler homeHandler) {
this.homeHandler = homeHandler;
}
- private synchronized void refreshZoneList() {
- Date now = new Date();
- if (now.after(refreshTime) || zoneList.isEmpty()) {
- // be frugal, we only need to refresh the battery state hourly
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(now);
- calendar.add(Calendar.HOUR, 1);
- refreshTime = calendar.getTime();
-
- Long homeId = homeHandler.getHomeId();
- if (homeId != null) {
- logger.debug("Fetching (battery state) zone list for HomeId {}", homeId);
- zoneList.clear();
- try {
- homeHandler.getApi().listZones(homeId).forEach(zone -> {
- boolean batteryLow = !zone.getDevices().stream().map(ControlDevice::getBatteryState)
- .filter(Objects::nonNull).allMatch(s -> s.equals("NORMAL"));
- zoneList.put(Long.valueOf(zone.getId()), OnOffType.from(batteryLow));
- });
- } catch (IOException | ApiException e) {
- logger.debug("Fetch (battery state) zone list exception");
- }
+ private void refreshZoneList() {
+ if (refreshTime.isAfter(Instant.now())) {
+ return;
+ }
+ // only refresh the battery state hourly
+ refreshTime = Instant.now().plus(1, ChronoUnit.HOURS);
+ Long homeId = homeHandler.getHomeId();
+ if (homeId != null) {
+ logger.debug("Fetching (battery state) zone list for HomeId {}", homeId);
+ try {
+ Map zones = new HashMap<>();
+ homeHandler.getApi().listZones(homeId).stream().filter(Objects::nonNull)
+ .forEach(zone -> zones.put((long) zone.getId(), zone));
+ this.zones = zones;
+ } catch (IOException | ApiException e) {
+ logger.debug("Fetch (battery state) zone list exception");
}
}
}
- public State getBatteryLowAlarm(long zoneId) {
+ public synchronized Optional getZone(long zoneId) {
refreshZoneList();
- return zoneList.getOrDefault(zoneId, UnDefType.UNDEF);
+ return Optional.ofNullable(zones.get(zoneId));
+ }
+
+ public State getBatteryLowAlarm(long zoneId) {
+ Optional zone = getZone(zoneId);
+ if (zone.isPresent()) {
+ boolean batteryOk = zone.get().getDevices().stream().map(ControlDevice::getBatteryState)
+ .filter(Objects::nonNull).allMatch(batteryState -> "NORMAL".equals(batteryState));
+ return OnOffType.from(!batteryOk);
+ }
+ return UnDefType.UNDEF;
}
}
diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHomeHandler.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHomeHandler.java
index f5e5bf1b5da..65fa4c076ac 100644
--- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHomeHandler.java
+++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHomeHandler.java
@@ -41,8 +41,6 @@ import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,7 +58,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
private final HomeApi api;
private @Nullable Long homeId;
- private @Nullable TadoBatteryChecker batteryChecker;
+ private final TadoBatteryChecker batteryChecker;
private @Nullable ScheduledFuture> initializationFuture;
public TadoHomeHandler(Bridge bridge) {
@@ -194,8 +192,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
}
}
- public State getBatteryLowAlarm(long zoneId) {
- TadoBatteryChecker batteryChecker = this.batteryChecker;
- return batteryChecker != null ? batteryChecker.getBatteryLowAlarm(zoneId) : UnDefType.UNDEF;
+ public TadoBatteryChecker getBatteryChecker() {
+ return this.batteryChecker;
}
}
diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoZoneHandler.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoZoneHandler.java
index ce30cb37d6e..5d830d76952 100644
--- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoZoneHandler.java
+++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoZoneHandler.java
@@ -15,7 +15,9 @@ package org.openhab.binding.tado.internal.handler;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.terminationConditionTemplateToTerminationCondition;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
+import java.util.StringJoiner;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -24,6 +26,7 @@ import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.tado.internal.CapabilitiesSupport;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
@@ -281,7 +284,13 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
updateProperty(TadoBindingConstants.PROPERTY_ZONE_NAME, zoneDetails.getName());
updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
+
this.capabilities = capabilities;
+
+ CapabilitiesSupport capabilitiesSupport = new CapabilitiesSupport(capabilities,
+ getHomeHandler().getBatteryChecker().getZone(getZoneId()));
+
+ updateDynamicChannels(capabilitiesSupport);
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
@@ -350,7 +359,7 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
}
updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM,
- getHomeHandler().getBatteryLowAlarm(getZoneId()));
+ getHomeHandler().getBatteryChecker().getBatteryLowAlarm(getZoneId()));
}
/**
@@ -474,4 +483,62 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
}
return gson.toJson(object);
}
+
+ /**
+ * If the given channel exists in the thing, but is NOT required in the thing, then add it to a list of channels to
+ * be removed. Or if the channel does NOT exist in the thing, but is required in the thing, then log a warning.
+ *
+ * @param removeList the list of channels to be removed from the thing.
+ * @param channelId the id of the channel to be (eventually) removed.
+ * @param channelRequired true if the thing requires this channel.
+ */
+ private void removeListProcessChannel(List removeList, String channelId, boolean channelRequired) {
+ Channel channel = thing.getChannel(channelId);
+ if (!channelRequired && channel != null) {
+ removeList.add(channel);
+ } else if (channelRequired && channel == null) {
+ logger.warn("Thing {} does not have a '{}' channel => please reinitialize it", thing.getUID(), channelId);
+ }
+ }
+
+ /**
+ * Remove previously statically created channels if the device does not support them.
+ *
+ * @param capabilitiesSupport a CapabilitiesSupport instance which summarizes the device's capabilities.
+ * @throws IllegalStateException if any of the channel builders failed.
+ */
+ private void updateDynamicChannels(CapabilitiesSupport capabilitiesSupport) {
+ List removeList = new ArrayList<>();
+
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM,
+ capabilitiesSupport.batteryLowAlarm());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED,
+ capabilitiesSupport.openWindow());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_LIGHT, capabilitiesSupport.light());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING,
+ capabilitiesSupport.horizontalSwing());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING,
+ capabilitiesSupport.verticalSwing());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_SWING, capabilitiesSupport.swing());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED,
+ capabilitiesSupport.fanSpeed());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL,
+ capabilitiesSupport.fanLevel());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_AC_POWER, capabilitiesSupport.acPower());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER,
+ capabilitiesSupport.heatingPower());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_HUMIDITY,
+ capabilitiesSupport.humidity());
+ removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE,
+ capabilitiesSupport.currentTemperature());
+
+ if (!removeList.isEmpty()) {
+ if (logger.isDebugEnabled()) {
+ StringJoiner joiner = new StringJoiner(", ");
+ removeList.forEach(c -> joiner.add(c.getUID().getId()));
+ logger.debug("Removing unsupported channels for {}: {}", thing.getUID(), joiner.toString());
+ }
+ updateThing(editThing().withoutChannels(removeList).build());
+ }
+ }
}
diff --git a/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/i18n/tado.properties b/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/i18n/tado.properties
index 3cf80d0521b..f79431c95d6 100644
--- a/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/i18n/tado.properties
+++ b/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/i18n/tado.properties
@@ -35,8 +35,8 @@ thing-type.config.tado.zone.refreshInterval.description = Refresh interval of ho
# channel types
-channel-type.tado.acPower.label = AirCon Power State
-channel-type.tado.acPower.description = Indicates if the air-conditioning is Off or On
+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
diff --git a/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/thing/thing-types.xml
index 58c940d21ba..e1dba5d2f5c 100644
--- a/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.tado/src/main/resources/OH-INF/thing/thing-types.xml
@@ -38,30 +38,24 @@
-
-
-
-
+
-
-
+
-
+
+
+
+
-
-
-
+ ON if one or more devices in the zone have a low battery
-
-
-
@@ -152,6 +146,7 @@
NumberCurrent heating power
+ Fire
@@ -183,6 +178,7 @@
StringAC fan speed (only if supported by AC)
+ Fan
@@ -197,18 +193,21 @@
SwitchState of AC swing (only if supported by AC)
+ FlowSwitchState of control panel light (only if supported by AC)
+ LightStringAC fan level (only if supported by AC)
+ Fan
@@ -226,6 +225,7 @@
StringState of AC horizontal swing (only if supported by AC)
+ Flow
@@ -244,6 +244,7 @@
StringState of AC vertical swing (only if supported by AC)
+ Flow
@@ -276,6 +277,7 @@
NumberTotal duration of a timer
+ Time
@@ -283,6 +285,7 @@
DateTimeTime until when the overlay is active. Null if no overlay is set or overlay type is manual.
+ Time
@@ -294,8 +297,9 @@
Switch
-
- Indicates if the air-conditioning is Off or On
+
+ Current power state of the air-conditioning
+ Climate
@@ -303,7 +307,7 @@
SwitchIndicates if an open window has been detected
- window
+ Window
diff --git a/bundles/org.openhab.binding.tado/src/test/java/org/openhab/binding/tado/tests/CapabilitiesSupportTest.java b/bundles/org.openhab.binding.tado/src/test/java/org/openhab/binding/tado/tests/CapabilitiesSupportTest.java
new file mode 100644
index 00000000000..020d2bb69b3
--- /dev/null
+++ b/bundles/org.openhab.binding.tado/src/test/java/org/openhab/binding/tado/tests/CapabilitiesSupportTest.java
@@ -0,0 +1,141 @@
+/**
+ * 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.tests;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.tado.internal.CapabilitiesSupport;
+import org.openhab.binding.tado.internal.api.model.ACFanLevel;
+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.AcModeCapabilities;
+import org.openhab.binding.tado.internal.api.model.AirConditioningCapabilities;
+import org.openhab.binding.tado.internal.api.model.ControlDevice;
+import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
+import org.openhab.binding.tado.internal.api.model.Power;
+import org.openhab.binding.tado.internal.api.model.TadoSystemType;
+import org.openhab.binding.tado.internal.api.model.Zone;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link CapabilitiesSupportTest} implements tests of the capabilities support evaluator.
+ *
+ * @author Andrew Fiddian-Green - Initial contributions
+ *
+ */
+@NonNullByDefault
+public class CapabilitiesSupportTest {
+
+ /**
+ * Test capabilities support (heating)
+ */
+ @Test
+ void testCapabilitiesSupportHeating() {
+ GenericZoneCapabilities caps = new GenericZoneCapabilities();
+ caps.setType(TadoSystemType.HEATING);
+
+ CapabilitiesSupport capabilitiesSupport = new CapabilitiesSupport(caps, Optional.empty());
+
+ assertTrue(capabilitiesSupport.heatingPower());
+
+ assertFalse(capabilitiesSupport.fanLevel());
+ assertFalse(capabilitiesSupport.fanSpeed());
+ assertFalse(capabilitiesSupport.horizontalSwing());
+ assertFalse(capabilitiesSupport.light());
+ assertFalse(capabilitiesSupport.swing());
+ assertFalse(capabilitiesSupport.verticalSwing());
+ assertFalse(capabilitiesSupport.acPower());
+ }
+
+ /**
+ * Test capabilities support (air conditioning)
+ */
+ @Test
+ void testCapabilitiesSupportAirContitioning() {
+ AirConditioningCapabilities caps = new AirConditioningCapabilities();
+ caps.setType(TadoSystemType.AIR_CONDITIONING);
+
+ AcModeCapabilities heat = new AcModeCapabilities();
+ heat.addFanLevelItem(ACFanLevel.LEVEL1);
+ heat.addSwingsItem(Power.OFF);
+ caps.HEAT(heat);
+
+ AcModeCapabilities cool = new AcModeCapabilities();
+ cool.addFanSpeedsItem(AcFanSpeed.AUTO);
+ cool.addVerticalSwingItem(ACVerticalSwing.DOWN);
+ caps.COOL(cool);
+
+ CapabilitiesSupport capabilitiesSupport = new CapabilitiesSupport(caps, Optional.empty());
+
+ assertTrue(capabilitiesSupport.fanLevel());
+ assertTrue(capabilitiesSupport.verticalSwing());
+ assertTrue(capabilitiesSupport.acPower());
+ assertTrue(capabilitiesSupport.fanSpeed());
+ assertTrue(capabilitiesSupport.swing());
+
+ assertFalse(capabilitiesSupport.horizontalSwing());
+ assertFalse(capabilitiesSupport.light());
+ assertFalse(capabilitiesSupport.heatingPower());
+ }
+
+ /**
+ * Test capabilities support (battery)
+ */
+ @Test
+ void testCapabilitiesBattery() {
+ CapabilitiesSupport capabilitiesSupport;
+ GenericZoneCapabilities caps = new GenericZoneCapabilities();
+ caps.setType(TadoSystemType.HEATING);
+
+ String jsonWithBattery = "{\"deviceType\": \"abc\", \"serialNo\": \"123\", \"batteryState\": \"NORMAL\"}";
+ String jsonNoBattery = "{\"deviceType\": \"xyz\", \"serialNo\": \"456\"}";
+
+ Gson gson = new Gson();
+
+ Zone zone = new Zone();
+ Optional optionalZone = Optional.of(zone);
+
+ // null devices list
+ capabilitiesSupport = new CapabilitiesSupport(caps, optionalZone);
+ assertFalse(capabilitiesSupport.batteryLowAlarm());
+
+ // empty devices list
+ zone.devices(new ArrayList<>());
+ capabilitiesSupport = new CapabilitiesSupport(caps, optionalZone);
+ assertFalse(capabilitiesSupport.batteryLowAlarm());
+
+ // list of non battery devices
+ zone.addDevicesItem(gson.fromJson(jsonNoBattery, ControlDevice.class));
+ zone.addDevicesItem(gson.fromJson(jsonNoBattery, ControlDevice.class));
+ zone.addDevicesItem(gson.fromJson(jsonNoBattery, ControlDevice.class));
+
+ capabilitiesSupport = new CapabilitiesSupport(caps, optionalZone);
+ assertFalse(capabilitiesSupport.batteryLowAlarm());
+
+ // at least one battery device in list
+ zone.addDevicesItem(gson.fromJson(jsonWithBattery, ControlDevice.class));
+
+ capabilitiesSupport = new CapabilitiesSupport(caps, optionalZone);
+ assertTrue(capabilitiesSupport.batteryLowAlarm());
+
+ // empty optional
+ capabilitiesSupport = new CapabilitiesSupport(caps, Optional.empty());
+ assertFalse(capabilitiesSupport.batteryLowAlarm());
+ }
+}