[tado] Fix AC control bugs; Add null annotations and checks (#12570)

* [tado] add null annotations and checks
* [tado] cosmetics
* [tado] tweak homeid
* [tado] revert int to Integer
* [tado] explicit method public declaration
* [tado] remove "Bridge not initialized" exception on shutdown
* [tado] new state values: error if unsupported; don't force defaults
* [tado] adopt reviewer suggestion
* [tado] tweaks
* [tado] use new,current,default mode for target capabilities; cosmetics
* [tado] eliminate dead code
* [tado] set or defaults
* [tado] change 'nullable Integer' to 'int'
* [tado] eliminate warning if handler already disposed
* [tado] improve json logging
* [tado] fix getTargetTemperature if state is OFF
* [tado] add null checks
* [tado] log warning instead of throwing exception
* [tado] improved json trace logging
* [tado] adopt reviewer suggestion
* [tado] fix logging to trace
* [tado] thread synch, and fix 'this' assignments on futures
* [tado] tweak yaml
* [tado] fix verticalSwing
* [tado] fix fanLevel

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
Andrew Fiddian-Green 2022-05-24 21:09:03 +01:00 committed by GitHub
parent 55beb413bd
commit 3ee741e63b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 533 additions and 376 deletions

View File

@ -1050,16 +1050,16 @@ definitions:
type: array type: array
items: items:
$ref: "#/definitions/ACFanLevel" $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: verticalSwing:
description: Cooling system vertical swing modes. (Tado confusingly names this array without an 's') description: Cooling system vertical swing modes. (Tado confusingly names this array without an 's')
type: array type: array
items: items:
$ref: "#/definitions/ACVerticalSwing" $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: HeatingCapabilities:
x-discriminator-value: HEATING x-discriminator-value: HEATING

View File

@ -14,6 +14,7 @@ package org.openhab.binding.tado.internal;
import java.io.IOException; 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.FanLevel;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; 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.builder.ZoneStateProvider;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler; import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.ThingHandler;
/** /**
* Builder for incremental creation of zone overlays. * Builder for incremental creation of zone overlays.
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoHvacChange { public class TadoHvacChange {
private TadoZoneHandler zoneHandler;
private final TadoZoneHandler zoneHandler;
private final TerminationConditionBuilder terminationConditionBuilder;
private final ZoneSettingsBuilder settingsBuilder;
private boolean followSchedule = false; private boolean followSchedule = false;
private TerminationConditionBuilder terminationConditionBuilder;
private ZoneSettingsBuilder settingsBuilder;
public TadoHvacChange(Thing zoneThing) { 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); throw new IllegalArgumentException("TadoZoneThing expected, but instead got " + zoneThing);
} }
zoneHandler = (TadoZoneHandler) handler;
this.zoneHandler = (TadoZoneHandler) zoneThing.getHandler(); terminationConditionBuilder = TerminationConditionBuilder.of(zoneHandler);
this.terminationConditionBuilder = TerminationConditionBuilder.of(zoneHandler); settingsBuilder = ZoneSettingsBuilder.of(zoneHandler);
this.settingsBuilder = ZoneSettingsBuilder.of(zoneHandler);
} }
public TadoHvacChange withOperationMode(OperationMode operationMode) { public TadoHvacChange withOperationMode(OperationMode operationMode) {
@ -60,12 +64,11 @@ public class TadoHvacChange {
case MANUAL: case MANUAL:
return activeForever(); return activeForever();
case TIMER: case TIMER:
return activeFor(null); return activeForMinutes(0);
case UNTIL_CHANGE: case UNTIL_CHANGE:
return activeUntilChange(); return activeUntilChange();
default:
return this;
} }
return this;
} }
public TadoHvacChange followSchedule() { public TadoHvacChange followSchedule() {
@ -83,9 +86,9 @@ public class TadoHvacChange {
return this; return this;
} }
public TadoHvacChange activeFor(Integer minutes) { public TadoHvacChange activeForMinutes(int minutes) {
terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.TIMER); terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.TIMER);
terminationConditionBuilder.withTimerDurationInSeconds(minutes != null ? minutes * 60 : null); terminationConditionBuilder.withTimerDurationInSeconds(minutes * 60);
return this; return this;
} }

View File

@ -16,6 +16,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.OffsetDateTime; 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.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode; import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit; 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.HotWaterZoneSetting;
import org.openhab.binding.tado.internal.api.model.Overlay; 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.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.Power;
import org.openhab.binding.tado.internal.api.model.SensorDataPoints; import org.openhab.binding.tado.internal.api.model.SensorDataPoints;
import org.openhab.binding.tado.internal.api.model.TadoSystemType; 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 * @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
* *
*/ */
@NonNullByDefault
public class TadoZoneStateAdapter { public class TadoZoneStateAdapter {
private ZoneState zoneState; private final ZoneState zoneState;
private TemperatureUnit temperatureUnit; private final TemperatureUnit temperatureUnit;
public TadoZoneStateAdapter(ZoneState zoneState, TemperatureUnit temperatureUnit) { public TadoZoneStateAdapter(ZoneState zoneState, TemperatureUnit temperatureUnit) {
this.zoneState = zoneState; this.zoneState = zoneState;
@ -69,10 +72,9 @@ public class TadoZoneStateAdapter {
return toTemperatureState(sensorDataPoints.getInsideTemperature(), temperatureUnit); return toTemperatureState(sensorDataPoints.getInsideTemperature(), temperatureUnit);
} }
public DecimalType getHumidity() { public State getHumidity() {
SensorDataPoints sensorDataPoints = zoneState.getSensorDataPoints(); PercentageDataPoint humidity = zoneState.getSensorDataPoints().getHumidity();
return sensorDataPoints.getHumidity() != null ? toDecimalType(sensorDataPoints.getHumidity().getPercentage()) return humidity != null ? toDecimalType(humidity.getPercentage()) : UnDefType.UNDEF;
: null;
} }
public DecimalType getHeatingPower() { public DecimalType getHeatingPower() {
@ -81,7 +83,7 @@ public class TadoZoneStateAdapter {
: DecimalType.ZERO; : DecimalType.ZERO;
} }
public OnOffType getAcPower() { public State getAcPower() {
ActivityDataPoints dataPoints = zoneState.getActivityDataPoints(); ActivityDataPoints dataPoints = zoneState.getActivityDataPoints();
AcPowerDataPoint acPower = dataPoints.getAcPower(); AcPowerDataPoint acPower = dataPoints.getAcPower();
if (acPower != null) { if (acPower != null) {
@ -90,7 +92,7 @@ public class TadoZoneStateAdapter {
return OnOffType.from(acPowerValue); return OnOffType.from(acPowerValue);
} }
} }
return null; return UnDefType.UNDEF;
} }
public StringType getMode() { public StringType getMode() {
@ -106,6 +108,9 @@ public class TadoZoneStateAdapter {
} }
public State getTargetTemperature() { public State getTargetTemperature() {
if (!isPowerOn()) {
return UnDefType.UNDEF;
}
switch (zoneState.getSetting().getType()) { switch (zoneState.getSetting().getType()) {
case HEATING: case HEATING:
return toTemperatureState(((HeatingZoneSetting) zoneState.getSetting()).getTemperature(), return toTemperatureState(((HeatingZoneSetting) zoneState.getSetting()).getTemperature(),
@ -228,20 +233,12 @@ public class TadoZoneStateAdapter {
} }
private static State toTemperatureState(TemperatureObject temperature, TemperatureUnit temperatureUnit) { private static State toTemperatureState(TemperatureObject temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return UnDefType.NULL;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT return temperatureUnit == TemperatureUnit.FAHRENHEIT
? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT) ? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT)
: new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS); : new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS);
} }
private static State toTemperatureState(TemperatureDataPoint temperature, TemperatureUnit temperatureUnit) { private static State toTemperatureState(TemperatureDataPoint temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return UnDefType.NULL;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT return temperatureUnit == TemperatureUnit.FAHRENHEIT
? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT) ? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT)
: new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS); : new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS);

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.tado.internal.api; 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.Authorizer;
import org.openhab.binding.tado.internal.api.auth.OAuthAuthorizer; import org.openhab.binding.tado.internal.api.auth.OAuthAuthorizer;
import org.openhab.binding.tado.internal.api.client.HomeApi; import org.openhab.binding.tado.internal.api.client.HomeApi;
@ -23,6 +24,7 @@ import com.google.gson.Gson;
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class HomeApiFactory { public class HomeApiFactory {
private static final String OAUTH_SCOPE = "home.user"; private static final String OAUTH_SCOPE = "home.user";
private static final String OAUTH_CLIENT_ID = "public-api-preview"; private static final String OAUTH_CLIENT_ID = "public-api-preview";

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.tado.internal.api; 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.FanLevel;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; 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.AcMode;
import org.openhab.binding.tado.internal.api.model.AcModeCapabilities; 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.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.ManualTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition; import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionTemplate; 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 * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoApiTypeUtils { public class TadoApiTypeUtils {
public static OverlayTerminationCondition getTerminationCondition(OverlayTerminationConditionType type, public static OverlayTerminationCondition getTerminationCondition(OverlayTerminationConditionType type,
Integer timerDurationInSeconds) { int timerDurationInSeconds) {
switch (type) { switch (type) {
case TIMER: case TIMER:
return timerTermination(timerDurationInSeconds); return timerTermination(timerDurationInSeconds);
@ -50,26 +55,38 @@ public class TadoApiTypeUtils {
case TADO_MODE: case TADO_MODE:
return tadoModeTermination(); return tadoModeTermination();
default: default:
return null; throw new IllegalArgumentException("Unexpected OverlayTerminationConditionType " + type);
} }
} }
public static OverlayTerminationCondition cleanTerminationCondition( public static OverlayTerminationCondition cleanTerminationCondition(
OverlayTerminationCondition terminationCondition) { OverlayTerminationCondition terminationCondition) {
Integer timerDuration = terminationCondition.getType() == OverlayTerminationConditionType.TIMER OverlayTerminationConditionType conditionType = terminationCondition.getType();
? ((TimerTerminationCondition) terminationCondition).getRemainingTimeInSeconds()
: null;
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( public static OverlayTerminationCondition terminationConditionTemplateToTerminationCondition(
OverlayTerminationConditionTemplate template) { OverlayTerminationConditionTemplate template) {
Integer timerDuration = template.getType() == OverlayTerminationConditionType.TIMER OverlayTerminationConditionType conditionType = template.getType();
? ((TimerTerminationConditionTemplate) template).getDurationInSeconds()
: null;
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) { public static TimerTerminationCondition timerTermination(int durationInSeconds) {
@ -103,18 +120,10 @@ public class TadoApiTypeUtils {
} }
public static Float getTemperatureInUnit(TemperatureObject temperature, TemperatureUnit temperatureUnit) { public static Float getTemperatureInUnit(TemperatureObject temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return null;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT ? temperature.getFahrenheit() : temperature.getCelsius(); return temperatureUnit == TemperatureUnit.FAHRENHEIT ? temperature.getFahrenheit() : temperature.getCelsius();
} }
public static AcMode getAcMode(HvacMode mode) { public static AcMode getAcMode(HvacMode mode) {
if (mode == null) {
return null;
}
switch (mode) { switch (mode) {
case HEAT: case HEAT:
return AcMode.HEAT; return AcMode.HEAT;
@ -127,15 +136,11 @@ public class TadoApiTypeUtils {
case AUTO: case AUTO:
return AcMode.AUTO; return AcMode.AUTO;
default: default:
return null; throw new IllegalArgumentException("Unexpected AcMode " + mode);
} }
} }
public static AcFanSpeed getAcFanSpeed(FanSpeed fanSpeed) { public static AcFanSpeed getAcFanSpeed(FanSpeed fanSpeed) {
if (fanSpeed == null) {
return null;
}
switch (fanSpeed) { switch (fanSpeed) {
case AUTO: case AUTO:
return AcFanSpeed.AUTO; return AcFanSpeed.AUTO;
@ -145,16 +150,12 @@ public class TadoApiTypeUtils {
return AcFanSpeed.MIDDLE; return AcFanSpeed.MIDDLE;
case LOW: case LOW:
return AcFanSpeed.LOW; return AcFanSpeed.LOW;
default:
throw new IllegalArgumentException("Unexpected AcFanSpeed " + fanSpeed);
} }
return null;
} }
public static ACFanLevel getFanLevel(FanLevel fanLevel) { public static ACFanLevel getFanLevel(FanLevel fanLevel) {
if (fanLevel == null) {
return null;
}
switch (fanLevel) { switch (fanLevel) {
case AUTO: case AUTO:
return ACFanLevel.AUTO; return ACFanLevel.AUTO;
@ -170,16 +171,12 @@ public class TadoApiTypeUtils {
return ACFanLevel.LEVEL5; return ACFanLevel.LEVEL5;
case SILENT: case SILENT:
return ACFanLevel.SILENT; return ACFanLevel.SILENT;
default:
throw new IllegalArgumentException("Unexpected FanLevel " + fanLevel);
} }
return null;
} }
public static ACHorizontalSwing getHorizontalSwing(HorizontalSwing horizontalSwing) { public static ACHorizontalSwing getHorizontalSwing(HorizontalSwing horizontalSwing) {
if (horizontalSwing == null) {
return null;
}
switch (horizontalSwing) { switch (horizontalSwing) {
case LEFT: case LEFT:
return ACHorizontalSwing.LEFT; return ACHorizontalSwing.LEFT;
@ -197,16 +194,12 @@ public class TadoApiTypeUtils {
return ACHorizontalSwing.OFF; return ACHorizontalSwing.OFF;
case AUTO: case AUTO:
return ACHorizontalSwing.AUTO; return ACHorizontalSwing.AUTO;
default:
throw new IllegalArgumentException("Unexpected HorizontalSwing " + horizontalSwing);
} }
return null;
} }
public static ACVerticalSwing getVerticalSwing(VerticalSwing verticalSwing) { public static ACVerticalSwing getVerticalSwing(VerticalSwing verticalSwing) {
if (verticalSwing == null) {
return null;
}
switch (verticalSwing) { switch (verticalSwing) {
case AUTO: case AUTO:
return ACVerticalSwing.AUTO; return ACVerticalSwing.AUTO;
@ -224,34 +217,34 @@ public class TadoApiTypeUtils {
return ACVerticalSwing.ON; return ACVerticalSwing.ON;
case OFF: case OFF:
return ACVerticalSwing.OFF; return ACVerticalSwing.OFF;
default:
throw new IllegalArgumentException("Unexpected VerticalSwing " + verticalSwing);
} }
return null;
} }
public static AcModeCapabilities getModeCapabilities(AirConditioningCapabilities capabilities, AcMode mode) { public static AcModeCapabilities getModeCapabilities(AcMode acMode,
AcModeCapabilities modeCapabilities = null; @Nullable GenericZoneCapabilities capabilities) {
AirConditioningCapabilities acCapabilities;
if (mode != null) { if (capabilities instanceof AirConditioningCapabilities) {
switch (mode) { acCapabilities = (AirConditioningCapabilities) capabilities;
case COOL: } else {
modeCapabilities = capabilities.getCOOL(); acCapabilities = new AirConditioningCapabilities();
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;
}
} }
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);
}
} }
} }

View File

@ -17,16 +17,21 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*;
import java.io.IOException; import java.io.IOException;
import java.util.List; 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.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit; 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.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.ACFanLevel;
import org.openhab.binding.tado.internal.api.model.ACHorizontalSwing; 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.ACVerticalSwing;
import org.openhab.binding.tado.internal.api.model.AcFanSpeed; 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.AcMode;
import org.openhab.binding.tado.internal.api.model.AcModeCapabilities; 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.CoolingZoneSetting;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities; import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting; import org.openhab.binding.tado.internal.api.model.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.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureObject; import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.api.model.TemperatureRange; import org.openhab.binding.tado.internal.api.model.TemperatureRange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* *
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder { public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final AcMode DEFAULT_MODE = AcMode.COOL; private static final AcMode DEFAULT_MODE = AcMode.COOL;
private static final float DEFAULT_TEMPERATURE_C = 20.0f; private static final float DEFAULT_TEMPERATURE_C = 20.0f;
private static final float DEFAULT_TEMPERATURE_F = 68.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 @Override
public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities genericCapabilities) public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities genericCapabilities)
throws IOException, ApiException { throws IOException, ApiException {
@ -53,95 +64,138 @@ public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
return coolingSetting(false); return coolingSetting(false);
} }
CoolingZoneSetting setting = coolingSetting(true); CoolingZoneSetting newSetting = coolingSetting(true);
setting.setMode(getAcMode(mode));
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) { if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit)); newSetting.setTemperature(temperature(temperature, temperatureUnit));
} }
Boolean swing = this.swing;
if (swing != null) { 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) { 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) { 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) { if (fanLevel != null) {
setting.setFanLevel(getFanLevel(fanLevel)); ACFanLevel targetFanLevel = getFanLevel(fanLevel);
List<ACFanLevel> 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) { if (horizontalSwing != null) {
setting.setHorizontalSwing(getHorizontalSwing(horizontalSwing)); ACHorizontalSwing targetHorizontalSwing = getHorizontalSwing(horizontalSwing);
List<ACHorizontalSwing> 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) { if (verticalSwing != null) {
setting.setVerticalSwing(getVerticalSwing(verticalSwing)); ACVerticalSwing targetVerticalSwing = getVerticalSwing(verticalSwing);
List<ACVerticalSwing> 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, private void addMissingSettingParts(ZoneStateProvider zoneStateProvider,
GenericZoneCapabilities genericCapabilities, CoolingZoneSetting setting) throws IOException, ApiException { GenericZoneCapabilities genericCapabilities, CoolingZoneSetting newSetting)
if (setting.getMode() == null) { throws IOException, ApiException {
if (newSetting.getMode() == null) {
AcMode targetMode = getCurrentOrDefaultAcMode(zoneStateProvider); AcMode targetMode = getCurrentOrDefaultAcMode(zoneStateProvider);
setting.setMode(targetMode); newSetting.setMode(targetMode);
} }
AcModeCapabilities capabilities = getModeCapabilities((AirConditioningCapabilities) genericCapabilities, AcModeCapabilities targetCapabilities = getModeCapabilities(newSetting.getMode(), genericCapabilities);
setting.getMode());
TemperatureRange temperatures = capabilities.getTemperatures(); TemperatureRange temperatures = targetCapabilities.getTemperatures();
if (temperatures != null && setting.getTemperature() == null) { if (temperatures != null && newSetting.getTemperature() == null) {
setting.setTemperature(getCurrentOrDefaultTemperature(zoneStateProvider, temperatures)); newSetting.setTemperature(getCurrentOrDefaultTemperature(zoneStateProvider, temperatures));
} }
List<AcFanSpeed> fanSpeeds = capabilities.getFanSpeeds(); List<AcFanSpeed> fanSpeeds = targetCapabilities.getFanSpeeds();
if (fanSpeeds != null && !fanSpeeds.isEmpty() && setting.getFanSpeed() == null) { if (fanSpeeds != null && !fanSpeeds.isEmpty() && newSetting.getFanSpeed() == null) {
setting.setFanSpeed(getCurrentOrDefaultFanSpeed(zoneStateProvider, fanSpeeds)); newSetting.setFanSpeed(getCurrentOrDefaultFanSpeed(zoneStateProvider, fanSpeeds));
} }
List<Power> swings = capabilities.getSwings(); List<Power> swings = targetCapabilities.getSwings();
if (swings != null && !swings.isEmpty() && setting.getSwing() == null) { if (swings != null && !swings.isEmpty() && newSetting.getSwing() == null) {
setting.setSwing(getCurrentOrDefaultSwing(zoneStateProvider, swings)); newSetting.setSwing(getCurrentOrDefaultSwing(zoneStateProvider, swings));
} }
// Tado confusingly calls the List / getter method 'fanLevel' / 'getFanLevel()' without 's' List<ACFanLevel> fanLevels = targetCapabilities.getFanLevel();
List<ACFanLevel> fanLevels = capabilities.getFanLevel(); if (fanLevels != null && !fanLevels.isEmpty() && newSetting.getFanLevel() == null) {
if (fanLevels != null && !fanLevels.isEmpty() && setting.getFanLevel() == null) { newSetting.setFanLevel(getCurrentOrDefaultFanLevel(zoneStateProvider, fanLevels));
setting.setFanLevel(getCurrentOrDefaultFanLevel(zoneStateProvider, fanLevels));
} }
// Tado confusingly calls the List / getter method 'horizontalSwing' / 'getHorizontalSwing()' without 's' List<ACHorizontalSwing> horizontalSwings = targetCapabilities.getHorizontalSwing();
List<ACHorizontalSwing> horizontalSwings = capabilities.getHorizontalSwing(); if (horizontalSwings != null && !horizontalSwings.isEmpty() && newSetting.getHorizontalSwing() == null) {
if (horizontalSwings != null && !horizontalSwings.isEmpty() && setting.getHorizontalSwing() == null) { newSetting.setHorizontalSwing(getCurrentOrDefaultHorizontalSwing(zoneStateProvider, horizontalSwings));
setting.setHorizontalSwing(getCurrentOrDefaultHorizontalSwing(zoneStateProvider, horizontalSwings));
} }
// Tado confusingly calls the List / getter method 'verticalSwing' / 'getVerticalSwing()' without 's' List<ACVerticalSwing> verticalSwings = targetCapabilities.getVerticalSwing();
List<ACVerticalSwing> verticalSwings = capabilities.getVerticalSwing(); if (verticalSwings != null && !verticalSwings.isEmpty() && newSetting.getVerticalSwing() == null) {
if (verticalSwings != null && !verticalSwings.isEmpty() && setting.getVerticalSwing() == null) { newSetting.setVerticalSwing(getCurrentOrDefaultVerticalSwing(zoneStateProvider, verticalSwings));
setting.setVerticalSwing(getCurrentOrDefaultVerticalSwing(zoneStateProvider, verticalSwings));
} }
// Tado confusingly calls the List / getter method 'light' / 'getLight()' without 's' List<Power> lights = targetCapabilities.getLight();
List<Power> lights = capabilities.getLight(); if (lights != null && !lights.isEmpty() && newSetting.getLight() == null) {
if (lights != null && !lights.isEmpty() && setting.getLight() == null) { newSetting.setLight(getCurrentOrDefaultLight(zoneStateProvider, lights));
setting.setLight(getCurrentOrDefaultLight(zoneStateProvider, lights));
} }
} }
private AcMode getCurrentOrDefaultAcMode(ZoneStateProvider zoneStateProvider) throws IOException, ApiException { private AcMode getCurrentOrDefaultAcMode(ZoneStateProvider zoneStateProvider) throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); AcMode acMode = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getMode();
return acMode != null ? acMode : DEFAULT_MODE;
return zoneSetting.getMode() != null ? zoneSetting.getMode() : DEFAULT_MODE;
} }
private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider, private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider,
@ -165,68 +219,40 @@ public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
private AcFanSpeed getCurrentOrDefaultFanSpeed(ZoneStateProvider zoneStateProvider, List<AcFanSpeed> fanSpeeds) private AcFanSpeed getCurrentOrDefaultFanSpeed(ZoneStateProvider zoneStateProvider, List<AcFanSpeed> fanSpeeds)
throws IOException, ApiException { throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); AcFanSpeed fanSpeed = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getFanSpeed();
return (fanSpeed != null) && fanSpeeds.contains(fanSpeed) ? fanSpeed : fanSpeeds.get(0);
if (zoneSetting.getFanSpeed() != null && fanSpeeds.contains(zoneSetting.getFanSpeed())) {
return zoneSetting.getFanSpeed();
}
return fanSpeeds.get(0);
} }
private Power getCurrentOrDefaultSwing(ZoneStateProvider zoneStateProvider, List<Power> swings) private Power getCurrentOrDefaultSwing(ZoneStateProvider zoneStateProvider, List<Power> swings)
throws IOException, ApiException { throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); Power swing = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getSwing();
return (swing != null) && swings.contains(swing) ? swing : swings.get(0);
if (zoneSetting.getSwing() != null && swings.contains(zoneSetting.getSwing())) {
return zoneSetting.getSwing();
}
return swings.get(0);
}
private Power getCurrentOrDefaultLight(ZoneStateProvider zoneStateProvider, List<Power> lights)
throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting.getLight() != null && lights.contains(zoneSetting.getLight())) {
return zoneSetting.getLight();
}
return lights.get(0);
} }
private ACFanLevel getCurrentOrDefaultFanLevel(ZoneStateProvider zoneStateProvider, List<ACFanLevel> fanLevels) private ACFanLevel getCurrentOrDefaultFanLevel(ZoneStateProvider zoneStateProvider, List<ACFanLevel> fanLevels)
throws IOException, ApiException { throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); ACFanLevel fanLevel = ((CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting()).getFanLevel();
return (fanLevel != null) && fanLevels.contains(fanLevel) ? fanLevel : fanLevels.get(0);
if (zoneSetting.getFanLevel() != null && fanLevels.contains(zoneSetting.getFanLevel())) {
return zoneSetting.getFanLevel();
}
return fanLevels.get(0);
}
private ACHorizontalSwing getCurrentOrDefaultHorizontalSwing(ZoneStateProvider zoneStateProvider,
List<ACHorizontalSwing> horizontalSwings) throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting.getHorizontalSwing() != null && horizontalSwings.contains(zoneSetting.getHorizontalSwing())) {
return zoneSetting.getHorizontalSwing();
}
return horizontalSwings.get(0);
} }
private ACVerticalSwing getCurrentOrDefaultVerticalSwing(ZoneStateProvider zoneStateProvider, private ACVerticalSwing getCurrentOrDefaultVerticalSwing(ZoneStateProvider zoneStateProvider,
List<ACVerticalSwing> verticalSwings) throws IOException, ApiException { List<ACVerticalSwing> vertSwings) throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting(); 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())) { private ACHorizontalSwing getCurrentOrDefaultHorizontalSwing(ZoneStateProvider zoneStateProvider,
return zoneSetting.getVerticalSwing(); List<ACHorizontalSwing> 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<Power> 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) { private CoolingZoneSetting coolingSetting(boolean powerOn) {

View File

@ -16,6 +16,7 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature
import java.io.IOException; 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.FanLevel;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; 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 * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder { public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final float DEFAULT_TEMPERATURE_C = 22.0f; private static final float DEFAULT_TEMPERATURE_C = 22.0f;
private static final float DEFAULT_TEMPERATURE_F = 72.0f; private static final float DEFAULT_TEMPERATURE_F = 72.0f;
@ -77,6 +79,7 @@ public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder {
HeatingZoneSetting setting = heatingSetting(true); HeatingZoneSetting setting = heatingSetting(true);
Float temperature = this.temperature;
if (temperature != null) { if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit)); setting.setTemperature(temperature(temperature, temperatureUnit));
} }

View File

@ -16,6 +16,7 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature
import java.io.IOException; 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.FanLevel;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; 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 * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder { public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final float DEFAULT_TEMPERATURE_C = 50.0f; private static final float DEFAULT_TEMPERATURE_C = 50.0f;
private static final float DEFAULT_TEMPERATURE_F = 122.0f; private static final float DEFAULT_TEMPERATURE_F = 122.0f;
@ -78,6 +80,7 @@ public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder {
HotWaterZoneSetting setting = hotWaterSetting(true); HotWaterZoneSetting setting = hotWaterSetting(true);
Float temperature = this.temperature;
if (temperature != null) { if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit)); setting.setTemperature(temperature(temperature, temperatureUnit));
} }

View File

@ -16,6 +16,8 @@ import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*;
import java.io.IOException; 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.ApiException;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition; import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType; 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 * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TerminationConditionBuilder { public class TerminationConditionBuilder {
private TadoZoneHandler zoneHandler;
private OverlayTerminationConditionType terminationType = null; private final TadoZoneHandler zoneHandler;
private Integer timerDurationInSeconds = null;
private @Nullable OverlayTerminationConditionType terminationType;
private int timerDurationInSeconds = 0;
protected TerminationConditionBuilder(TadoZoneHandler zoneHandler) { protected TerminationConditionBuilder(TadoZoneHandler zoneHandler) {
this.zoneHandler = zoneHandler; this.zoneHandler = zoneHandler;
@ -45,23 +49,23 @@ public class TerminationConditionBuilder {
public TerminationConditionBuilder withTerminationType(OverlayTerminationConditionType terminationType) { public TerminationConditionBuilder withTerminationType(OverlayTerminationConditionType terminationType) {
this.terminationType = terminationType; this.terminationType = terminationType;
if (terminationType != OverlayTerminationConditionType.TIMER) { if (terminationType != OverlayTerminationConditionType.TIMER) {
timerDurationInSeconds = null; timerDurationInSeconds = 0;
} }
return this; return this;
} }
public TerminationConditionBuilder withTimerDurationInSeconds(Integer timerDurationInSeconds) { public TerminationConditionBuilder withTimerDurationInSeconds(int timerDurationInSeconds) {
this.terminationType = OverlayTerminationConditionType.TIMER; this.terminationType = OverlayTerminationConditionType.TIMER;
this.timerDurationInSeconds = timerDurationInSeconds; this.timerDurationInSeconds = timerDurationInSeconds;
return this; return this;
} }
public OverlayTerminationCondition build(ZoneStateProvider zoneStateProvider) throws IOException, ApiException { public OverlayTerminationCondition build(ZoneStateProvider zoneStateProvider) throws IOException, ApiException {
OverlayTerminationCondition terminationCondition = null; OverlayTerminationCondition terminationCondition;
OverlayTerminationConditionType terminationType = this.terminationType;
if (terminationType != null) { if (terminationType != null) {
if (terminationType != OverlayTerminationConditionType.TIMER || timerDurationInSeconds != null) { if (terminationType != OverlayTerminationConditionType.TIMER || timerDurationInSeconds > 0) {
terminationCondition = getTerminationCondition(terminationType, timerDurationInSeconds); terminationCondition = getTerminationCondition(terminationType, timerDurationInSeconds);
} else { } else {
terminationCondition = getCurrentOrDefaultTimerTermination(zoneStateProvider); terminationCondition = getCurrentOrDefaultTimerTermination(zoneStateProvider);
@ -75,18 +79,19 @@ public class TerminationConditionBuilder {
terminationCondition = getDefaultTerminationCondition(); terminationCondition = getDefaultTerminationCondition();
} }
} }
return terminationCondition; return terminationCondition;
} }
private OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException { private OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
OverlayTerminationCondition defaultTerminationCondition = zoneHandler.getDefaultTerminationCondition(); OverlayTerminationCondition defaultTerminationCondition = zoneHandler.getDefaultTerminationCondition();
return defaultTerminationCondition != null ? defaultTerminationCondition : manualTermination(); return defaultTerminationCondition;
} }
private TimerTerminationCondition getCurrentOrDefaultTimerTermination(ZoneStateProvider zoneStateProvider) private TimerTerminationCondition getCurrentOrDefaultTimerTermination(ZoneStateProvider zoneStateProvider)
throws IOException, ApiException { throws IOException, ApiException {
// Timer without duration // Timer without duration
int duration = zoneHandler.getFallbackTimerDuration() * 60; Integer duration = zoneHandler.getFallbackTimerDuration() * 60;
ZoneState zoneState = zoneStateProvider.getZoneState(); ZoneState zoneState = zoneStateProvider.getZoneState();

View File

@ -14,13 +14,15 @@ package org.openhab.binding.tado.internal.builder;
import java.io.IOException; 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.FanLevel;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed; import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing; import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode; import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit; import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing; 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.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities; import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting; import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
@ -32,12 +34,12 @@ import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public abstract class ZoneSettingsBuilder { public abstract class ZoneSettingsBuilder {
public static ZoneSettingsBuilder of(TadoZoneHandler zoneHandler) { public static ZoneSettingsBuilder of(TadoZoneHandler zoneHandler) {
TadoBindingConstants.ZoneType zoneType = zoneHandler.getZoneType(); ZoneType zoneType = zoneHandler.getZoneType();
if (zoneType == null) {
throw new IllegalArgumentException("Zone type is null");
}
switch (zoneType) { switch (zoneType) {
case HEATING: case HEATING:
return new HeatingZoneSettingsBuilder(); return new HeatingZoneSettingsBuilder();
@ -46,19 +48,19 @@ public abstract class ZoneSettingsBuilder {
case HOT_WATER: case HOT_WATER:
return new HotWaterZoneSettingsBuilder(); return new HotWaterZoneSettingsBuilder();
default: 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 TemperatureUnit temperatureUnit = TemperatureUnit.CELSIUS;
protected Boolean swing = null; protected @Nullable Float temperature;
protected Boolean light = null; protected @Nullable HvacMode mode;
protected FanSpeed fanSpeed = null; protected @Nullable Boolean swing;
protected FanLevel fanLevel = null; protected @Nullable Boolean light;
protected HorizontalSwing horizontalSwing = null; protected @Nullable FanSpeed fanSpeed;
protected VerticalSwing verticalSwing = null; protected @Nullable FanLevel fanLevel;
protected @Nullable HorizontalSwing horizontalSwing;
protected @Nullable VerticalSwing verticalSwing;
public ZoneSettingsBuilder withMode(HvacMode mode) { public ZoneSettingsBuilder withMode(HvacMode mode) {
this.mode = mode; this.mode = mode;
@ -100,10 +102,6 @@ public abstract class ZoneSettingsBuilder {
throws IOException, ApiException; throws IOException, ApiException;
protected TemperatureObject truncateTemperature(TemperatureObject temperature) { protected TemperatureObject truncateTemperature(TemperatureObject temperature) {
if (temperature == null) {
return null;
}
TemperatureObject temperatureObject = new TemperatureObject(); TemperatureObject temperatureObject = new TemperatureObject();
if (temperatureUnit == TemperatureUnit.FAHRENHEIT) { if (temperatureUnit == TemperatureUnit.FAHRENHEIT) {
temperatureObject.setFahrenheit(temperature.getFahrenheit()); temperatureObject.setFahrenheit(temperature.getFahrenheit());

View File

@ -14,6 +14,8 @@ package org.openhab.binding.tado.internal.builder;
import java.io.IOException; 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.ApiException;
import org.openhab.binding.tado.internal.api.model.ZoneState; import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler; 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 * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class ZoneStateProvider { public class ZoneStateProvider {
private TadoZoneHandler zoneHandler; private final TadoZoneHandler zoneHandler;
private ZoneState zoneState; private @Nullable ZoneState zoneState;
public ZoneStateProvider(TadoZoneHandler zoneHandler) { public ZoneStateProvider(TadoZoneHandler zoneHandler) {
this.zoneHandler = zoneHandler; this.zoneHandler = zoneHandler;
} }
ZoneState getZoneState() throws IOException, ApiException { public synchronized ZoneState getZoneState() throws IOException, ApiException {
if (this.zoneState == null) { ZoneState zoneState = this.zoneState;
ZoneState retrievedZoneState = zoneHandler.getZoneState(); if (zoneState == null) {
// empty zone state behaves like a NULL object zoneState = this.zoneState = zoneHandler.getZoneState();
this.zoneState = retrievedZoneState != null ? retrievedZoneState : new ZoneState();
} }
return zoneState;
return this.zoneState;
} }
} }

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.tado.internal.config; package org.openhab.binding.tado.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Holder-object for home configuration * Holder-object for home configuration
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoHomeConfig { public class TadoHomeConfig {
public String username; public String username = "";
public String password; public String password = "";
} }

View File

@ -12,11 +12,14 @@
*/ */
package org.openhab.binding.tado.internal.config; package org.openhab.binding.tado.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Holder-object for mobile device configuration * Holder-object for mobile device configuration
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoMobileDeviceConfig { public class TadoMobileDeviceConfig {
public int id; public int id;
public int refreshInterval; public int refreshInterval;

View File

@ -12,11 +12,14 @@
*/ */
package org.openhab.binding.tado.internal.config; package org.openhab.binding.tado.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Holder-object for zone configuration * Holder-object for zone configuration
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoZoneConfig { public class TadoZoneConfig {
public long id; public long id;
public int refreshInterval; public int refreshInterval;

View File

@ -25,6 +25,8 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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.TadoBindingConstants;
import org.openhab.binding.tado.internal.api.ApiException; import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.MobileDevice; import org.openhab.binding.tado.internal.api.model.MobileDevice;
@ -43,13 +45,14 @@ import org.slf4j.LoggerFactory;
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoDiscoveryService extends AbstractDiscoveryService { public class TadoDiscoveryService extends AbstractDiscoveryService {
private static final int TIMEOUT = 5; private static final int TIMEOUT = 5;
private static final long REFRESH = 600; private static final long REFRESH = 600;
private final Logger logger = LoggerFactory.getLogger(TadoDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(TadoDiscoveryService.class);
private ScheduledFuture<?> discoveryFuture; private @Nullable ScheduledFuture<?> discoveryFuture;
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_ZONE, THING_TYPE_MOBILE_DEVICE).collect(Collectors.toSet())); .unmodifiableSet(Stream.of(THING_TYPE_ZONE, THING_TYPE_MOBILE_DEVICE).collect(Collectors.toSet()));
@ -83,23 +86,30 @@ public class TadoDiscoveryService extends AbstractDiscoveryService {
@Override @Override
protected void startBackgroundDiscovery() { protected void startBackgroundDiscovery() {
logger.debug("Start Tado background discovery"); logger.debug("Start Tado background discovery");
ScheduledFuture<?> discoveryFuture = this.discoveryFuture;
if (discoveryFuture == null || discoveryFuture.isCancelled()) { if (discoveryFuture == null || discoveryFuture.isCancelled()) {
logger.debug("Start Scan"); logger.debug("Start Scan");
discoveryFuture = scheduler.scheduleWithFixedDelay(this::startScan, 30, REFRESH, TimeUnit.SECONDS); this.discoveryFuture = scheduler.scheduleWithFixedDelay(this::startScan, 30, REFRESH, TimeUnit.SECONDS);
} }
} }
@Override @Override
protected void stopBackgroundDiscovery() { protected void stopBackgroundDiscovery() {
logger.debug("Stop Tado background discovery"); logger.debug("Stop Tado background discovery");
ScheduledFuture<?> discoveryFuture = this.discoveryFuture;
if (discoveryFuture != null && !discoveryFuture.isCancelled()) { if (discoveryFuture != null && !discoveryFuture.isCancelled()) {
discoveryFuture.cancel(true); discoveryFuture.cancel(true);
discoveryFuture = null;
} }
} }
private void discoverZones() { private void discoverZones() {
Long homeId = homeHandler.getHomeId(); Long homeId = homeHandler.getHomeId();
if (homeId == null) {
logger.debug("Could not discover tado zones: Missing home id");
return;
}
try { try {
List<Zone> zoneList = homeHandler.getApi().listZones(homeId); List<Zone> zoneList = homeHandler.getApi().listZones(homeId);
@ -132,6 +142,12 @@ public class TadoDiscoveryService extends AbstractDiscoveryService {
private void discoverMobileDevices() { private void discoverMobileDevices() {
Long homeId = homeHandler.getHomeId(); Long homeId = homeHandler.getHomeId();
if (homeId == null) {
logger.debug("Could not discover mobile devices: Missing home id");
return;
}
try { try {
List<MobileDevice> mobileDeviceList = homeHandler.getApi().listMobileDevices(homeId); List<MobileDevice> mobileDeviceList = homeHandler.getApi().listMobileDevices(homeId);
@ -143,7 +159,7 @@ public class TadoDiscoveryService extends AbstractDiscoveryService {
} }
} }
} catch (IOException | ApiException e) { } catch (IOException | ApiException e) {
logger.debug("Could not discover tado zones: {}", e.getMessage(), e); logger.debug("Could not discover mobile devices: {}", e.getMessage(), e);
} }
} }

View File

@ -12,36 +12,47 @@
*/ */
package org.openhab.binding.tado.internal.handler; 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.binding.tado.internal.api.client.HomeApi;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
/** /**
* Common base class for home-based thing-handler. * Common base class for home-based thing-handler.
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public abstract class BaseHomeThingHandler extends BaseThingHandler { public abstract class BaseHomeThingHandler extends BaseThingHandler {
public BaseHomeThingHandler(Thing thing) { public BaseHomeThingHandler(Thing thing) {
super(thing); super(thing);
} }
public Long getHomeId() { public @Nullable Long getHomeId() {
TadoHomeHandler handler = getHomeHandler(); TadoHomeHandler handler = getHomeHandler();
return handler != null ? handler.getHomeId() : Long.valueOf(0); return handler.getHomeId();
} }
protected TadoHomeHandler getHomeHandler() { protected TadoHomeHandler getHomeHandler() {
Bridge bridge = getBridge(); 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() { protected HomeApi getApi() {
TadoHomeHandler handler = getHomeHandler(); TadoHomeHandler handler = getHomeHandler();
return handler != null ? handler.getApi() : null; return handler.getApi();
} }
protected void onSuccessfulOperation() { protected void onSuccessfulOperation() {

View File

@ -19,6 +19,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; 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.ApiException;
import org.openhab.binding.tado.internal.api.model.ControlDevice; import org.openhab.binding.tado.internal.api.model.ControlDevice;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -32,14 +33,16 @@ import org.slf4j.LoggerFactory;
* devices. * devices.
* *
* @author Andrew Fiddian-Green - Initial contribution * @author Andrew Fiddian-Green - Initial contribution
* *
*/ */
@NonNullByDefault
public class TadoBatteryChecker { public class TadoBatteryChecker {
private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class); private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class);
private Map<Long, State> zoneList = new HashMap<>(); private final Map<Long, State> zoneList = new HashMap<>();
private final TadoHomeHandler homeHandler;
private Date refreshTime = new Date(); private Date refreshTime = new Date();
private TadoHomeHandler homeHandler;
public TadoBatteryChecker(TadoHomeHandler homeHandler) { public TadoBatteryChecker(TadoHomeHandler homeHandler) {
this.homeHandler = homeHandler; this.homeHandler = homeHandler;
@ -47,7 +50,7 @@ public class TadoBatteryChecker {
private synchronized void refreshZoneList() { private synchronized void refreshZoneList() {
Date now = new Date(); 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 // be frugal, we only need to refresh the battery state hourly
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
calendar.setTime(now); calendar.setTime(now);

View File

@ -22,6 +22,8 @@ import java.util.Hashtable;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.binding.tado.internal.discovery.TadoDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
@ -42,6 +44,7 @@ import org.osgi.service.component.annotations.Reference;
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
@Component(configurationPid = "binding.tado", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.tado", service = ThingHandlerFactory.class)
public class TadoHandlerFactory extends BaseThingHandlerFactory { public class TadoHandlerFactory extends BaseThingHandlerFactory {
@ -63,7 +66,7 @@ public class TadoHandlerFactory extends BaseThingHandlerFactory {
} }
@Override @Override
protected ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_HOME)) { if (thingTypeUID.equals(THING_TYPE_HOME)) {

View File

@ -13,9 +13,12 @@
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.List;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; 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;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit; import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.ApiException; 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.HomeState;
import org.openhab.binding.tado.internal.api.model.PresenceState; 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.User;
import org.openhab.binding.tado.internal.api.model.UserHomes;
import org.openhab.binding.tado.internal.config.TadoHomeConfig; import org.openhab.binding.tado.internal.config.TadoHomeConfig;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge; 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.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State; 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;
@ -44,21 +49,23 @@ import org.slf4j.LoggerFactory;
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoHomeHandler extends BaseBridgeHandler { public class TadoHomeHandler extends BaseBridgeHandler {
private Logger logger = LoggerFactory.getLogger(TadoHomeHandler.class); private Logger logger = LoggerFactory.getLogger(TadoHomeHandler.class);
private TadoHomeConfig configuration; private TadoHomeConfig configuration;
private HomeApi api; private final HomeApi api;
private Long homeId;
private TadoBatteryChecker batteryChecker; private @Nullable Long homeId;
private @Nullable TadoBatteryChecker batteryChecker;
private ScheduledFuture<?> initializationFuture; private @Nullable ScheduledFuture<?> initializationFuture;
public TadoHomeHandler(Bridge bridge) { public TadoHomeHandler(Bridge bridge) {
super(bridge); super(bridge);
batteryChecker = new TadoBatteryChecker(this); batteryChecker = new TadoBatteryChecker(this);
configuration = getConfigAs(TadoHomeConfig.class);
api = new HomeApiFactory().create(configuration.username, configuration.password);
} }
public TemperatureUnit getTemperatureUnit() { public TemperatureUnit getTemperatureUnit() {
@ -70,11 +77,10 @@ public class TadoHomeHandler extends BaseBridgeHandler {
@Override @Override
public void initialize() { public void initialize() {
configuration = getConfigAs(TadoHomeConfig.class); configuration = getConfigAs(TadoHomeConfig.class);
api = new HomeApiFactory().create(configuration.username, configuration.password); ScheduledFuture<?> initializationFuture = this.initializationFuture;
if (initializationFuture == null || initializationFuture.isDone()) {
if (this.initializationFuture == null || this.initializationFuture.isDone()) { this.initializationFuture = scheduler.scheduleWithFixedDelay(
initializationFuture = scheduler.scheduleWithFixedDelay(this::initializeBridgeStatusAndPropertiesIfOffline, this::initializeBridgeStatusAndPropertiesIfOffline, 0, 300, TimeUnit.SECONDS);
0, 300, TimeUnit.SECONDS);
} }
} }
@ -93,13 +99,20 @@ public class TadoHomeHandler extends BaseBridgeHandler {
return; return;
} }
if (user.getHomes().isEmpty()) { List<UserHomes> homes = user.getHomes();
if (homes == null || homes.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"User does not have access to any home"); "User does not have access to any home");
return; 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); HomeInfo homeInfo = api.showHome(homeId);
TemperatureUnit temperatureUnit = org.openhab.binding.tado.internal.api.model.TemperatureUnit.FAHRENHEIT == homeInfo TemperatureUnit temperatureUnit = org.openhab.binding.tado.internal.api.model.TemperatureUnit.FAHRENHEIT == homeInfo
@ -118,9 +131,9 @@ public class TadoHomeHandler extends BaseBridgeHandler {
@Override @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
if (this.initializationFuture != null || !this.initializationFuture.isDone()) { ScheduledFuture<?> initializationFuture = this.initializationFuture;
this.initializationFuture.cancel(true); if (initializationFuture != null && !initializationFuture.isCancelled()) {
this.initializationFuture = null; initializationFuture.cancel(true);
} }
} }
@ -128,13 +141,12 @@ public class TadoHomeHandler extends BaseBridgeHandler {
return api; return api;
} }
public Long getHomeId() { public @Nullable Long getHomeId() {
return homeId; return homeId;
} }
public HomeState getHomeState() throws IOException, ApiException { public HomeState getHomeState() throws IOException, ApiException {
HomeApi api = getApi(); return api.homeState(getHomeId());
return api != null ? api.homeState(getHomeId()) : null;
} }
public void updateHomeState() { public void updateHomeState() {
@ -173,6 +185,7 @@ public class TadoHomeHandler extends BaseBridgeHandler {
} }
public State getBatteryLowAlarm(long zoneId) { public State getBatteryLowAlarm(long zoneId) {
return batteryChecker.getBatteryLowAlarm(zoneId); TadoBatteryChecker batteryChecker = this.batteryChecker;
return batteryChecker != null ? batteryChecker.getBatteryLowAlarm(zoneId) : UnDefType.UNDEF;
} }
} }

View File

@ -16,6 +16,8 @@ import java.io.IOException;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; 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;
import org.openhab.binding.tado.internal.api.ApiException; import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.MobileDevice; import org.openhab.binding.tado.internal.api.model.MobileDevice;
@ -37,15 +39,17 @@ import org.slf4j.LoggerFactory;
* *
* @author Dennis Frommknecht - Initial contribution * @author Dennis Frommknecht - Initial contribution
*/ */
@NonNullByDefault
public class TadoMobileDeviceHandler extends BaseHomeThingHandler { public class TadoMobileDeviceHandler extends BaseHomeThingHandler {
private Logger logger = LoggerFactory.getLogger(TadoMobileDeviceHandler.class); private Logger logger = LoggerFactory.getLogger(TadoMobileDeviceHandler.class);
private TadoMobileDeviceConfig configuration; private TadoMobileDeviceConfig configuration;
private ScheduledFuture<?> refreshTimer; private @Nullable ScheduledFuture<?> refreshTimer;
public TadoMobileDeviceHandler(Thing thing) { public TadoMobileDeviceHandler(Thing thing) {
super(thing); super(thing);
configuration = getConfigAs(TadoMobileDeviceConfig.class);
} }
@Override @Override
@ -61,7 +65,6 @@ public class TadoMobileDeviceHandler extends BaseHomeThingHandler {
@Override @Override
public void initialize() { public void initialize() {
configuration = getConfigAs(TadoMobileDeviceConfig.class); configuration = getConfigAs(TadoMobileDeviceConfig.class);
if (configuration.refreshInterval <= 0) { if (configuration.refreshInterval <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone " updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
+ configuration.id + " of home " + getHomeId() + " must be greater than zero"); + configuration.id + " of home " + getHomeId() + " must be greater than zero");
@ -135,13 +138,15 @@ public class TadoMobileDeviceHandler extends BaseHomeThingHandler {
} }
private void scheduleZoneStateUpdate() { private void scheduleZoneStateUpdate() {
ScheduledFuture<?> refreshTimer = this.refreshTimer;
if (refreshTimer == null || refreshTimer.isCancelled()) { if (refreshTimer == null || refreshTimer.isCancelled()) {
refreshTimer = scheduler.scheduleWithFixedDelay(this::updateState, 5, configuration.refreshInterval, this.refreshTimer = scheduler.scheduleWithFixedDelay(this::updateState, 5, configuration.refreshInterval,
TimeUnit.SECONDS); TimeUnit.SECONDS);
} }
} }
private void cancelScheduledStateUpdate() { private void cancelScheduledStateUpdate() {
ScheduledFuture<?> refreshTimer = this.refreshTimer;
if (refreshTimer != null) { if (refreshTimer != null) {
refreshTimer.cancel(false); refreshTimer.cancel(false);
} }

View File

@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import javax.measure.quantity.Temperature; import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
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;
@ -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.TadoHvacChange;
import org.openhab.binding.tado.internal.adapter.TadoZoneStateAdapter; import org.openhab.binding.tado.internal.adapter.TadoZoneStateAdapter;
import org.openhab.binding.tado.internal.api.ApiException; 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.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.ACFanLevel;
import org.openhab.binding.tado.internal.api.model.ACHorizontalSwing; 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.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.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.CoolingZoneSetting;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities; import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting; import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
@ -65,11 +66,12 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo; import org.openhab.core.thing.ThingStatusInfo;
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.StateOption; import org.openhab.core.types.StateOption;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/** /**
* The {@link TadoZoneHandler} is responsible for handling commands of zones and update their state. * 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 * @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
* *
*/ */
@NonNullByDefault
public class TadoZoneHandler extends BaseHomeThingHandler { public class TadoZoneHandler extends BaseHomeThingHandler {
private Logger logger = LoggerFactory.getLogger(TadoZoneHandler.class); private Logger logger = LoggerFactory.getLogger(TadoZoneHandler.class);
private final TadoStateDescriptionProvider stateDescriptionProvider; private final TadoStateDescriptionProvider stateDescriptionProvider;
private TadoZoneConfig configuration; private TadoZoneConfig configuration;
private ScheduledFuture<?> refreshTimer;
private ScheduledFuture<?> scheduledHvacChange; private @Nullable ScheduledFuture<?> refreshTimer;
private GenericZoneCapabilities capabilities; private @Nullable ScheduledFuture<?> scheduledHvacChange;
TadoHvacChange pendingHvacChange; private @Nullable GenericZoneCapabilities capabilities;
private @Nullable TadoHvacChange pendingHvacChange;
private boolean disposing = false;
private @Nullable Gson gson;
public TadoZoneHandler(Thing thing, TadoStateDescriptionProvider stateDescriptionProvider) { public TadoZoneHandler(Thing thing, TadoStateDescriptionProvider stateDescriptionProvider) {
super(thing); super(thing);
this.stateDescriptionProvider = stateDescriptionProvider; this.stateDescriptionProvider = stateDescriptionProvider;
configuration = getConfigAs(TadoZoneConfig.class);
} }
public long getZoneId() { public long getZoneId() {
return this.configuration.id; return configuration.id;
} }
public int getFallbackTimerDuration() { public int getFallbackTimerDuration() {
return this.configuration.fallbackTimerDuration; return configuration.fallbackTimerDuration;
} }
public @Nullable ZoneType getZoneType() { public ZoneType getZoneType() {
String zoneTypeStr = this.thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE); String zoneTypeStr = thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE);
return zoneTypeStr != null ? ZoneType.valueOf(zoneTypeStr) : null; if (zoneTypeStr == null) {
throw new IllegalStateException("Zone type not initialized");
}
return ZoneType.valueOf(zoneTypeStr);
} }
public OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException { public OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
OverlayTemplate overlayTemplate = getApi().showZoneDefaultOverlay(getHomeId(), getZoneId()); OverlayTemplate overlayTemplate = getApi().showZoneDefaultOverlay(getHomeId(), getZoneId());
logApiTransaction(overlayTemplate, false);
return terminationConditionTemplateToTerminationCondition(overlayTemplate.getTerminationCondition()); return terminationConditionTemplateToTerminationCondition(overlayTemplate.getTerminationCondition());
} }
public ZoneState getZoneState() throws IOException, ApiException { public ZoneState getZoneState() throws IOException, ApiException {
HomeApi api = getApi(); ZoneState zoneState = getApi().showZoneState(getHomeId(), getZoneId());
return api != null ? api.showZoneState(getHomeId(), getZoneId()) : null; logApiTransaction(zoneState, false);
return zoneState;
} }
public GenericZoneCapabilities getZoneCapabilities() { 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() { public TemperatureUnit getTemperatureUnit() {
@ -125,9 +141,17 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
} }
public Overlay setOverlay(Overlay overlay) throws IOException, ApiException { public Overlay setOverlay(Overlay overlay) throws IOException, ApiException {
logger.debug("Setting overlay of home {} and zone {} with overlay: {}", getHomeId(), getZoneId(), try {
overlay.toString()); logApiTransaction(overlay, true);
return getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay); 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 { public void removeOverlay() throws IOException, ApiException {
@ -144,65 +168,75 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
return; return;
} }
switch (id) { synchronized (this) {
case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE: TadoHvacChange pendingHvacChange = this.pendingHvacChange;
pendingHvacChange.withHvacMode(((StringType) command).toFullString()); if (pendingHvacChange == null) {
scheduleHvacChange(); throw new IllegalStateException("Zone pendingHvacChange not initialized");
break; }
case TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE:
QuantityType<Temperature> state = (QuantityType<Temperature>) command;
QuantityType<Temperature> stateInTargetUnit = getTemperatureUnit() == TemperatureUnit.FAHRENHEIT
? state.toUnit(ImperialUnits.FAHRENHEIT)
: state.toUnit(SIUnits.CELSIUS);
if (stateInTargetUnit != null) { switch (id) {
pendingHvacChange.withTemperature(stateInTargetUnit.floatValue()); case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE:
pendingHvacChange.withHvacMode(((StringType) command).toFullString());
scheduleHvacChange(); scheduleHvacChange();
} break;
case TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE:
if (command instanceof QuantityType<?>) {
@SuppressWarnings("unchecked")
QuantityType<Temperature> state = (QuantityType<Temperature>) command;
QuantityType<Temperature> stateInTargetUnit = getTemperatureUnit() == TemperatureUnit.FAHRENHEIT
? state.toUnit(ImperialUnits.FAHRENHEIT)
: state.toUnit(SIUnits.CELSIUS);
break; if (stateInTargetUnit != null) {
case TadoBindingConstants.CHANNEL_ZONE_SWING: pendingHvacChange.withTemperature(stateInTargetUnit.floatValue());
pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON); scheduleHvacChange();
scheduleHvacChange(); }
break; }
case TadoBindingConstants.CHANNEL_ZONE_LIGHT: break;
pendingHvacChange.withLight(((OnOffType) command) == OnOffType.ON); case TadoBindingConstants.CHANNEL_ZONE_SWING:
scheduleHvacChange(); pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON);
break; scheduleHvacChange();
case TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED: break;
pendingHvacChange.withFanSpeed(((StringType) command).toFullString()); case TadoBindingConstants.CHANNEL_ZONE_LIGHT:
scheduleHvacChange(); pendingHvacChange.withLight(((OnOffType) command) == OnOffType.ON);
break; scheduleHvacChange();
case TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL: break;
String fanLevelString = ((StringType) command).toFullString(); case TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED:
pendingHvacChange.withFanLevel(FanLevel.valueOf(fanLevelString.toUpperCase())); pendingHvacChange.withFanSpeed(((StringType) command).toFullString());
break; scheduleHvacChange();
case TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING: break;
String horizontalSwingString = ((StringType) command).toFullString(); case TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL:
pendingHvacChange.withHorizontalSwing(HorizontalSwing.valueOf(horizontalSwingString.toUpperCase())); String fanLevelString = ((StringType) command).toFullString();
scheduleHvacChange(); pendingHvacChange.withFanLevel(FanLevel.valueOf(fanLevelString.toUpperCase()));
break; scheduleHvacChange();
case TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING: break;
String verticalSwingString = ((StringType) command).toFullString(); case TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING:
pendingHvacChange.withVerticalSwing(VerticalSwing.valueOf(verticalSwingString.toUpperCase())); String horizontalSwingString = ((StringType) command).toFullString();
scheduleHvacChange(); pendingHvacChange.withHorizontalSwing(HorizontalSwing.valueOf(horizontalSwingString.toUpperCase()));
break; scheduleHvacChange();
case TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE: break;
String operationMode = ((StringType) command).toFullString(); case TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING:
pendingHvacChange.withOperationMode(OperationMode.valueOf(operationMode)); String verticalSwingString = ((StringType) command).toFullString();
scheduleHvacChange(); pendingHvacChange.withVerticalSwing(VerticalSwing.valueOf(verticalSwingString.toUpperCase()));
break; scheduleHvacChange();
case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION: break;
pendingHvacChange.activeFor(((DecimalType) command).intValue()); case TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE:
scheduleHvacChange(); String operationMode = ((StringType) command).toFullString();
break; pendingHvacChange.withOperationMode(OperationMode.valueOf(operationMode));
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION:
pendingHvacChange.activeForMinutes(((DecimalType) command).intValue());
scheduleHvacChange();
break;
}
} }
} }
@Override @Override
public void initialize() { public void initialize() {
disposing = false;
configuration = getConfigAs(TadoZoneConfig.class); configuration = getConfigAs(TadoZoneConfig.class);
if (configuration.refreshInterval <= 0) { if (configuration.refreshInterval <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone " updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
+ getZoneId() + " of home " + getHomeId() + " must be greater than zero"); + getZoneId() + " of home " + getHomeId() + " must be greater than zero");
@ -225,6 +259,7 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
@Override @Override
public void dispose() { public void dispose() {
disposing = true;
cancelScheduledZoneStateUpdate(); cancelScheduledZoneStateUpdate();
} }
@ -233,7 +268,10 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) { if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
try { try {
Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId()); Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId());
logApiTransaction(zoneDetails, false);
GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId()); GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId());
logApiTransaction(capabilities, false);
if (zoneDetails == null || capabilities == null) { if (zoneDetails == null || capabilities == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, 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_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;
logger.debug("Got capabilities: {}", capabilities.toString());
} 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());
@ -263,12 +300,14 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
} }
private void updateZoneState(boolean forceUpdate) { private void updateZoneState(boolean forceUpdate) {
TadoHomeHandler home = getHomeHandler(); if ((thing.getStatus() != ThingStatus.ONLINE) || disposing) {
if (home != null) { return;
home.updateHomeState();
} }
getHomeHandler().updateHomeState();
// No update during HVAC change debounce // No update during HVAC change debounce
ScheduledFuture<?> scheduledHvacChange = this.scheduledHvacChange;
if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) { if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) {
return; return;
} }
@ -276,18 +315,14 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
try { try {
ZoneState zoneState = getZoneState(); ZoneState zoneState = getZoneState();
if (zoneState == null) {
return;
}
logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId()); logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId());
TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit()); TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature()); updateState(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity()); updateState(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower()); updateState(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower()); updateState(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower());
updateState(TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE, state.getOperationMode()); 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()); "Could not connect to server due to " + e.getMessage());
} }
if (home != null) { updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM,
updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM, home.getBatteryLowAlarm(getZoneId())); getHomeHandler().getBatteryLowAlarm(getZoneId()));
}
} }
/** /**
@ -333,32 +367,35 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
return; return;
} }
AcModeCapabilities acCapabilities = TadoApiTypeUtils.getModeCapabilities( AcMode acMode = ((CoolingZoneSetting) setting).getMode();
(AirConditioningCapabilities) capabilities, ((CoolingZoneSetting) setting).getMode()); AcModeCapabilities acModeCapabilities = acMode == null ? new AcModeCapabilities()
: TadoApiTypeUtils.getModeCapabilities(acMode, capabilities);
if (acCapabilities != null) { // update the options list of supported fan levels
Channel channel; Channel channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL);
if (channel != null) {
// update the options list of supported fan levels List<ACFanLevel> fanLevels = acModeCapabilities.getFanLevel();
channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL); if (fanLevels != null) {
List<ACFanLevel> fanLevels = acCapabilities.getFanLevel();
if (channel != null && fanLevels != null) {
stateDescriptionProvider.setStateOptions(channel.getUID(), stateDescriptionProvider.setStateOptions(channel.getUID(),
fanLevels.stream().map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList())); fanLevels.stream().map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
} }
}
// update the options list of supported horizontal swing settings // update the options list of supported horizontal swing settings
channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING); channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING);
List<ACHorizontalSwing> horizontalSwings = acCapabilities.getHorizontalSwing(); if (channel != null) {
if (channel != null && horizontalSwings != null) { List<ACHorizontalSwing> horizontalSwings = acModeCapabilities.getHorizontalSwing();
if (horizontalSwings != null) {
stateDescriptionProvider.setStateOptions(channel.getUID(), horizontalSwings.stream() stateDescriptionProvider.setStateOptions(channel.getUID(), horizontalSwings.stream()
.map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList())); .map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
} }
}
// update the options list of supported vertical swing settings // update the options list of supported vertical swing settings
channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING); channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING);
List<ACVerticalSwing> verticalSwings = acCapabilities.getVerticalSwing(); if (channel != null) {
if (channel != null && verticalSwings != null) { List<ACVerticalSwing> verticalSwings = acModeCapabilities.getVerticalSwing();
if (verticalSwings != null) {
stateDescriptionProvider.setStateOptions(channel.getUID(), verticalSwings.stream() stateDescriptionProvider.setStateOptions(channel.getUID(), verticalSwings.stream()
.map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList())); .map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
} }
@ -366,8 +403,9 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
} }
private void scheduleZoneStateUpdate() { private void scheduleZoneStateUpdate() {
ScheduledFuture<?> refreshTimer = this.refreshTimer;
if (refreshTimer == null || refreshTimer.isCancelled()) { if (refreshTimer == null || refreshTimer.isCancelled()) {
refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() { this.refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() {
@Override @Override
public void run() { public void run() {
updateZoneState(false); updateZoneState(false);
@ -377,21 +415,26 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
} }
private void cancelScheduledZoneStateUpdate() { private void cancelScheduledZoneStateUpdate() {
ScheduledFuture<?> refreshTimer = this.refreshTimer;
if (refreshTimer != null) { if (refreshTimer != null) {
refreshTimer.cancel(false); refreshTimer.cancel(false);
} }
} }
private void scheduleHvacChange() { private void scheduleHvacChange() {
ScheduledFuture<?> scheduledHvacChange = this.scheduledHvacChange;
if (scheduledHvacChange != null) { if (scheduledHvacChange != null) {
scheduledHvacChange.cancel(false); scheduledHvacChange.cancel(false);
} }
this.scheduledHvacChange = scheduler.schedule(() -> {
scheduledHvacChange = scheduler.schedule(() -> {
try { try {
TadoHvacChange change = this.pendingHvacChange; synchronized (this) {
this.pendingHvacChange = new TadoHvacChange(getThing()); TadoHvacChange pendingHvacChange = this.pendingHvacChange;
change.apply(); this.pendingHvacChange = new TadoHvacChange(getThing());
if (pendingHvacChange != null) {
pendingHvacChange.apply();
}
}
} catch (IOException e) { } catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (ApiException e) { } catch (ApiException e) {
@ -403,9 +446,32 @@ public class TadoZoneHandler extends BaseHomeThingHandler {
}, configuration.hvacChangeDebounce, TimeUnit.SECONDS); }, configuration.hvacChangeDebounce, TimeUnit.SECONDS);
} }
private void updateStateIfNotNull(String channelID, State state) { /**
if (state != null) { * Helper method to log an API transaction on the given object.
updateState(channelID, state); * 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);
}
} }