diff --git a/bundles/org.openhab.binding.tado/src/main/api/tado-api.yaml b/bundles/org.openhab.binding.tado/src/main/api/tado-api.yaml index 05226ae6ce7..d45f4519fc5 100644 --- a/bundles/org.openhab.binding.tado/src/main/api/tado-api.yaml +++ b/bundles/org.openhab.binding.tado/src/main/api/tado-api.yaml @@ -1050,16 +1050,16 @@ definitions: 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" + horizontalSwing: + description: Cooling system horizontal swing modes. (Tado confusingly names this array without an 's') + type: array + items: + $ref: "#/definitions/ACHorizontalSwing" HeatingCapabilities: x-discriminator-value: HEATING diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/TadoHvacChange.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/TadoHvacChange.java index de47df19fcb..42b1a08c03f 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/TadoHvacChange.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/TadoHvacChange.java @@ -14,6 +14,7 @@ package org.openhab.binding.tado.internal; import java.io.IOException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; @@ -30,27 +31,30 @@ import org.openhab.binding.tado.internal.builder.ZoneSettingsBuilder; import org.openhab.binding.tado.internal.builder.ZoneStateProvider; import org.openhab.binding.tado.internal.handler.TadoZoneHandler; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingHandler; /** * Builder for incremental creation of zone overlays. * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoHvacChange { - private TadoZoneHandler zoneHandler; + + private final TadoZoneHandler zoneHandler; + private final TerminationConditionBuilder terminationConditionBuilder; + private final ZoneSettingsBuilder settingsBuilder; private boolean followSchedule = false; - private TerminationConditionBuilder terminationConditionBuilder; - private ZoneSettingsBuilder settingsBuilder; public TadoHvacChange(Thing zoneThing) { - if (!(zoneThing.getHandler() instanceof TadoZoneHandler)) { + ThingHandler handler = zoneThing.getHandler(); + if (!(handler instanceof TadoZoneHandler)) { throw new IllegalArgumentException("TadoZoneThing expected, but instead got " + zoneThing); } - - this.zoneHandler = (TadoZoneHandler) zoneThing.getHandler(); - this.terminationConditionBuilder = TerminationConditionBuilder.of(zoneHandler); - this.settingsBuilder = ZoneSettingsBuilder.of(zoneHandler); + zoneHandler = (TadoZoneHandler) handler; + terminationConditionBuilder = TerminationConditionBuilder.of(zoneHandler); + settingsBuilder = ZoneSettingsBuilder.of(zoneHandler); } public TadoHvacChange withOperationMode(OperationMode operationMode) { @@ -60,12 +64,11 @@ public class TadoHvacChange { case MANUAL: return activeForever(); case TIMER: - return activeFor(null); + return activeForMinutes(0); case UNTIL_CHANGE: return activeUntilChange(); - default: - return this; } + return this; } public TadoHvacChange followSchedule() { @@ -83,9 +86,9 @@ public class TadoHvacChange { return this; } - public TadoHvacChange activeFor(Integer minutes) { + public TadoHvacChange activeForMinutes(int minutes) { terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.TIMER); - terminationConditionBuilder.withTimerDurationInSeconds(minutes != null ? minutes * 60 : null); + terminationConditionBuilder.withTimerDurationInSeconds(minutes * 60); return this; } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/adapter/TadoZoneStateAdapter.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/adapter/TadoZoneStateAdapter.java index 4e80dc9f08a..ed33bac2007 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/adapter/TadoZoneStateAdapter.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/adapter/TadoZoneStateAdapter.java @@ -16,6 +16,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.time.OffsetDateTime; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode; import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode; import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit; @@ -31,6 +32,7 @@ import org.openhab.binding.tado.internal.api.model.HeatingZoneSetting; import org.openhab.binding.tado.internal.api.model.HotWaterZoneSetting; import org.openhab.binding.tado.internal.api.model.Overlay; import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType; +import org.openhab.binding.tado.internal.api.model.PercentageDataPoint; import org.openhab.binding.tado.internal.api.model.Power; import org.openhab.binding.tado.internal.api.model.SensorDataPoints; import org.openhab.binding.tado.internal.api.model.TadoSystemType; @@ -55,9 +57,10 @@ import org.openhab.core.types.UnDefType; * @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels * */ +@NonNullByDefault public class TadoZoneStateAdapter { - private ZoneState zoneState; - private TemperatureUnit temperatureUnit; + private final ZoneState zoneState; + private final TemperatureUnit temperatureUnit; public TadoZoneStateAdapter(ZoneState zoneState, TemperatureUnit temperatureUnit) { this.zoneState = zoneState; @@ -69,10 +72,9 @@ public class TadoZoneStateAdapter { return toTemperatureState(sensorDataPoints.getInsideTemperature(), temperatureUnit); } - public DecimalType getHumidity() { - SensorDataPoints sensorDataPoints = zoneState.getSensorDataPoints(); - return sensorDataPoints.getHumidity() != null ? toDecimalType(sensorDataPoints.getHumidity().getPercentage()) - : null; + public State getHumidity() { + PercentageDataPoint humidity = zoneState.getSensorDataPoints().getHumidity(); + return humidity != null ? toDecimalType(humidity.getPercentage()) : UnDefType.UNDEF; } public DecimalType getHeatingPower() { @@ -81,7 +83,7 @@ public class TadoZoneStateAdapter { : DecimalType.ZERO; } - public OnOffType getAcPower() { + public State getAcPower() { ActivityDataPoints dataPoints = zoneState.getActivityDataPoints(); AcPowerDataPoint acPower = dataPoints.getAcPower(); if (acPower != null) { @@ -90,7 +92,7 @@ public class TadoZoneStateAdapter { return OnOffType.from(acPowerValue); } } - return null; + return UnDefType.UNDEF; } public StringType getMode() { @@ -106,6 +108,9 @@ public class TadoZoneStateAdapter { } public State getTargetTemperature() { + if (!isPowerOn()) { + return UnDefType.UNDEF; + } switch (zoneState.getSetting().getType()) { case HEATING: return toTemperatureState(((HeatingZoneSetting) zoneState.getSetting()).getTemperature(), @@ -228,20 +233,12 @@ public class TadoZoneStateAdapter { } private static State toTemperatureState(TemperatureObject temperature, TemperatureUnit temperatureUnit) { - if (temperature == null) { - return UnDefType.NULL; - } - return temperatureUnit == TemperatureUnit.FAHRENHEIT ? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT) : new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS); } private static State toTemperatureState(TemperatureDataPoint temperature, TemperatureUnit temperatureUnit) { - if (temperature == null) { - return UnDefType.NULL; - } - return temperatureUnit == TemperatureUnit.FAHRENHEIT ? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT) : new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS); diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/HomeApiFactory.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/HomeApiFactory.java index 75a38dbfb53..adc8dc4cfc1 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/HomeApiFactory.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/HomeApiFactory.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.tado.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.tado.internal.api.auth.Authorizer; import org.openhab.binding.tado.internal.api.auth.OAuthAuthorizer; import org.openhab.binding.tado.internal.api.client.HomeApi; @@ -23,6 +24,7 @@ import com.google.gson.Gson; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class HomeApiFactory { private static final String OAUTH_SCOPE = "home.user"; private static final String OAUTH_CLIENT_ID = "public-api-preview"; diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/TadoApiTypeUtils.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/TadoApiTypeUtils.java index 909648f9b34..86e4235c7d8 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/TadoApiTypeUtils.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/api/TadoApiTypeUtils.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.tado.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; @@ -25,6 +27,7 @@ import org.openhab.binding.tado.internal.api.model.AcFanSpeed; import org.openhab.binding.tado.internal.api.model.AcMode; import org.openhab.binding.tado.internal.api.model.AcModeCapabilities; import org.openhab.binding.tado.internal.api.model.AirConditioningCapabilities; +import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities; import org.openhab.binding.tado.internal.api.model.ManualTerminationCondition; import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition; import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionTemplate; @@ -39,9 +42,11 @@ import org.openhab.binding.tado.internal.api.model.TimerTerminationConditionTemp * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoApiTypeUtils { + public static OverlayTerminationCondition getTerminationCondition(OverlayTerminationConditionType type, - Integer timerDurationInSeconds) { + int timerDurationInSeconds) { switch (type) { case TIMER: return timerTermination(timerDurationInSeconds); @@ -50,26 +55,38 @@ public class TadoApiTypeUtils { case TADO_MODE: return tadoModeTermination(); default: - return null; + throw new IllegalArgumentException("Unexpected OverlayTerminationConditionType " + type); } } public static OverlayTerminationCondition cleanTerminationCondition( OverlayTerminationCondition terminationCondition) { - Integer timerDuration = terminationCondition.getType() == OverlayTerminationConditionType.TIMER - ? ((TimerTerminationCondition) terminationCondition).getRemainingTimeInSeconds() - : null; + OverlayTerminationConditionType conditionType = terminationCondition.getType(); - return getTerminationCondition(terminationCondition.getType(), timerDuration); + int timerDuration = 0; + if (conditionType == OverlayTerminationConditionType.TIMER) { + Integer duration = ((TimerTerminationCondition) terminationCondition).getRemainingTimeInSeconds(); + if (duration != null) { + timerDuration = duration.intValue(); + } + } + + return getTerminationCondition(conditionType, timerDuration); } public static OverlayTerminationCondition terminationConditionTemplateToTerminationCondition( OverlayTerminationConditionTemplate template) { - Integer timerDuration = template.getType() == OverlayTerminationConditionType.TIMER - ? ((TimerTerminationConditionTemplate) template).getDurationInSeconds() - : null; + OverlayTerminationConditionType conditionType = template.getType(); - return getTerminationCondition(template.getType(), timerDuration); + int timerDuration = 0; + if (conditionType == OverlayTerminationConditionType.TIMER) { + Integer duration = ((TimerTerminationConditionTemplate) template).getDurationInSeconds(); + if (duration != null) { + timerDuration = duration.intValue(); + } + } + + return getTerminationCondition(conditionType, timerDuration); } public static TimerTerminationCondition timerTermination(int durationInSeconds) { @@ -103,18 +120,10 @@ public class TadoApiTypeUtils { } public static Float getTemperatureInUnit(TemperatureObject temperature, TemperatureUnit temperatureUnit) { - if (temperature == null) { - return null; - } - return temperatureUnit == TemperatureUnit.FAHRENHEIT ? temperature.getFahrenheit() : temperature.getCelsius(); } public static AcMode getAcMode(HvacMode mode) { - if (mode == null) { - return null; - } - switch (mode) { case HEAT: return AcMode.HEAT; @@ -127,15 +136,11 @@ public class TadoApiTypeUtils { case AUTO: return AcMode.AUTO; default: - return null; + throw new IllegalArgumentException("Unexpected AcMode " + mode); } } public static AcFanSpeed getAcFanSpeed(FanSpeed fanSpeed) { - if (fanSpeed == null) { - return null; - } - switch (fanSpeed) { case AUTO: return AcFanSpeed.AUTO; @@ -145,16 +150,12 @@ public class TadoApiTypeUtils { return AcFanSpeed.MIDDLE; case LOW: return AcFanSpeed.LOW; + default: + throw new IllegalArgumentException("Unexpected AcFanSpeed " + fanSpeed); } - - return null; } public static ACFanLevel getFanLevel(FanLevel fanLevel) { - if (fanLevel == null) { - return null; - } - switch (fanLevel) { case AUTO: return ACFanLevel.AUTO; @@ -170,16 +171,12 @@ public class TadoApiTypeUtils { return ACFanLevel.LEVEL5; case SILENT: return ACFanLevel.SILENT; + default: + throw new IllegalArgumentException("Unexpected FanLevel " + fanLevel); } - - return null; } public static ACHorizontalSwing getHorizontalSwing(HorizontalSwing horizontalSwing) { - if (horizontalSwing == null) { - return null; - } - switch (horizontalSwing) { case LEFT: return ACHorizontalSwing.LEFT; @@ -197,16 +194,12 @@ public class TadoApiTypeUtils { return ACHorizontalSwing.OFF; case AUTO: return ACHorizontalSwing.AUTO; + default: + throw new IllegalArgumentException("Unexpected HorizontalSwing " + horizontalSwing); } - - return null; } public static ACVerticalSwing getVerticalSwing(VerticalSwing verticalSwing) { - if (verticalSwing == null) { - return null; - } - switch (verticalSwing) { case AUTO: return ACVerticalSwing.AUTO; @@ -224,34 +217,34 @@ public class TadoApiTypeUtils { return ACVerticalSwing.ON; case OFF: return ACVerticalSwing.OFF; + default: + throw new IllegalArgumentException("Unexpected VerticalSwing " + verticalSwing); } - - return null; } - public static AcModeCapabilities getModeCapabilities(AirConditioningCapabilities capabilities, AcMode mode) { - AcModeCapabilities modeCapabilities = null; + public static AcModeCapabilities getModeCapabilities(AcMode acMode, + @Nullable GenericZoneCapabilities capabilities) { + AirConditioningCapabilities acCapabilities; - if (mode != null) { - switch (mode) { - case COOL: - modeCapabilities = capabilities.getCOOL(); - break; - case HEAT: - modeCapabilities = capabilities.getHEAT(); - break; - case DRY: - modeCapabilities = capabilities.getDRY(); - break; - case AUTO: - modeCapabilities = capabilities.getAUTO(); - break; - case FAN: - modeCapabilities = capabilities.getFAN(); - break; - } + if (capabilities instanceof AirConditioningCapabilities) { + acCapabilities = (AirConditioningCapabilities) capabilities; + } else { + acCapabilities = new AirConditioningCapabilities(); } - return modeCapabilities != null ? modeCapabilities : new AcModeCapabilities(); + switch (acMode) { + case COOL: + return acCapabilities.getCOOL(); + case HEAT: + return acCapabilities.getHEAT(); + case DRY: + return acCapabilities.getDRY(); + case AUTO: + return acCapabilities.getAUTO(); + case FAN: + return acCapabilities.getFAN(); + default: + throw new IllegalArgumentException("Unexpected AcMode " + acMode); + } } } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/AirConditioningZoneSettingsBuilder.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/AirConditioningZoneSettingsBuilder.java index 0a5a3c60514..e914c9f56d2 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/AirConditioningZoneSettingsBuilder.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/AirConditioningZoneSettingsBuilder.java @@ -17,16 +17,21 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*; import java.io.IOException; import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; +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.TadoApiTypeUtils; 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; -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; @@ -35,17 +40,23 @@ import org.openhab.binding.tado.internal.api.model.Power; import org.openhab.binding.tado.internal.api.model.TadoSystemType; import org.openhab.binding.tado.internal.api.model.TemperatureObject; import org.openhab.binding.tado.internal.api.model.TemperatureRange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder { private static final AcMode DEFAULT_MODE = AcMode.COOL; private static final float DEFAULT_TEMPERATURE_C = 20.0f; private static final float DEFAULT_TEMPERATURE_F = 68.0f; + private static final String STATE_VALUE_NOT_SUPPORTED = "Your a/c unit does not support '{}:{}' when in state '{}:{}', (supported values: [{}])."; + private Logger logger = LoggerFactory.getLogger(AirConditioningZoneSettingsBuilder.class); + @Override public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities genericCapabilities) throws IOException, ApiException { @@ -53,95 +64,138 @@ public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder { return coolingSetting(false); } - CoolingZoneSetting setting = coolingSetting(true); - setting.setMode(getAcMode(mode)); + CoolingZoneSetting newSetting = coolingSetting(true); + + AcMode targetMode; + HvacMode mode = this.mode; + if (mode != null) { + targetMode = getAcMode(mode); + newSetting.setMode(targetMode); + } else { + // if mode not changing, so the reference is the current (or default) mode + targetMode = getCurrentOrDefaultAcMode(zoneStateProvider); + } + + Float temperature = this.temperature; if (temperature != null) { - setting.setTemperature(temperature(temperature, temperatureUnit)); + newSetting.setTemperature(temperature(temperature, temperatureUnit)); } + Boolean swing = this.swing; if (swing != null) { - setting.setSwing(swing ? Power.ON : Power.OFF); + newSetting.setSwing(swing.booleanValue() ? Power.ON : Power.OFF); } + Boolean light = this.light; if (light != null) { - setting.setLight(light ? Power.ON : Power.OFF); + newSetting.setLight(light.booleanValue() ? Power.ON : Power.OFF); } + FanSpeed fanSpeed = this.fanSpeed; if (fanSpeed != null) { - setting.setFanSpeed(getAcFanSpeed(fanSpeed)); + newSetting.setFanSpeed(getAcFanSpeed(fanSpeed)); } + /* + * In the latest API release Tado introduced extra AC settings that have an open ended list of possible + * supported state values. And for any particular device, its specific list of supported values is available + * via its 'capabilities' structure. So before setting a new value, we check if the respective new value is in + * the capabilities list that corresponds to the target AC mode. And if not, a warning message is logged. + */ + AcModeCapabilities targetModeCapabilities = TadoApiTypeUtils.getModeCapabilities(targetMode, + genericCapabilities); + + FanLevel fanLevel = this.fanLevel; if (fanLevel != null) { - setting.setFanLevel(getFanLevel(fanLevel)); + ACFanLevel targetFanLevel = getFanLevel(fanLevel); + List targetFanLevels = targetModeCapabilities.getFanLevel(); + if (targetFanLevels != null && targetFanLevels.contains(targetFanLevel)) { + newSetting.setFanLevel(targetFanLevel); + } else { + logger.warn(STATE_VALUE_NOT_SUPPORTED, targetFanLevel.getClass().getSimpleName(), targetFanLevel, + targetMode.getClass().getSimpleName(), targetMode, targetFanLevels); + } } + HorizontalSwing horizontalSwing = this.horizontalSwing; if (horizontalSwing != null) { - setting.setHorizontalSwing(getHorizontalSwing(horizontalSwing)); + ACHorizontalSwing targetHorizontalSwing = getHorizontalSwing(horizontalSwing); + List targetHorizontalSwings = targetModeCapabilities.getHorizontalSwing(); + if (targetHorizontalSwings != null && targetHorizontalSwings.contains(targetHorizontalSwing)) { + newSetting.setHorizontalSwing(targetHorizontalSwing); + } else { + logger.warn(STATE_VALUE_NOT_SUPPORTED, targetHorizontalSwing.getClass().getSimpleName(), + targetHorizontalSwing, targetMode.getClass().getSimpleName(), targetMode, + targetHorizontalSwings); + } } + VerticalSwing verticalSwing = this.verticalSwing; if (verticalSwing != null) { - setting.setVerticalSwing(getVerticalSwing(verticalSwing)); + ACVerticalSwing targetVerticalSwing = getVerticalSwing(verticalSwing); + List targetVerticalSwings = targetModeCapabilities.getVerticalSwing(); + if (targetVerticalSwings != null && targetVerticalSwings.contains(targetVerticalSwing)) { + newSetting.setVerticalSwing(targetVerticalSwing); + } else { + logger.warn(STATE_VALUE_NOT_SUPPORTED, targetVerticalSwing.getClass().getSimpleName(), + targetVerticalSwing, targetMode.getClass().getSimpleName(), targetMode, targetVerticalSwings); + } } - addMissingSettingParts(zoneStateProvider, genericCapabilities, setting); + addMissingSettingParts(zoneStateProvider, genericCapabilities, newSetting); - return setting; + return newSetting; } private void addMissingSettingParts(ZoneStateProvider zoneStateProvider, - GenericZoneCapabilities genericCapabilities, CoolingZoneSetting setting) throws IOException, ApiException { - if (setting.getMode() == null) { + GenericZoneCapabilities genericCapabilities, CoolingZoneSetting newSetting) + throws IOException, ApiException { + if (newSetting.getMode() == null) { AcMode targetMode = getCurrentOrDefaultAcMode(zoneStateProvider); - setting.setMode(targetMode); + newSetting.setMode(targetMode); } - AcModeCapabilities capabilities = getModeCapabilities((AirConditioningCapabilities) genericCapabilities, - setting.getMode()); + AcModeCapabilities targetCapabilities = getModeCapabilities(newSetting.getMode(), genericCapabilities); - TemperatureRange temperatures = capabilities.getTemperatures(); - if (temperatures != null && setting.getTemperature() == null) { - setting.setTemperature(getCurrentOrDefaultTemperature(zoneStateProvider, temperatures)); + TemperatureRange temperatures = targetCapabilities.getTemperatures(); + if (temperatures != null && newSetting.getTemperature() == null) { + newSetting.setTemperature(getCurrentOrDefaultTemperature(zoneStateProvider, temperatures)); } - List fanSpeeds = capabilities.getFanSpeeds(); - if (fanSpeeds != null && !fanSpeeds.isEmpty() && setting.getFanSpeed() == null) { - setting.setFanSpeed(getCurrentOrDefaultFanSpeed(zoneStateProvider, fanSpeeds)); + List fanSpeeds = targetCapabilities.getFanSpeeds(); + if (fanSpeeds != null && !fanSpeeds.isEmpty() && newSetting.getFanSpeed() == null) { + newSetting.setFanSpeed(getCurrentOrDefaultFanSpeed(zoneStateProvider, fanSpeeds)); } - List swings = capabilities.getSwings(); - if (swings != null && !swings.isEmpty() && setting.getSwing() == null) { - setting.setSwing(getCurrentOrDefaultSwing(zoneStateProvider, swings)); + List swings = targetCapabilities.getSwings(); + if (swings != null && !swings.isEmpty() && newSetting.getSwing() == null) { + newSetting.setSwing(getCurrentOrDefaultSwing(zoneStateProvider, swings)); } - // Tado confusingly calls the List / getter method 'fanLevel' / 'getFanLevel()' without 's' - List fanLevels = capabilities.getFanLevel(); - if (fanLevels != null && !fanLevels.isEmpty() && setting.getFanLevel() == null) { - setting.setFanLevel(getCurrentOrDefaultFanLevel(zoneStateProvider, fanLevels)); + List fanLevels = targetCapabilities.getFanLevel(); + if (fanLevels != null && !fanLevels.isEmpty() && newSetting.getFanLevel() == null) { + newSetting.setFanLevel(getCurrentOrDefaultFanLevel(zoneStateProvider, fanLevels)); } - // Tado confusingly calls the List / getter method 'horizontalSwing' / 'getHorizontalSwing()' without 's' - List horizontalSwings = capabilities.getHorizontalSwing(); - if (horizontalSwings != null && !horizontalSwings.isEmpty() && setting.getHorizontalSwing() == null) { - setting.setHorizontalSwing(getCurrentOrDefaultHorizontalSwing(zoneStateProvider, horizontalSwings)); + List horizontalSwings = targetCapabilities.getHorizontalSwing(); + if (horizontalSwings != null && !horizontalSwings.isEmpty() && newSetting.getHorizontalSwing() == null) { + newSetting.setHorizontalSwing(getCurrentOrDefaultHorizontalSwing(zoneStateProvider, horizontalSwings)); } - // Tado confusingly calls the List / getter method 'verticalSwing' / 'getVerticalSwing()' without 's' - List verticalSwings = capabilities.getVerticalSwing(); - if (verticalSwings != null && !verticalSwings.isEmpty() && setting.getVerticalSwing() == null) { - setting.setVerticalSwing(getCurrentOrDefaultVerticalSwing(zoneStateProvider, verticalSwings)); + List verticalSwings = targetCapabilities.getVerticalSwing(); + if (verticalSwings != null && !verticalSwings.isEmpty() && newSetting.getVerticalSwing() == null) { + newSetting.setVerticalSwing(getCurrentOrDefaultVerticalSwing(zoneStateProvider, verticalSwings)); } - // Tado confusingly calls the List / getter method 'light' / 'getLight()' without 's' - List lights = capabilities.getLight(); - if (lights != null && !lights.isEmpty() && setting.getLight() == null) { - setting.setLight(getCurrentOrDefaultLight(zoneStateProvider, lights)); + List lights = targetCapabilities.getLight(); + if (lights != null && !lights.isEmpty() && newSetting.getLight() == null) { + newSetting.setLight(getCurrentOrDefaultLight(zoneStateProvider, lights)); } } private AcMode getCurrentOrDefaultAcMode(ZoneStateProvider zoneStateProvider) throws IOException, ApiException { - CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); - - return zoneSetting.getMode() != null ? zoneSetting.getMode() : DEFAULT_MODE; + AcMode acMode = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getMode(); + return acMode != null ? acMode : DEFAULT_MODE; } private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider, @@ -165,68 +219,40 @@ public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder { private AcFanSpeed getCurrentOrDefaultFanSpeed(ZoneStateProvider zoneStateProvider, List fanSpeeds) throws IOException, ApiException { - CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); - - if (zoneSetting.getFanSpeed() != null && fanSpeeds.contains(zoneSetting.getFanSpeed())) { - return zoneSetting.getFanSpeed(); - } - - return fanSpeeds.get(0); + AcFanSpeed fanSpeed = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getFanSpeed(); + return (fanSpeed != null) && fanSpeeds.contains(fanSpeed) ? fanSpeed : fanSpeeds.get(0); } private Power getCurrentOrDefaultSwing(ZoneStateProvider zoneStateProvider, List swings) throws IOException, ApiException { - CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); - - if (zoneSetting.getSwing() != null && swings.contains(zoneSetting.getSwing())) { - return zoneSetting.getSwing(); - } - - return swings.get(0); - } - - private Power getCurrentOrDefaultLight(ZoneStateProvider zoneStateProvider, List 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); + Power swing = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getSwing(); + return (swing != null) && swings.contains(swing) ? swing : swings.get(0); } private ACFanLevel getCurrentOrDefaultFanLevel(ZoneStateProvider zoneStateProvider, List 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 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); + ACFanLevel fanLevel = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getFanLevel(); + return (fanLevel != null) && fanLevels.contains(fanLevel) ? fanLevel : fanLevels.get(0); } private ACVerticalSwing getCurrentOrDefaultVerticalSwing(ZoneStateProvider zoneStateProvider, - List verticalSwings) throws IOException, ApiException { - CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); + List vertSwings) throws IOException, ApiException { + ACVerticalSwing vertSwing = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()) + .getVerticalSwing(); + return (vertSwing != null) && vertSwings.contains(vertSwing) ? vertSwing : vertSwings.get(0); + } - if (zoneSetting.getVerticalSwing() != null && verticalSwings.contains(zoneSetting.getVerticalSwing())) { - return zoneSetting.getVerticalSwing(); - } + private ACHorizontalSwing getCurrentOrDefaultHorizontalSwing(ZoneStateProvider zoneStateProvider, + List horzSwings) throws IOException, ApiException { + ACHorizontalSwing horzSwing = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()) + .getHorizontalSwing(); + return (horzSwing != null) && horzSwings.contains(horzSwing) ? horzSwing : horzSwings.get(0); + } - return verticalSwings.get(0); + private Power getCurrentOrDefaultLight(ZoneStateProvider zoneStateProvider, List lights) + throws IOException, ApiException { + Power light = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getLight(); + return (light != null) && lights.contains(light) ? light : lights.get(0); } private CoolingZoneSetting coolingSetting(boolean powerOn) { diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HeatingZoneSettingsBuilder.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HeatingZoneSettingsBuilder.java index d6d35896571..ef4997e1b74 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HeatingZoneSettingsBuilder.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HeatingZoneSettingsBuilder.java @@ -16,6 +16,7 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature import java.io.IOException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; @@ -34,6 +35,7 @@ import org.openhab.binding.tado.internal.api.model.TemperatureObject; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder { private static final float DEFAULT_TEMPERATURE_C = 22.0f; private static final float DEFAULT_TEMPERATURE_F = 72.0f; @@ -77,6 +79,7 @@ public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder { HeatingZoneSetting setting = heatingSetting(true); + Float temperature = this.temperature; if (temperature != null) { setting.setTemperature(temperature(temperature, temperatureUnit)); } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HotWaterZoneSettingsBuilder.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HotWaterZoneSettingsBuilder.java index b4e9d7c77af..5961547918f 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HotWaterZoneSettingsBuilder.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/HotWaterZoneSettingsBuilder.java @@ -16,6 +16,7 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature import java.io.IOException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; @@ -35,6 +36,7 @@ import org.openhab.binding.tado.internal.api.model.TemperatureObject; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder { private static final float DEFAULT_TEMPERATURE_C = 50.0f; private static final float DEFAULT_TEMPERATURE_F = 122.0f; @@ -78,6 +80,7 @@ public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder { HotWaterZoneSetting setting = hotWaterSetting(true); + Float temperature = this.temperature; if (temperature != null) { setting.setTemperature(temperature(temperature, temperatureUnit)); } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/TerminationConditionBuilder.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/TerminationConditionBuilder.java index 3eaadd32d8c..a75273e51f7 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/TerminationConditionBuilder.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/TerminationConditionBuilder.java @@ -16,6 +16,8 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*; import java.io.IOException; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.api.ApiException; import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition; import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType; @@ -28,11 +30,13 @@ import org.openhab.binding.tado.internal.handler.TadoZoneHandler; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TerminationConditionBuilder { - private TadoZoneHandler zoneHandler; - private OverlayTerminationConditionType terminationType = null; - private Integer timerDurationInSeconds = null; + private final TadoZoneHandler zoneHandler; + + private @Nullable OverlayTerminationConditionType terminationType; + private int timerDurationInSeconds = 0; protected TerminationConditionBuilder(TadoZoneHandler zoneHandler) { this.zoneHandler = zoneHandler; @@ -45,23 +49,23 @@ public class TerminationConditionBuilder { public TerminationConditionBuilder withTerminationType(OverlayTerminationConditionType terminationType) { this.terminationType = terminationType; if (terminationType != OverlayTerminationConditionType.TIMER) { - timerDurationInSeconds = null; + timerDurationInSeconds = 0; } - return this; } - public TerminationConditionBuilder withTimerDurationInSeconds(Integer timerDurationInSeconds) { + public TerminationConditionBuilder withTimerDurationInSeconds(int timerDurationInSeconds) { this.terminationType = OverlayTerminationConditionType.TIMER; this.timerDurationInSeconds = timerDurationInSeconds; return this; } public OverlayTerminationCondition build(ZoneStateProvider zoneStateProvider) throws IOException, ApiException { - OverlayTerminationCondition terminationCondition = null; + OverlayTerminationCondition terminationCondition; + OverlayTerminationConditionType terminationType = this.terminationType; if (terminationType != null) { - if (terminationType != OverlayTerminationConditionType.TIMER || timerDurationInSeconds != null) { + if (terminationType != OverlayTerminationConditionType.TIMER || timerDurationInSeconds > 0) { terminationCondition = getTerminationCondition(terminationType, timerDurationInSeconds); } else { terminationCondition = getCurrentOrDefaultTimerTermination(zoneStateProvider); @@ -75,18 +79,19 @@ public class TerminationConditionBuilder { terminationCondition = getDefaultTerminationCondition(); } } + return terminationCondition; } private OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException { OverlayTerminationCondition defaultTerminationCondition = zoneHandler.getDefaultTerminationCondition(); - return defaultTerminationCondition != null ? defaultTerminationCondition : manualTermination(); + return defaultTerminationCondition; } private TimerTerminationCondition getCurrentOrDefaultTimerTermination(ZoneStateProvider zoneStateProvider) throws IOException, ApiException { // Timer without duration - int duration = zoneHandler.getFallbackTimerDuration() * 60; + Integer duration = zoneHandler.getFallbackTimerDuration() * 60; ZoneState zoneState = zoneStateProvider.getZoneState(); diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneSettingsBuilder.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneSettingsBuilder.java index 43d7efd9991..2f10418ac16 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneSettingsBuilder.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneSettingsBuilder.java @@ -14,13 +14,15 @@ package org.openhab.binding.tado.internal.builder; import java.io.IOException; -import org.openhab.binding.tado.internal.TadoBindingConstants; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; 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.TadoBindingConstants.ZoneType; 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; @@ -32,12 +34,12 @@ import org.openhab.binding.tado.internal.handler.TadoZoneHandler; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public abstract class ZoneSettingsBuilder { + public static ZoneSettingsBuilder of(TadoZoneHandler zoneHandler) { - TadoBindingConstants.ZoneType zoneType = zoneHandler.getZoneType(); - if (zoneType == null) { - throw new IllegalArgumentException("Zone type is null"); - } + ZoneType zoneType = zoneHandler.getZoneType(); + switch (zoneType) { case HEATING: return new HeatingZoneSettingsBuilder(); @@ -46,19 +48,19 @@ public abstract class ZoneSettingsBuilder { case HOT_WATER: return new HotWaterZoneSettingsBuilder(); default: - throw new IllegalArgumentException("Zone type " + zoneHandler.getZoneType() + " unknown"); + throw new IllegalArgumentException("Zone type " + zoneType + " unknown"); } } - protected HvacMode mode = null; - 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; + protected @Nullable Float temperature; + protected @Nullable HvacMode mode; + protected @Nullable Boolean swing; + protected @Nullable Boolean light; + protected @Nullable FanSpeed fanSpeed; + protected @Nullable FanLevel fanLevel; + protected @Nullable HorizontalSwing horizontalSwing; + protected @Nullable VerticalSwing verticalSwing; public ZoneSettingsBuilder withMode(HvacMode mode) { this.mode = mode; @@ -100,10 +102,6 @@ public abstract class ZoneSettingsBuilder { throws IOException, ApiException; protected TemperatureObject truncateTemperature(TemperatureObject temperature) { - if (temperature == null) { - return null; - } - TemperatureObject temperatureObject = new TemperatureObject(); if (temperatureUnit == TemperatureUnit.FAHRENHEIT) { temperatureObject.setFahrenheit(temperature.getFahrenheit()); diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneStateProvider.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneStateProvider.java index ecf9a390e7c..ae19a841847 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneStateProvider.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/builder/ZoneStateProvider.java @@ -14,6 +14,8 @@ package org.openhab.binding.tado.internal.builder; import java.io.IOException; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.api.ApiException; import org.openhab.binding.tado.internal.api.model.ZoneState; import org.openhab.binding.tado.internal.handler.TadoZoneHandler; @@ -23,21 +25,20 @@ import org.openhab.binding.tado.internal.handler.TadoZoneHandler; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class ZoneStateProvider { - private TadoZoneHandler zoneHandler; - private ZoneState zoneState; + private final TadoZoneHandler zoneHandler; + private @Nullable ZoneState zoneState; public ZoneStateProvider(TadoZoneHandler zoneHandler) { this.zoneHandler = zoneHandler; } - ZoneState getZoneState() throws IOException, ApiException { - if (this.zoneState == null) { - ZoneState retrievedZoneState = zoneHandler.getZoneState(); - // empty zone state behaves like a NULL object - this.zoneState = retrievedZoneState != null ? retrievedZoneState : new ZoneState(); + public synchronized ZoneState getZoneState() throws IOException, ApiException { + ZoneState zoneState = this.zoneState; + if (zoneState == null) { + zoneState = this.zoneState = zoneHandler.getZoneState(); } - - return this.zoneState; + return zoneState; } } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoHomeConfig.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoHomeConfig.java index 230c04bfa56..1a9b9250acf 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoHomeConfig.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoHomeConfig.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.tado.internal.config; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Holder-object for home configuration * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoHomeConfig { - public String username; - public String password; + public String username = ""; + public String password = ""; } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoMobileDeviceConfig.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoMobileDeviceConfig.java index f702fb57551..7e71b57b42d 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoMobileDeviceConfig.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoMobileDeviceConfig.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.tado.internal.config; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Holder-object for mobile device configuration * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoMobileDeviceConfig { public int id; public int refreshInterval; diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoZoneConfig.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoZoneConfig.java index 467ed802140..e5fa258d91e 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoZoneConfig.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/config/TadoZoneConfig.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.tado.internal.config; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Holder-object for zone configuration * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoZoneConfig { public long id; public int refreshInterval; diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/discovery/TadoDiscoveryService.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/discovery/TadoDiscoveryService.java index 9de930c4881..64d18df58b1 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/discovery/TadoDiscoveryService.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/discovery/TadoDiscoveryService.java @@ -25,6 +25,8 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.TadoBindingConstants; import org.openhab.binding.tado.internal.api.ApiException; import org.openhab.binding.tado.internal.api.model.MobileDevice; @@ -43,13 +45,14 @@ import org.slf4j.LoggerFactory; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoDiscoveryService extends AbstractDiscoveryService { private static final int TIMEOUT = 5; private static final long REFRESH = 600; private final Logger logger = LoggerFactory.getLogger(TadoDiscoveryService.class); - private ScheduledFuture discoveryFuture; + private @Nullable ScheduledFuture discoveryFuture; public static final Set DISCOVERABLE_THING_TYPES_UIDS = Collections .unmodifiableSet(Stream.of(THING_TYPE_ZONE, THING_TYPE_MOBILE_DEVICE).collect(Collectors.toSet())); @@ -83,23 +86,30 @@ public class TadoDiscoveryService extends AbstractDiscoveryService { @Override protected void startBackgroundDiscovery() { logger.debug("Start Tado background discovery"); + ScheduledFuture discoveryFuture = this.discoveryFuture; if (discoveryFuture == null || discoveryFuture.isCancelled()) { logger.debug("Start Scan"); - discoveryFuture = scheduler.scheduleWithFixedDelay(this::startScan, 30, REFRESH, TimeUnit.SECONDS); + this.discoveryFuture = scheduler.scheduleWithFixedDelay(this::startScan, 30, REFRESH, TimeUnit.SECONDS); } } @Override protected void stopBackgroundDiscovery() { logger.debug("Stop Tado background discovery"); + ScheduledFuture discoveryFuture = this.discoveryFuture; if (discoveryFuture != null && !discoveryFuture.isCancelled()) { discoveryFuture.cancel(true); - discoveryFuture = null; } } private void discoverZones() { Long homeId = homeHandler.getHomeId(); + + if (homeId == null) { + logger.debug("Could not discover tado zones: Missing home id"); + return; + } + try { List zoneList = homeHandler.getApi().listZones(homeId); @@ -132,6 +142,12 @@ public class TadoDiscoveryService extends AbstractDiscoveryService { private void discoverMobileDevices() { Long homeId = homeHandler.getHomeId(); + + if (homeId == null) { + logger.debug("Could not discover mobile devices: Missing home id"); + return; + } + try { List mobileDeviceList = homeHandler.getApi().listMobileDevices(homeId); @@ -143,7 +159,7 @@ public class TadoDiscoveryService extends AbstractDiscoveryService { } } } catch (IOException | ApiException e) { - logger.debug("Could not discover tado zones: {}", e.getMessage(), e); + logger.debug("Could not discover mobile devices: {}", e.getMessage(), e); } } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/BaseHomeThingHandler.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/BaseHomeThingHandler.java index fc41c6bc1a2..b8231b0ba5f 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/BaseHomeThingHandler.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/BaseHomeThingHandler.java @@ -12,36 +12,47 @@ */ package org.openhab.binding.tado.internal.handler; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.api.client.HomeApi; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; /** * Common base class for home-based thing-handler. * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public abstract class BaseHomeThingHandler extends BaseThingHandler { public BaseHomeThingHandler(Thing thing) { super(thing); } - public Long getHomeId() { + public @Nullable Long getHomeId() { TadoHomeHandler handler = getHomeHandler(); - return handler != null ? handler.getHomeId() : Long.valueOf(0); + return handler.getHomeId(); } protected TadoHomeHandler getHomeHandler() { Bridge bridge = getBridge(); - return bridge != null ? (TadoHomeHandler) bridge.getHandler() : null; + if (bridge == null) { + throw new IllegalStateException("Bridge not initialized"); + } + BridgeHandler handler = bridge.getHandler(); + if (!(handler instanceof TadoHomeHandler)) { + throw new IllegalStateException("Handler not initialized"); + } + return (TadoHomeHandler) handler; } protected HomeApi getApi() { TadoHomeHandler handler = getHomeHandler(); - return handler != null ? handler.getApi() : null; + return handler.getApi(); } protected void onSuccessfulOperation() { 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 856125477ef..d2edfad2272 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 @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +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.core.library.types.OnOffType; @@ -32,14 +33,16 @@ import org.slf4j.LoggerFactory; * devices. * * @author Andrew Fiddian-Green - Initial contribution - * + * */ +@NonNullByDefault public class TadoBatteryChecker { private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class); - private Map zoneList = new HashMap<>(); + private final Map zoneList = new HashMap<>(); + private final TadoHomeHandler homeHandler; + private Date refreshTime = new Date(); - private TadoHomeHandler homeHandler; public TadoBatteryChecker(TadoHomeHandler homeHandler) { this.homeHandler = homeHandler; @@ -47,7 +50,7 @@ public class TadoBatteryChecker { private synchronized void refreshZoneList() { Date now = new Date(); - if (homeHandler != null && (now.after(refreshTime) || zoneList.isEmpty())) { + if (now.after(refreshTime) || zoneList.isEmpty()) { // be frugal, we only need to refresh the battery state hourly Calendar calendar = Calendar.getInstance(); calendar.setTime(now); diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHandlerFactory.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHandlerFactory.java index f7ac2d6752d..8cdbdea3754 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHandlerFactory.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoHandlerFactory.java @@ -22,6 +22,8 @@ import java.util.Hashtable; import java.util.Map; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.discovery.TadoDiscoveryService; import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.thing.Bridge; @@ -42,6 +44,7 @@ import org.osgi.service.component.annotations.Reference; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault @Component(configurationPid = "binding.tado", service = ThingHandlerFactory.class) public class TadoHandlerFactory extends BaseThingHandlerFactory { @@ -63,7 +66,7 @@ public class TadoHandlerFactory extends BaseThingHandlerFactory { } @Override - protected ThingHandler createHandler(Thing thing) { + protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (thingTypeUID.equals(THING_TYPE_HOME)) { 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 3cffe4bfded..e7064327a0e 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 @@ -13,9 +13,12 @@ package org.openhab.binding.tado.internal.handler; import java.io.IOException; +import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.TadoBindingConstants; import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit; import org.openhab.binding.tado.internal.api.ApiException; @@ -26,6 +29,7 @@ import org.openhab.binding.tado.internal.api.model.HomePresence; import org.openhab.binding.tado.internal.api.model.HomeState; import org.openhab.binding.tado.internal.api.model.PresenceState; import org.openhab.binding.tado.internal.api.model.User; +import org.openhab.binding.tado.internal.api.model.UserHomes; import org.openhab.binding.tado.internal.config.TadoHomeConfig; import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.Bridge; @@ -36,6 +40,7 @@ import org.openhab.core.thing.binding.BaseBridgeHandler; 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; @@ -44,21 +49,23 @@ import org.slf4j.LoggerFactory; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoHomeHandler extends BaseBridgeHandler { private Logger logger = LoggerFactory.getLogger(TadoHomeHandler.class); private TadoHomeConfig configuration; - private HomeApi api; - private Long homeId; + private final HomeApi api; - private TadoBatteryChecker batteryChecker; - - private ScheduledFuture initializationFuture; + private @Nullable Long homeId; + private @Nullable TadoBatteryChecker batteryChecker; + private @Nullable ScheduledFuture initializationFuture; public TadoHomeHandler(Bridge bridge) { super(bridge); batteryChecker = new TadoBatteryChecker(this); + configuration = getConfigAs(TadoHomeConfig.class); + api = new HomeApiFactory().create(configuration.username, configuration.password); } public TemperatureUnit getTemperatureUnit() { @@ -70,11 +77,10 @@ public class TadoHomeHandler extends BaseBridgeHandler { @Override public void initialize() { configuration = getConfigAs(TadoHomeConfig.class); - api = new HomeApiFactory().create(configuration.username, configuration.password); - - if (this.initializationFuture == null || this.initializationFuture.isDone()) { - initializationFuture = scheduler.scheduleWithFixedDelay(this::initializeBridgeStatusAndPropertiesIfOffline, - 0, 300, TimeUnit.SECONDS); + ScheduledFuture initializationFuture = this.initializationFuture; + if (initializationFuture == null || initializationFuture.isDone()) { + this.initializationFuture = scheduler.scheduleWithFixedDelay( + this::initializeBridgeStatusAndPropertiesIfOffline, 0, 300, TimeUnit.SECONDS); } } @@ -93,13 +99,20 @@ public class TadoHomeHandler extends BaseBridgeHandler { return; } - if (user.getHomes().isEmpty()) { + List homes = user.getHomes(); + if (homes == null || homes.isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "User does not have access to any home"); return; } - homeId = user.getHomes().get(0).getId().longValue(); + Integer firstHomeId = homes.get(0).getId(); + if (firstHomeId == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Missing Home Id"); + return; + } + + homeId = firstHomeId.longValue(); HomeInfo homeInfo = api.showHome(homeId); TemperatureUnit temperatureUnit = org.openhab.binding.tado.internal.api.model.TemperatureUnit.FAHRENHEIT == homeInfo @@ -118,9 +131,9 @@ public class TadoHomeHandler extends BaseBridgeHandler { @Override public void dispose() { super.dispose(); - if (this.initializationFuture != null || !this.initializationFuture.isDone()) { - this.initializationFuture.cancel(true); - this.initializationFuture = null; + ScheduledFuture initializationFuture = this.initializationFuture; + if (initializationFuture != null && !initializationFuture.isCancelled()) { + initializationFuture.cancel(true); } } @@ -128,13 +141,12 @@ public class TadoHomeHandler extends BaseBridgeHandler { return api; } - public Long getHomeId() { + public @Nullable Long getHomeId() { return homeId; } public HomeState getHomeState() throws IOException, ApiException { - HomeApi api = getApi(); - return api != null ? api.homeState(getHomeId()) : null; + return api.homeState(getHomeId()); } public void updateHomeState() { @@ -173,6 +185,7 @@ public class TadoHomeHandler extends BaseBridgeHandler { } public State getBatteryLowAlarm(long zoneId) { - return batteryChecker.getBatteryLowAlarm(zoneId); + TadoBatteryChecker batteryChecker = this.batteryChecker; + return batteryChecker != null ? batteryChecker.getBatteryLowAlarm(zoneId) : UnDefType.UNDEF; } } diff --git a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoMobileDeviceHandler.java b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoMobileDeviceHandler.java index 81c46717c95..38b34ac34a3 100644 --- a/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoMobileDeviceHandler.java +++ b/bundles/org.openhab.binding.tado/src/main/java/org/openhab/binding/tado/internal/handler/TadoMobileDeviceHandler.java @@ -16,6 +16,8 @@ import java.io.IOException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.TadoBindingConstants; import org.openhab.binding.tado.internal.api.ApiException; import org.openhab.binding.tado.internal.api.model.MobileDevice; @@ -37,15 +39,17 @@ import org.slf4j.LoggerFactory; * * @author Dennis Frommknecht - Initial contribution */ +@NonNullByDefault public class TadoMobileDeviceHandler extends BaseHomeThingHandler { private Logger logger = LoggerFactory.getLogger(TadoMobileDeviceHandler.class); private TadoMobileDeviceConfig configuration; - private ScheduledFuture refreshTimer; + private @Nullable ScheduledFuture refreshTimer; public TadoMobileDeviceHandler(Thing thing) { super(thing); + configuration = getConfigAs(TadoMobileDeviceConfig.class); } @Override @@ -61,7 +65,6 @@ public class TadoMobileDeviceHandler extends BaseHomeThingHandler { @Override public void initialize() { configuration = getConfigAs(TadoMobileDeviceConfig.class); - if (configuration.refreshInterval <= 0) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone " + configuration.id + " of home " + getHomeId() + " must be greater than zero"); @@ -135,13 +138,15 @@ public class TadoMobileDeviceHandler extends BaseHomeThingHandler { } private void scheduleZoneStateUpdate() { + ScheduledFuture refreshTimer = this.refreshTimer; if (refreshTimer == null || refreshTimer.isCancelled()) { - refreshTimer = scheduler.scheduleWithFixedDelay(this::updateState, 5, configuration.refreshInterval, + this.refreshTimer = scheduler.scheduleWithFixedDelay(this::updateState, 5, configuration.refreshInterval, TimeUnit.SECONDS); } } private void cancelScheduledStateUpdate() { + ScheduledFuture refreshTimer = this.refreshTimer; if (refreshTimer != null) { refreshTimer.cancel(false); } 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 e76aa515ead..ce30cb37d6e 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 @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import javax.measure.quantity.Temperature; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tado.internal.TadoBindingConstants; import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel; @@ -33,13 +34,13 @@ 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.GsonBuilderFactory; 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.AcMode; import org.openhab.binding.tado.internal.api.model.AcModeCapabilities; -import org.openhab.binding.tado.internal.api.model.AirConditioningCapabilities; import org.openhab.binding.tado.internal.api.model.CoolingZoneSetting; import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities; import org.openhab.binding.tado.internal.api.model.GenericZoneSetting; @@ -65,11 +66,12 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusInfo; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; import org.openhab.core.types.StateOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.Gson; + /** * The {@link TadoZoneHandler} is responsible for handling commands of zones and update their state. * @@ -77,47 +79,61 @@ import org.slf4j.LoggerFactory; * @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels * */ +@NonNullByDefault 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; + + private @Nullable ScheduledFuture refreshTimer; + private @Nullable ScheduledFuture scheduledHvacChange; + private @Nullable GenericZoneCapabilities capabilities; + private @Nullable TadoHvacChange pendingHvacChange; + + private boolean disposing = false; + private @Nullable Gson gson; public TadoZoneHandler(Thing thing, TadoStateDescriptionProvider stateDescriptionProvider) { super(thing); this.stateDescriptionProvider = stateDescriptionProvider; + configuration = getConfigAs(TadoZoneConfig.class); } public long getZoneId() { - return this.configuration.id; + return configuration.id; } public int getFallbackTimerDuration() { - return this.configuration.fallbackTimerDuration; + return configuration.fallbackTimerDuration; } - public @Nullable ZoneType getZoneType() { - String zoneTypeStr = this.thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE); - return zoneTypeStr != null ? ZoneType.valueOf(zoneTypeStr) : null; + public ZoneType getZoneType() { + String zoneTypeStr = thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE); + if (zoneTypeStr == null) { + throw new IllegalStateException("Zone type not initialized"); + } + return ZoneType.valueOf(zoneTypeStr); } public OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException { OverlayTemplate overlayTemplate = getApi().showZoneDefaultOverlay(getHomeId(), getZoneId()); + logApiTransaction(overlayTemplate, false); return terminationConditionTemplateToTerminationCondition(overlayTemplate.getTerminationCondition()); } public ZoneState getZoneState() throws IOException, ApiException { - HomeApi api = getApi(); - return api != null ? api.showZoneState(getHomeId(), getZoneId()) : null; + ZoneState zoneState = getApi().showZoneState(getHomeId(), getZoneId()); + logApiTransaction(zoneState, false); + return zoneState; } public GenericZoneCapabilities getZoneCapabilities() { - return this.capabilities; + GenericZoneCapabilities capabilities = this.capabilities; + if (capabilities == null) { + throw new IllegalStateException("Zone capabilities not initialized"); + } + return capabilities; } public TemperatureUnit getTemperatureUnit() { @@ -125,9 +141,17 @@ public class TadoZoneHandler extends BaseHomeThingHandler { } public Overlay setOverlay(Overlay overlay) throws IOException, ApiException { - logger.debug("Setting overlay of home {} and zone {} with overlay: {}", getHomeId(), getZoneId(), - overlay.toString()); - return getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay); + try { + logApiTransaction(overlay, true); + Overlay newOverlay = getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay); + logApiTransaction(newOverlay, false); + return newOverlay; + } catch (ApiException e) { + if (!logger.isTraceEnabled()) { + logger.warn("ApiException sending JSON content:\n{}", convertToJsonString(overlay)); + } + throw e; + } } public void removeOverlay() throws IOException, ApiException { @@ -144,65 +168,75 @@ public class TadoZoneHandler extends BaseHomeThingHandler { return; } - switch (id) { - case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE: - pendingHvacChange.withHvacMode(((StringType) command).toFullString()); - scheduleHvacChange(); - break; - case TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE: - QuantityType state = (QuantityType) command; - QuantityType stateInTargetUnit = getTemperatureUnit() == TemperatureUnit.FAHRENHEIT - ? state.toUnit(ImperialUnits.FAHRENHEIT) - : state.toUnit(SIUnits.CELSIUS); + synchronized (this) { + TadoHvacChange pendingHvacChange = this.pendingHvacChange; + if (pendingHvacChange == null) { + throw new IllegalStateException("Zone pendingHvacChange not initialized"); + } - if (stateInTargetUnit != null) { - pendingHvacChange.withTemperature(stateInTargetUnit.floatValue()); + switch (id) { + case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE: + pendingHvacChange.withHvacMode(((StringType) command).toFullString()); scheduleHvacChange(); - } + break; + case TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE: + if (command instanceof QuantityType) { + @SuppressWarnings("unchecked") + QuantityType state = (QuantityType) command; + QuantityType stateInTargetUnit = getTemperatureUnit() == TemperatureUnit.FAHRENHEIT + ? state.toUnit(ImperialUnits.FAHRENHEIT) + : state.toUnit(SIUnits.CELSIUS); - break; - case TadoBindingConstants.CHANNEL_ZONE_SWING: - 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)); - scheduleHvacChange(); - break; - case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION: - pendingHvacChange.activeFor(((DecimalType) command).intValue()); - scheduleHvacChange(); - break; + if (stateInTargetUnit != null) { + pendingHvacChange.withTemperature(stateInTargetUnit.floatValue()); + scheduleHvacChange(); + } + } + break; + case TadoBindingConstants.CHANNEL_ZONE_SWING: + pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON); + scheduleHvacChange(); + break; + case TadoBindingConstants.CHANNEL_ZONE_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())); + scheduleHvacChange(); + 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)); + scheduleHvacChange(); + break; + case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION: + pendingHvacChange.activeForMinutes(((DecimalType) command).intValue()); + scheduleHvacChange(); + break; + } } } @Override public void initialize() { + disposing = false; configuration = getConfigAs(TadoZoneConfig.class); - if (configuration.refreshInterval <= 0) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone " + getZoneId() + " of home " + getHomeId() + " must be greater than zero"); @@ -225,6 +259,7 @@ public class TadoZoneHandler extends BaseHomeThingHandler { @Override public void dispose() { + disposing = true; cancelScheduledZoneStateUpdate(); } @@ -233,7 +268,10 @@ public class TadoZoneHandler extends BaseHomeThingHandler { if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) { try { Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId()); + logApiTransaction(zoneDetails, false); + GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId()); + logApiTransaction(capabilities, false); if (zoneDetails == null || capabilities == null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, @@ -244,7 +282,6 @@ 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()); @@ -263,12 +300,14 @@ public class TadoZoneHandler extends BaseHomeThingHandler { } private void updateZoneState(boolean forceUpdate) { - TadoHomeHandler home = getHomeHandler(); - if (home != null) { - home.updateHomeState(); + if ((thing.getStatus() != ThingStatus.ONLINE) || disposing) { + return; } + getHomeHandler().updateHomeState(); + // No update during HVAC change debounce + ScheduledFuture scheduledHvacChange = this.scheduledHvacChange; if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) { return; } @@ -276,18 +315,14 @@ public class TadoZoneHandler extends BaseHomeThingHandler { try { ZoneState zoneState = getZoneState(); - if (zoneState == null) { - return; - } - logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId()); TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit()); - updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature()); - updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity()); + updateState(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature()); + updateState(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity()); - updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower()); - updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower()); + updateState(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower()); + updateState(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower()); updateState(TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE, state.getOperationMode()); @@ -314,9 +349,8 @@ public class TadoZoneHandler extends BaseHomeThingHandler { "Could not connect to server due to " + e.getMessage()); } - if (home != null) { - updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM, home.getBatteryLowAlarm(getZoneId())); - } + updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM, + getHomeHandler().getBatteryLowAlarm(getZoneId())); } /** @@ -333,32 +367,35 @@ public class TadoZoneHandler extends BaseHomeThingHandler { return; } - AcModeCapabilities acCapabilities = TadoApiTypeUtils.getModeCapabilities( - (AirConditioningCapabilities) capabilities, ((CoolingZoneSetting) setting).getMode()); + AcMode acMode = ((CoolingZoneSetting) setting).getMode(); + AcModeCapabilities acModeCapabilities = acMode == null ? new AcModeCapabilities() + : TadoApiTypeUtils.getModeCapabilities(acMode, capabilities); - if (acCapabilities != null) { - Channel channel; - - // update the options list of supported fan levels - channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL); - List fanLevels = acCapabilities.getFanLevel(); - if (channel != null && fanLevels != null) { + // update the options list of supported fan levels + Channel channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL); + if (channel != null) { + List fanLevels = acModeCapabilities.getFanLevel(); + if (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 horizontalSwings = acCapabilities.getHorizontalSwing(); - if (channel != null && horizontalSwings != null) { + // update the options list of supported horizontal swing settings + channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING); + if (channel != null) { + List horizontalSwings = acModeCapabilities.getHorizontalSwing(); + if (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 verticalSwings = acCapabilities.getVerticalSwing(); - if (channel != null && verticalSwings != null) { + // update the options list of supported vertical swing settings + channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING); + if (channel != null) { + List verticalSwings = acModeCapabilities.getVerticalSwing(); + if (verticalSwings != null) { stateDescriptionProvider.setStateOptions(channel.getUID(), verticalSwings.stream() .map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList())); } @@ -366,8 +403,9 @@ public class TadoZoneHandler extends BaseHomeThingHandler { } private void scheduleZoneStateUpdate() { + ScheduledFuture refreshTimer = this.refreshTimer; if (refreshTimer == null || refreshTimer.isCancelled()) { - refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() { + this.refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() { @Override public void run() { updateZoneState(false); @@ -377,21 +415,26 @@ public class TadoZoneHandler extends BaseHomeThingHandler { } private void cancelScheduledZoneStateUpdate() { + ScheduledFuture refreshTimer = this.refreshTimer; if (refreshTimer != null) { refreshTimer.cancel(false); } } private void scheduleHvacChange() { + ScheduledFuture scheduledHvacChange = this.scheduledHvacChange; if (scheduledHvacChange != null) { scheduledHvacChange.cancel(false); } - - scheduledHvacChange = scheduler.schedule(() -> { + this.scheduledHvacChange = scheduler.schedule(() -> { try { - TadoHvacChange change = this.pendingHvacChange; - this.pendingHvacChange = new TadoHvacChange(getThing()); - change.apply(); + synchronized (this) { + TadoHvacChange pendingHvacChange = this.pendingHvacChange; + this.pendingHvacChange = new TadoHvacChange(getThing()); + if (pendingHvacChange != null) { + pendingHvacChange.apply(); + } + } } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (ApiException e) { @@ -403,9 +446,32 @@ public class TadoZoneHandler extends BaseHomeThingHandler { }, configuration.hvacChangeDebounce, TimeUnit.SECONDS); } - private void updateStateIfNotNull(String channelID, State state) { - if (state != null) { - updateState(channelID, state); + /** + * Helper method to log an API transaction on the given object. + * If the logger level is 'debug', the transaction is simply logged. + * If the logger level is 'trace, the object's JSON serial contents are included. + * + * @param object the object to be logged. + * @param isCommand marks whether the transaction is a command to, or a response from, the server. + */ + private void logApiTransaction(Object object, boolean isCommand) { + if (logger.isDebugEnabled() || logger.isTraceEnabled()) { + String logType = isCommand ? "command" : "response"; + if (logger.isTraceEnabled()) { + logger.trace("Api {}: homeId:{}, zoneId:{}, objectId:{}, content:\n{}", logType, getHomeId(), + getZoneId(), object.getClass().getSimpleName(), convertToJsonString(object)); + } else if (logger.isDebugEnabled()) { + logger.debug("Api {}: homeId:{}, zoneId:{}, objectId:{}", logType, getHomeId(), getZoneId(), + object.getClass().getSimpleName()); + } } } + + private synchronized String convertToJsonString(Object object) { + Gson gson = this.gson; + if (gson == null) { + gson = this.gson = GsonBuilderFactory.defaultGsonBuilder().setPrettyPrinting().create(); + } + return gson.toJson(object); + } }