mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[tado] Channels are visible depending on device type and capabilities (#13301)
* [tado] create zone channels dynamically * [tado] add JUnit test classes * [tado] eliminate maven compiler warnings * [tado] code optimisation * [tado] read me * [tado] current temperature & humidity also dynamic * [tado] battery/window channels * [tado] simplify channel builder * [tado] fix bundle initialisation bug * [tado] fix insert positions * [tado] add channel type categories * [tado] refactor battery checker * [tado] create capabilities support in caller vs callee * [tado] method doesn't throw exception * [tado] remove new line * [velux] add self to code owners * [tado] capitalize categories Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
8d9dfb20e3
commit
5c7eaa33ac
@ -319,7 +319,7 @@
|
|||||||
/bundles/org.openhab.binding.synopanalyzer/ @clinique
|
/bundles/org.openhab.binding.synopanalyzer/ @clinique
|
||||||
/bundles/org.openhab.binding.systeminfo/ @svilenvul
|
/bundles/org.openhab.binding.systeminfo/ @svilenvul
|
||||||
/bundles/org.openhab.binding.tacmi/ @twendt @Wolfgang1966 @marvkis
|
/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.tankerkoenig/ @dolic @JueBag
|
||||||
/bundles/org.openhab.binding.tapocontrol/ @wildcs
|
/bundles/org.openhab.binding.tapocontrol/ @wildcs
|
||||||
/bundles/org.openhab.binding.telegram/ @ZzetT
|
/bundles/org.openhab.binding.telegram/ @ZzetT
|
||||||
|
@ -66,21 +66,23 @@ Name | Type | Description | Read/Write | Zone type
|
|||||||
-|-|-|-|-
|
-|-|-|-|-
|
||||||
`currentTemperature` | Number:Temperature | Current inside temperature | R | `HEATING`, `AC`
|
`currentTemperature` | Number:Temperature | Current inside temperature | R | `HEATING`, `AC`
|
||||||
`humidity` | Number | Current relative inside humidity in percent | 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
|
`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`
|
`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`<sup>1)</sup> | String | Fan speed, one of `AUTO`, `LOW`, `MIDDLE`, `HIGH` | 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`
|
`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`
|
`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`
|
`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`
|
`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`
|
`batteryLowAlarm` | Switch | A control device in the Zone has a low battery | R | Any Zone
|
||||||
`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
|
`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`).
|
The `RW` items are used to either override the schedule or to return to it (if `hvacMode` is set to `SCHEDULE`).
|
||||||
|
|
||||||
|
@ -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<Zone> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -13,15 +13,17 @@
|
|||||||
package org.openhab.binding.tado.internal.handler;
|
package org.openhab.binding.tado.internal.handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Calendar;
|
import java.time.Instant;
|
||||||
import java.util.Date;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.tado.internal.api.ApiException;
|
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.ControlDevice;
|
||||||
|
import org.openhab.binding.tado.internal.api.model.Zone;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
import org.openhab.core.types.UnDefType;
|
import org.openhab.core.types.UnDefType;
|
||||||
@ -39,43 +41,46 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class TadoBatteryChecker {
|
public class TadoBatteryChecker {
|
||||||
private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class);
|
private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class);
|
||||||
|
|
||||||
private final Map<Long, State> zoneList = new HashMap<>();
|
|
||||||
private final TadoHomeHandler homeHandler;
|
private final TadoHomeHandler homeHandler;
|
||||||
|
private Map<Long, Zone> zones = new HashMap<>();
|
||||||
private Date refreshTime = new Date();
|
private Instant refreshTime = Instant.MIN;
|
||||||
|
|
||||||
public TadoBatteryChecker(TadoHomeHandler homeHandler) {
|
public TadoBatteryChecker(TadoHomeHandler homeHandler) {
|
||||||
this.homeHandler = homeHandler;
|
this.homeHandler = homeHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void refreshZoneList() {
|
private void refreshZoneList() {
|
||||||
Date now = new Date();
|
if (refreshTime.isAfter(Instant.now())) {
|
||||||
if (now.after(refreshTime) || zoneList.isEmpty()) {
|
return;
|
||||||
// be frugal, we only need to refresh the battery state hourly
|
}
|
||||||
Calendar calendar = Calendar.getInstance();
|
// only refresh the battery state hourly
|
||||||
calendar.setTime(now);
|
refreshTime = Instant.now().plus(1, ChronoUnit.HOURS);
|
||||||
calendar.add(Calendar.HOUR, 1);
|
|
||||||
refreshTime = calendar.getTime();
|
|
||||||
|
|
||||||
Long homeId = homeHandler.getHomeId();
|
Long homeId = homeHandler.getHomeId();
|
||||||
if (homeId != null) {
|
if (homeId != null) {
|
||||||
logger.debug("Fetching (battery state) zone list for HomeId {}", homeId);
|
logger.debug("Fetching (battery state) zone list for HomeId {}", homeId);
|
||||||
zoneList.clear();
|
|
||||||
try {
|
try {
|
||||||
homeHandler.getApi().listZones(homeId).forEach(zone -> {
|
Map<Long, Zone> zones = new HashMap<>();
|
||||||
boolean batteryLow = !zone.getDevices().stream().map(ControlDevice::getBatteryState)
|
homeHandler.getApi().listZones(homeId).stream().filter(Objects::nonNull)
|
||||||
.filter(Objects::nonNull).allMatch(s -> s.equals("NORMAL"));
|
.forEach(zone -> zones.put((long) zone.getId(), zone));
|
||||||
zoneList.put(Long.valueOf(zone.getId()), OnOffType.from(batteryLow));
|
this.zones = zones;
|
||||||
});
|
|
||||||
} catch (IOException | ApiException e) {
|
} catch (IOException | ApiException e) {
|
||||||
logger.debug("Fetch (battery state) zone list exception");
|
logger.debug("Fetch (battery state) zone list exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized Optional<Zone> getZone(long zoneId) {
|
||||||
|
refreshZoneList();
|
||||||
|
return Optional.ofNullable(zones.get(zoneId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public State getBatteryLowAlarm(long zoneId) {
|
public State getBatteryLowAlarm(long zoneId) {
|
||||||
refreshZoneList();
|
Optional<Zone> zone = getZone(zoneId);
|
||||||
return zoneList.getOrDefault(zoneId, UnDefType.UNDEF);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,6 @@ import org.openhab.core.thing.binding.BaseBridgeHandler;
|
|||||||
import org.openhab.core.thing.binding.ThingHandler;
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -60,7 +58,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
|||||||
private final HomeApi api;
|
private final HomeApi api;
|
||||||
|
|
||||||
private @Nullable Long homeId;
|
private @Nullable Long homeId;
|
||||||
private @Nullable TadoBatteryChecker batteryChecker;
|
private final TadoBatteryChecker batteryChecker;
|
||||||
private @Nullable ScheduledFuture<?> initializationFuture;
|
private @Nullable ScheduledFuture<?> initializationFuture;
|
||||||
|
|
||||||
public TadoHomeHandler(Bridge bridge) {
|
public TadoHomeHandler(Bridge bridge) {
|
||||||
@ -194,8 +192,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public State getBatteryLowAlarm(long zoneId) {
|
public TadoBatteryChecker getBatteryChecker() {
|
||||||
TadoBatteryChecker batteryChecker = this.batteryChecker;
|
return this.batteryChecker;
|
||||||
return batteryChecker != null ? batteryChecker.getBatteryLowAlarm(zoneId) : UnDefType.UNDEF;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@ package org.openhab.binding.tado.internal.handler;
|
|||||||
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.terminationConditionTemplateToTerminationCondition;
|
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.terminationConditionTemplateToTerminationCondition;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.StringJoiner;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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;
|
||||||
import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
|
import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
|
||||||
import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
|
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_NAME, zoneDetails.getName());
|
||||||
updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
|
updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
|
||||||
|
|
||||||
this.capabilities = capabilities;
|
this.capabilities = capabilities;
|
||||||
|
|
||||||
|
CapabilitiesSupport capabilitiesSupport = new CapabilitiesSupport(capabilities,
|
||||||
|
getHomeHandler().getBatteryChecker().getZone(getZoneId()));
|
||||||
|
|
||||||
|
updateDynamicChannels(capabilitiesSupport);
|
||||||
} catch (IOException | ApiException e) {
|
} catch (IOException | ApiException e) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
"Could not connect to server due to " + e.getMessage());
|
"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,
|
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);
|
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<Channel> 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<Channel> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ thing-type.config.tado.zone.refreshInterval.description = Refresh interval of ho
|
|||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
channel-type.tado.acPower.label = AirCon Power State
|
channel-type.tado.acPower.label = Air-conditioning Power
|
||||||
channel-type.tado.acPower.description = Indicates if the air-conditioning is Off or On
|
channel-type.tado.acPower.description = Current power state of the air-conditioning
|
||||||
channel-type.tado.atHome.label = At Home
|
channel-type.tado.atHome.label = At Home
|
||||||
channel-type.tado.atHome.description = ON if at home, OFF if away
|
channel-type.tado.atHome.description = ON if at home, OFF if away
|
||||||
channel-type.tado.currentTemperature.label = Temperature
|
channel-type.tado.currentTemperature.label = Temperature
|
||||||
|
@ -38,30 +38,24 @@
|
|||||||
<channels>
|
<channels>
|
||||||
<channel typeId="currentTemperature" id="currentTemperature"></channel>
|
<channel typeId="currentTemperature" id="currentTemperature"></channel>
|
||||||
<channel typeId="humidity" id="humidity"></channel>
|
<channel typeId="humidity" id="humidity"></channel>
|
||||||
|
|
||||||
<channel typeId="heatingPower" id="heatingPower"></channel>
|
<channel typeId="heatingPower" id="heatingPower"></channel>
|
||||||
|
<channel typeId="acPower" id="acPower"></channel>
|
||||||
<channel typeId="hvacMode" id="hvacMode"></channel>
|
|
||||||
<channel typeId="targetTemperature" id="targetTemperature"></channel>
|
|
||||||
<channel typeId="fanspeed" id="fanspeed"></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="fanLevel" id="fanLevel"></channel>
|
||||||
|
<channel typeId="swing" id="swing"></channel>
|
||||||
<channel typeId="horizontalSwing" id="horizontalSwing"></channel>
|
<channel typeId="horizontalSwing" id="horizontalSwing"></channel>
|
||||||
<channel typeId="verticalSwing" id="verticalSwing"></channel>
|
<channel typeId="verticalSwing" id="verticalSwing"></channel>
|
||||||
|
<channel typeId="light" id="light"></channel>
|
||||||
|
<channel typeId="hvacMode" id="hvacMode"></channel>
|
||||||
|
<channel typeId="targetTemperature" id="targetTemperature"></channel>
|
||||||
|
<channel typeId="operationMode" id="operationMode"></channel>
|
||||||
<channel typeId="overlayExpiry" id="overlayExpiry"></channel>
|
<channel typeId="overlayExpiry" id="overlayExpiry"></channel>
|
||||||
<channel typeId="timerDuration" id="timerDuration"></channel>
|
<channel typeId="timerDuration" id="timerDuration"></channel>
|
||||||
|
<channel typeId="openWindowDetected" id="openWindowDetected"></channel>
|
||||||
<channel typeId="operationMode" id="operationMode"></channel>
|
|
||||||
|
|
||||||
<channel typeId="system.low-battery" id="batteryLowAlarm">
|
<channel typeId="system.low-battery" id="batteryLowAlarm">
|
||||||
<label>Battery Low Alarm</label>
|
<label>Battery Low Alarm</label>
|
||||||
<description>ON if one or more devices in the zone have a low battery</description>
|
<description>ON if one or more devices in the zone have a low battery</description>
|
||||||
</channel>
|
</channel>
|
||||||
|
|
||||||
<channel typeId="acPower" id="acPower"></channel>
|
|
||||||
<channel typeId="openWindowDetected" id="openWindowDetected"></channel>
|
|
||||||
</channels>
|
</channels>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@ -152,6 +146,7 @@
|
|||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Heating Power</label>
|
<label>Heating Power</label>
|
||||||
<description>Current heating power</description>
|
<description>Current heating power</description>
|
||||||
|
<category>Fire</category>
|
||||||
<state readOnly="true" pattern="%.0f %%"></state>
|
<state readOnly="true" pattern="%.0f %%"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
@ -183,6 +178,7 @@
|
|||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Fan Speed</label>
|
<label>Fan Speed</label>
|
||||||
<description>AC fan speed (only if supported by AC)</description>
|
<description>AC fan speed (only if supported by AC)</description>
|
||||||
|
<category>Fan</category>
|
||||||
<state readOnly="false">
|
<state readOnly="false">
|
||||||
<options>
|
<options>
|
||||||
<option value="LOW">Low</option>
|
<option value="LOW">Low</option>
|
||||||
@ -197,18 +193,21 @@
|
|||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>Swing</label>
|
<label>Swing</label>
|
||||||
<description>State of AC swing (only if supported by AC)</description>
|
<description>State of AC swing (only if supported by AC)</description>
|
||||||
|
<category>Flow</category>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="light">
|
<channel-type id="light">
|
||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>Light</label>
|
<label>Light</label>
|
||||||
<description>State of control panel light (only if supported by AC)</description>
|
<description>State of control panel light (only if supported by AC)</description>
|
||||||
|
<category>Light</category>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="fanLevel">
|
<channel-type id="fanLevel">
|
||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Fan Speed</label>
|
<label>Fan Speed</label>
|
||||||
<description>AC fan level (only if supported by AC)</description>
|
<description>AC fan level (only if supported by AC)</description>
|
||||||
|
<category>Fan</category>
|
||||||
<state readOnly="false">
|
<state readOnly="false">
|
||||||
<options>
|
<options>
|
||||||
<option value="SILENT">SILENT</option>
|
<option value="SILENT">SILENT</option>
|
||||||
@ -226,6 +225,7 @@
|
|||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Horizontal Swing</label>
|
<label>Horizontal Swing</label>
|
||||||
<description>State of AC horizontal swing (only if supported by AC)</description>
|
<description>State of AC horizontal swing (only if supported by AC)</description>
|
||||||
|
<category>Flow</category>
|
||||||
<state readOnly="false">
|
<state readOnly="false">
|
||||||
<options>
|
<options>
|
||||||
<option value="AUTO">AUTO</option>
|
<option value="AUTO">AUTO</option>
|
||||||
@ -244,6 +244,7 @@
|
|||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Vertical Swing</label>
|
<label>Vertical Swing</label>
|
||||||
<description>State of AC vertical swing (only if supported by AC)</description>
|
<description>State of AC vertical swing (only if supported by AC)</description>
|
||||||
|
<category>Flow</category>
|
||||||
<state readOnly="false">
|
<state readOnly="false">
|
||||||
<options>
|
<options>
|
||||||
<option value="AUTO">AUTO</option>
|
<option value="AUTO">AUTO</option>
|
||||||
@ -276,6 +277,7 @@
|
|||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Timer Duration</label>
|
<label>Timer Duration</label>
|
||||||
<description>Total duration of a timer</description>
|
<description>Total duration of a timer</description>
|
||||||
|
<category>Time</category>
|
||||||
<state min="0" step="1" pattern="%d min" readOnly="false"></state>
|
<state min="0" step="1" pattern="%d min" readOnly="false"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
@ -283,6 +285,7 @@
|
|||||||
<item-type>DateTime</item-type>
|
<item-type>DateTime</item-type>
|
||||||
<label>Overlay End Time</label>
|
<label>Overlay End Time</label>
|
||||||
<description>Time until when the overlay is active. Null if no overlay is set or overlay type is manual.</description>
|
<description>Time until when the overlay is active. Null if no overlay is set or overlay type is manual.</description>
|
||||||
|
<category>Time</category>
|
||||||
<state readOnly="true" pattern="%1$tF %1$tR"/>
|
<state readOnly="true" pattern="%1$tF %1$tR"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
@ -294,8 +297,9 @@
|
|||||||
|
|
||||||
<channel-type id="acPower">
|
<channel-type id="acPower">
|
||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>AirCon Power State</label>
|
<label>Air-conditioning Power</label>
|
||||||
<description>Indicates if the air-conditioning is Off or On</description>
|
<description>Current power state of the air-conditioning</description>
|
||||||
|
<category>Climate</category>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
@ -303,7 +307,7 @@
|
|||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>Open Window Detected</label>
|
<label>Open Window Detected</label>
|
||||||
<description>Indicates if an open window has been detected</description>
|
<description>Indicates if an open window has been detected</description>
|
||||||
<category>window</category>
|
<category>Window</category>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
@ -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<Zone> 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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user