added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openhab.binding.tado</groupId>
<artifactId>api-client</artifactId>
<version>1.3.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<pluginRepositories>
<pluginRepository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>io.swagger</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/tado-api.yaml</inputSpec>
<language>com.github.dfrommi.swagger.OpenHABClientGenerator</language>
<apiPackage>org.openhab.binding.tado.internal.api.client</apiPackage>
<modelPackage>org.openhab.binding.tado.internal.api.model</modelPackage>
<invokerPackage>org.openhab.binding.tado.internal.api</invokerPackage>
<configOptions>
<artifactId>tado</artifactId>
<artifactVersion>${project.version}</artifactVersion>
</configOptions>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.github.dfrommi</groupId>
<artifactId>swagger-codegen-openhab</artifactId>
<version>0.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>4.1.0</version>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
<outputDirectory>${project.basedir}/../../../lib</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>9.4.12.v20180830</version>
</dependency>
</dependencies>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.tado-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-tado" description="Tado Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tado/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link TadoBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Dennis Frommknecht - Initial contribution
* @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
*
*/
@NonNullByDefault
public class TadoBindingConstants {
public static final String BINDING_ID = "tado";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_HOME = new ThingTypeUID(BINDING_ID, "home");
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
public static final ThingTypeUID THING_TYPE_MOBILE_DEVICE = new ThingTypeUID(BINDING_ID, "mobiledevice");
// List of all Channel IDs
public static final String PROPERTY_HOME_TEMPERATURE_UNIT = "temperatureUnit";
public static enum TemperatureUnit {
CELSIUS,
FAHRENHEIT
}
public static final String CHANNEL_ZONE_CURRENT_TEMPERATURE = "currentTemperature";
public static final String CHANNEL_ZONE_HUMIDITY = "humidity";
public static final String CHANNEL_ZONE_HEATING_POWER = "heatingPower";
// air conditioning power
public static final String CHANNEL_ZONE_AC_POWER = "acPower";
public static final String CHANNEL_ZONE_HVAC_MODE = "hvacMode";
public static enum HvacMode {
OFF,
HEAT,
COOL,
DRY,
FAN,
AUTO
}
public static final String CHANNEL_ZONE_TARGET_TEMPERATURE = "targetTemperature";
public static final String CHANNEL_ZONE_SWING = "swing";
public static final String CHANNEL_ZONE_FAN_SPEED = "fanspeed";
public static enum FanSpeed {
LOW,
MIDDLE,
HIGH,
AUTO
}
public static final String CHANNEL_ZONE_OPERATION_MODE = "operationMode";
public static enum OperationMode {
SCHEDULE,
TIMER,
MANUAL,
UNTIL_CHANGE
}
public static final String CHANNEL_ZONE_TIMER_DURATION = "timerDuration";
public static final String CHANNEL_ZONE_OVERLAY_EXPIRY = "overlayExpiry";
// battery low alarm channel
public static final String CHANNEL_ZONE_BATTERY_LOW_ALARM = "batteryLowAlarm";
// open window detected channel
public static final String CHANNEL_ZONE_OPEN_WINDOW_DETECTED = "openWindowDetected";
public static final String CHANNEL_MOBILE_DEVICE_AT_HOME = "atHome";
// Configuration
public static final String CONFIG_ZONE_ID = "id";
public static final String CONFIG_MOBILE_DEVICE_ID = "id";
// Properties
public static final String PROPERTY_ZONE_NAME = "name";
public static final String PROPERTY_ZONE_TYPE = "type";
public static enum ZoneType {
HEATING,
AIR_CONDITIONING,
HOT_WATER
}
public static final String PROPERTY_MOBILE_DEVICE_NAME = "name";
}

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.Overlay;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.builder.TerminationConditionBuilder;
import org.openhab.binding.tado.internal.builder.ZoneSettingsBuilder;
import org.openhab.binding.tado.internal.builder.ZoneStateProvider;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
import org.openhab.core.thing.Thing;
/**
* Builder for incremental creation of zone overlays.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoHvacChange {
private TadoZoneHandler zoneHandler;
private boolean followSchedule = false;
private TerminationConditionBuilder terminationConditionBuilder;
private ZoneSettingsBuilder settingsBuilder;
public TadoHvacChange(Thing zoneThing) {
if (!(zoneThing.getHandler() instanceof TadoZoneHandler)) {
throw new IllegalArgumentException("TadoZoneThing expected, but instead got " + zoneThing);
}
this.zoneHandler = (TadoZoneHandler) zoneThing.getHandler();
this.terminationConditionBuilder = TerminationConditionBuilder.of(zoneHandler);
this.settingsBuilder = ZoneSettingsBuilder.of(zoneHandler);
}
public TadoHvacChange withOperationMode(OperationMode operationMode) {
switch (operationMode) {
case SCHEDULE:
return followSchedule();
case MANUAL:
return activeForever();
case TIMER:
return activeFor(null);
case UNTIL_CHANGE:
return activeUntilChange();
default:
return this;
}
}
public TadoHvacChange followSchedule() {
followSchedule = true;
return this;
}
public TadoHvacChange activeForever() {
terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.MANUAL);
return this;
}
public TadoHvacChange activeUntilChange() {
terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.TADO_MODE);
return this;
}
public TadoHvacChange activeFor(Integer minutes) {
terminationConditionBuilder.withTerminationType(OverlayTerminationConditionType.TIMER);
terminationConditionBuilder.withTimerDurationInSeconds(minutes != null ? minutes * 60 : null);
return this;
}
public TadoHvacChange withTemperature(float temperatureValue) {
settingsBuilder.withTemperature(temperatureValue, zoneHandler.getTemperatureUnit());
return this;
}
public TadoHvacChange withHvacMode(HvacMode mode) {
settingsBuilder.withMode(mode);
return this;
}
public TadoHvacChange withHvacMode(String mode) {
return withHvacMode(HvacMode.valueOf(mode.toUpperCase()));
}
public TadoHvacChange withSwing(boolean swingOn) {
settingsBuilder.withSwing(swingOn);
return this;
}
public TadoHvacChange withFanSpeed(FanSpeed fanSpeed) {
settingsBuilder.withFanSpeed(fanSpeed);
return this;
}
public TadoHvacChange withFanSpeed(String fanSpeed) {
withFanSpeed(FanSpeed.valueOf(fanSpeed.toUpperCase()));
return this;
}
public void apply() throws IOException, ApiException {
if (followSchedule) {
zoneHandler.removeOverlay();
} else {
Overlay overlay = buildOverlay();
zoneHandler.setOverlay(overlay);
}
}
private Overlay buildOverlay() throws IOException, ApiException {
ZoneStateProvider zoneStateProvider = new ZoneStateProvider(zoneHandler);
OverlayTerminationCondition terminationCondition = terminationConditionBuilder.build(zoneStateProvider);
GenericZoneSetting setting = settingsBuilder.build(zoneStateProvider, zoneHandler.getZoneCapabilities());
Overlay overlay = new Overlay();
overlay.setTermination(terminationCondition);
overlay.setSetting(setting);
return overlay;
}
}

View File

@@ -0,0 +1,237 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.adapter;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.model.AcPowerDataPoint;
import org.openhab.binding.tado.internal.api.model.ActivityDataPoints;
import org.openhab.binding.tado.internal.api.model.CoolingZoneSetting;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.HeatingZoneSetting;
import org.openhab.binding.tado.internal.api.model.HotWaterZoneSetting;
import org.openhab.binding.tado.internal.api.model.Overlay;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.api.model.Power;
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.TemperatureDataPoint;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.api.model.TimerTerminationCondition;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* Adapter from API-level zone state to the binding's item-based zone state.
*
* @author Dennis Frommknecht - Initial contribution
* @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
*
*/
public class TadoZoneStateAdapter {
private ZoneState zoneState;
private TemperatureUnit temperatureUnit;
public TadoZoneStateAdapter(ZoneState zoneState, TemperatureUnit temperatureUnit) {
this.zoneState = zoneState;
this.temperatureUnit = temperatureUnit;
}
public State getInsideTemperature() {
SensorDataPoints sensorDataPoints = zoneState.getSensorDataPoints();
return toTemperatureState(sensorDataPoints.getInsideTemperature(), temperatureUnit);
}
public DecimalType getHumidity() {
SensorDataPoints sensorDataPoints = zoneState.getSensorDataPoints();
return sensorDataPoints.getHumidity() != null ? toDecimalType(sensorDataPoints.getHumidity().getPercentage())
: null;
}
public DecimalType getHeatingPower() {
ActivityDataPoints dataPoints = zoneState.getActivityDataPoints();
return dataPoints.getHeatingPower() != null ? toDecimalType(dataPoints.getHeatingPower().getPercentage())
: DecimalType.ZERO;
}
public OnOffType getAcPower() {
ActivityDataPoints dataPoints = zoneState.getActivityDataPoints();
AcPowerDataPoint acPower = dataPoints.getAcPower();
if (acPower != null) {
String acPowerValue = acPower.getValue();
if (acPowerValue != null) {
return OnOffType.from(acPowerValue);
}
}
return null;
}
public StringType getMode() {
GenericZoneSetting setting = zoneState.getSetting();
if (!isPowerOn()) {
return StringType.valueOf(HvacMode.OFF.name());
} else if (setting.getType() == TadoSystemType.HEATING || setting.getType() == TadoSystemType.HOT_WATER) {
return StringType.valueOf(HvacMode.HEAT.name());
} else {
return StringType.valueOf(((CoolingZoneSetting) setting).getMode().getValue());
}
}
public State getTargetTemperature() {
switch (zoneState.getSetting().getType()) {
case HEATING:
return toTemperatureState(((HeatingZoneSetting) zoneState.getSetting()).getTemperature(),
temperatureUnit);
case AIR_CONDITIONING:
return toTemperatureState(((CoolingZoneSetting) zoneState.getSetting()).getTemperature(),
temperatureUnit);
case HOT_WATER:
return toTemperatureState(((HotWaterZoneSetting) zoneState.getSetting()).getTemperature(),
temperatureUnit);
default:
return UnDefType.UNDEF;
}
}
public State getFanSpeed() {
if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
CoolingZoneSetting setting = (CoolingZoneSetting) zoneState.getSetting();
return setting.getFanSpeed() != null ? StringType.valueOf(setting.getFanSpeed().getValue())
: UnDefType.NULL;
} else {
return UnDefType.UNDEF;
}
}
public State getSwing() {
if (zoneState.getSetting().getType() == TadoSystemType.AIR_CONDITIONING) {
CoolingZoneSetting setting = (CoolingZoneSetting) zoneState.getSetting();
if (setting.getSwing() == null) {
return UnDefType.NULL;
} else if (setting.getSwing() == Power.ON) {
return OnOffType.ON;
} else {
return OnOffType.OFF;
}
} else {
return UnDefType.UNDEF;
}
}
public StringType getOperationMode() {
Overlay overlay = zoneState.getOverlay();
if (overlay != null) {
switch (overlay.getTermination().getType()) {
case MANUAL:
return new StringType(OperationMode.MANUAL.name());
case TIMER:
return new StringType(OperationMode.TIMER.name());
case TADO_MODE:
return new StringType(OperationMode.UNTIL_CHANGE.name());
}
}
return new StringType(OperationMode.SCHEDULE.name());
}
public DecimalType getRemainingTimerDuration() {
Overlay overlay = zoneState.getOverlay();
if (overlay != null && overlay.getTermination().getType() == OverlayTerminationConditionType.TIMER) {
TimerTerminationCondition timerTerminationCondition = (TimerTerminationCondition) overlay.getTermination();
Integer remainingTimeInSeconds = timerTerminationCondition.getRemainingTimeInSeconds();
// If there's a timer overlay, then the timer should never be smaller than 1
return new DecimalType((int) Math.max(1.0, Math.round(remainingTimeInSeconds / 60.0)));
}
return new DecimalType();
}
public State getOverlayExpiration() {
Overlay overlay = zoneState.getOverlay();
if (overlay == null || overlay.getTermination().getProjectedExpiry() == null) {
return UnDefType.UNDEF;
}
return toDateTimeType(overlay.getTermination().getProjectedExpiry());
}
public boolean isPowerOn() {
Power power = Power.OFF;
GenericZoneSetting setting = zoneState.getSetting();
switch (setting.getType()) {
case HEATING:
power = ((HeatingZoneSetting) setting).getPower();
break;
case AIR_CONDITIONING:
power = ((CoolingZoneSetting) setting).getPower();
break;
case HOT_WATER:
power = ((HotWaterZoneSetting) setting).getPower();
break;
}
return power.getValue().equals("ON");
}
private static DecimalType toDecimalType(double value) {
BigDecimal decimal = new BigDecimal(value).setScale(2, BigDecimal.ROUND_HALF_UP);
return new DecimalType(decimal);
}
private static DateTimeType toDateTimeType(OffsetDateTime offsetDateTime) {
return new DateTimeType(offsetDateTime.toZonedDateTime());
}
private static State toTemperatureState(TemperatureObject temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return UnDefType.NULL;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT
? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT)
: new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS);
}
private static State toTemperatureState(TemperatureDataPoint temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return UnDefType.NULL;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT
? new QuantityType<>(temperature.getFahrenheit(), ImperialUnits.FAHRENHEIT)
: new QuantityType<>(temperature.getCelsius(), SIUnits.CELSIUS);
}
public State getOpenWindowDetected() {
Boolean openWindowDetected = zoneState.isOpenWindowDetected();
if (openWindowDetected != null) {
return OnOffType.from(openWindowDetected);
}
return OnOffType.OFF;
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.api;
import org.openhab.binding.tado.internal.api.auth.Authorizer;
import org.openhab.binding.tado.internal.api.auth.OAuthAuthorizer;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import com.google.gson.Gson;
/**
* Factory to create and configure {@link HomeApi} instances.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class HomeApiFactory {
private static final String OAUTH_SCOPE = "home.user";
private static final String OAUTH_CLIENT_ID = "public-api-preview";
private static final String OAUTH_CLIENT_SECRET = "4HJGRffVR8xb3XdEUQpjgZ1VplJi6Xgw";
public HomeApi create(String username, String password) {
Gson gson = GsonBuilderFactory.defaultGsonBuilder().create();
Authorizer authorizer = new OAuthAuthorizer().passwordFlow(username, password).clientId(OAUTH_CLIENT_ID)
.clientSecret(OAUTH_CLIENT_SECRET).scopes(OAUTH_SCOPE);
return new HomeApi(gson, authorizer);
}
}

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.api;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.model.AcFanSpeed;
import org.openhab.binding.tado.internal.api.model.AcMode;
import org.openhab.binding.tado.internal.api.model.AcModeCapabilities;
import org.openhab.binding.tado.internal.api.model.AirConditioningCapabilities;
import org.openhab.binding.tado.internal.api.model.ManualTerminationCondition;
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.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.api.model.TadoModeTerminationCondition;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.api.model.TimerTerminationCondition;
import org.openhab.binding.tado.internal.api.model.TimerTerminationConditionTemplate;
/**
* Utility methods for the conversion of API types.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoApiTypeUtils {
public static OverlayTerminationCondition getTerminationCondition(OverlayTerminationConditionType type,
Integer timerDurationInSeconds) {
switch (type) {
case TIMER:
return timerTermination(timerDurationInSeconds);
case MANUAL:
return manualTermination();
case TADO_MODE:
return tadoModeTermination();
default:
return null;
}
}
public static OverlayTerminationCondition cleanTerminationCondition(
OverlayTerminationCondition terminationCondition) {
Integer timerDuration = terminationCondition.getType() == OverlayTerminationConditionType.TIMER
? ((TimerTerminationCondition) terminationCondition).getRemainingTimeInSeconds()
: null;
return getTerminationCondition(terminationCondition.getType(), timerDuration);
}
public static OverlayTerminationCondition terminationConditionTemplateToTerminationCondition(
OverlayTerminationConditionTemplate template) {
Integer timerDuration = template.getType() == OverlayTerminationConditionType.TIMER
? ((TimerTerminationConditionTemplate) template).getDurationInSeconds()
: null;
return getTerminationCondition(template.getType(), timerDuration);
}
public static TimerTerminationCondition timerTermination(int durationInSeconds) {
TimerTerminationCondition terminationCondition = new TimerTerminationCondition();
terminationCondition.setType(OverlayTerminationConditionType.TIMER);
terminationCondition.setDurationInSeconds(durationInSeconds);
return terminationCondition;
}
public static ManualTerminationCondition manualTermination() {
ManualTerminationCondition terminationCondition = new ManualTerminationCondition();
terminationCondition.setType(OverlayTerminationConditionType.MANUAL);
return terminationCondition;
}
public static TadoModeTerminationCondition tadoModeTermination() {
TadoModeTerminationCondition terminationCondition = new TadoModeTerminationCondition();
terminationCondition.setType(OverlayTerminationConditionType.TADO_MODE);
return terminationCondition;
}
public static TemperatureObject temperature(float degree, TemperatureUnit temperatureUnit) {
TemperatureObject temperature = new TemperatureObject();
if (temperatureUnit == TemperatureUnit.FAHRENHEIT) {
temperature.setFahrenheit(degree);
} else {
temperature.setCelsius(degree);
}
return temperature;
}
public static Float getTemperatureInUnit(TemperatureObject temperature, TemperatureUnit temperatureUnit) {
if (temperature == null) {
return null;
}
return temperatureUnit == TemperatureUnit.FAHRENHEIT ? temperature.getFahrenheit() : temperature.getCelsius();
}
public static AcMode getAcMode(HvacMode mode) {
if (mode == null) {
return null;
}
switch (mode) {
case HEAT:
return AcMode.HEAT;
case COOL:
return AcMode.COOL;
case FAN:
return AcMode.FAN;
case DRY:
return AcMode.DRY;
case AUTO:
return AcMode.AUTO;
default:
return null;
}
}
public static AcFanSpeed getAcFanSpeed(FanSpeed fanSpeed) {
if (fanSpeed == null) {
return null;
}
switch (fanSpeed) {
case AUTO:
return AcFanSpeed.AUTO;
case HIGH:
return AcFanSpeed.HIGH;
case MIDDLE:
return AcFanSpeed.MIDDLE;
case LOW:
return AcFanSpeed.LOW;
}
return null;
}
public static AcModeCapabilities getModeCapabilities(AirConditioningCapabilities capabilities, AcMode mode) {
AcModeCapabilities modeCapabilities = null;
if (mode != null) {
switch (mode) {
case COOL:
modeCapabilities = capabilities.getCOOL();
break;
case HEAT:
modeCapabilities = capabilities.getHEAT();
break;
case DRY:
modeCapabilities = capabilities.getDRY();
break;
case AUTO:
modeCapabilities = capabilities.getAUTO();
break;
case FAN:
modeCapabilities = capabilities.getFAN();
break;
}
}
return modeCapabilities != null ? modeCapabilities : new AcModeCapabilities();
}
}

View File

@@ -0,0 +1,153 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*;
import java.io.IOException;
import java.util.List;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.AcFanSpeed;
import org.openhab.binding.tado.internal.api.model.AcMode;
import org.openhab.binding.tado.internal.api.model.AcModeCapabilities;
import org.openhab.binding.tado.internal.api.model.AirConditioningCapabilities;
import org.openhab.binding.tado.internal.api.model.CoolingZoneSetting;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.IntRange;
import org.openhab.binding.tado.internal.api.model.Power;
import org.openhab.binding.tado.internal.api.model.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.api.model.TemperatureRange;
/**
*
*
* @author Dennis Frommknecht - Initial contribution
*/
public class AirConditioningZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final AcMode DEFAULT_MODE = AcMode.COOL;
private static final float DEFAULT_TEMPERATURE_C = 20.0f;
private static final float DEFAULT_TEMPERATURE_F = 68.0f;
@Override
public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities genericCapabilities)
throws IOException, ApiException {
if (mode == HvacMode.OFF) {
return coolingSetting(false);
}
CoolingZoneSetting setting = coolingSetting(true);
setting.setMode(getAcMode(mode));
if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit));
}
if (swing != null) {
setting.setSwing(swing ? Power.ON : Power.OFF);
}
if (fanSpeed != null) {
setting.setFanSpeed(getAcFanSpeed(fanSpeed));
}
addMissingSettingParts(zoneStateProvider, genericCapabilities, setting);
return setting;
}
private void addMissingSettingParts(ZoneStateProvider zoneStateProvider,
GenericZoneCapabilities genericCapabilities, CoolingZoneSetting setting) throws IOException, ApiException {
if (setting.getMode() == null) {
AcMode targetMode = getCurrentOrDefaultAcMode(zoneStateProvider);
setting.setMode(targetMode);
}
AcModeCapabilities capabilities = getModeCapabilities((AirConditioningCapabilities) genericCapabilities,
setting.getMode());
if (capabilities.getTemperatures() != null && setting.getTemperature() == null) {
TemperatureObject targetTemperature = getCurrentOrDefaultTemperature(zoneStateProvider,
capabilities.getTemperatures());
setting.setTemperature(targetTemperature);
}
if (capabilities.getFanSpeeds() != null && !capabilities.getFanSpeeds().isEmpty()
&& setting.getFanSpeed() == null) {
AcFanSpeed fanSpeed = getCurrentOrDefaultFanSpeed(zoneStateProvider, capabilities.getFanSpeeds());
setting.setFanSpeed(fanSpeed);
}
if (capabilities.getSwings() != null && !capabilities.getSwings().isEmpty() && setting.getSwing() == null) {
Power swing = getCurrentOrDefaultSwing(zoneStateProvider, capabilities.getSwings());
setting.setSwing(swing);
}
}
private AcMode getCurrentOrDefaultAcMode(ZoneStateProvider zoneStateProvider) throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
return zoneSetting.getMode() != null ? zoneSetting.getMode() : DEFAULT_MODE;
}
private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider,
TemperatureRange temperatureRanges) throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
Float defaultTemperature = temperatureUnit == TemperatureUnit.FAHRENHEIT ? DEFAULT_TEMPERATURE_F
: DEFAULT_TEMPERATURE_C;
Float temperature = (zoneSetting != null && zoneSetting.getTemperature() != null)
? getTemperatureInUnit(zoneSetting.getTemperature(), temperatureUnit)
: defaultTemperature;
IntRange temperatureRange = temperatureUnit == TemperatureUnit.FAHRENHEIT ? temperatureRanges.getFahrenheit()
: temperatureRanges.getCelsius();
Float finalTemperature = temperatureRange.getMax() >= temperature && temperatureRange.getMin() <= temperature
? temperature
: temperatureRange.getMax();
return temperature(finalTemperature, temperatureUnit);
}
private AcFanSpeed getCurrentOrDefaultFanSpeed(ZoneStateProvider zoneStateProvider, List<AcFanSpeed> fanSpeeds)
throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting.getFanSpeed() != null && fanSpeeds.contains(zoneSetting.getFanSpeed())) {
return zoneSetting.getFanSpeed();
}
return fanSpeeds.get(0);
}
private Power getCurrentOrDefaultSwing(ZoneStateProvider zoneStateProvider, List<Power> swings)
throws IOException, ApiException {
CoolingZoneSetting zoneSetting = (CoolingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting.getSwing() != null && swings.contains(zoneSetting.getSwing())) {
return zoneSetting.getSwing();
}
return swings.get(0);
}
private CoolingZoneSetting coolingSetting(boolean powerOn) {
CoolingZoneSetting setting = new CoolingZoneSetting();
setting.setType(TadoSystemType.AIR_CONDITIONING);
setting.setPower(powerOn ? Power.ON : Power.OFF);
return setting;
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.HeatingZoneSetting;
import org.openhab.binding.tado.internal.api.model.Power;
import org.openhab.binding.tado.internal.api.model.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
/**
* Builder for incremental creation of heating zone settings.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class HeatingZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final float DEFAULT_TEMPERATURE_C = 22.0f;
private static final float DEFAULT_TEMPERATURE_F = 72.0f;
@Override
public ZoneSettingsBuilder withSwing(boolean swingOn) {
throw new IllegalArgumentException("Heating zones don't support SWING");
}
@Override
public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
throw new IllegalArgumentException("Heating zones don't support FAN SPEED");
}
@Override
public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
throws IOException, ApiException {
if (mode == HvacMode.OFF) {
return heatingSetting(false);
}
HeatingZoneSetting setting = heatingSetting(true);
if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit));
}
addMissingSettingParts(setting, zoneStateProvider);
return setting;
}
private void addMissingSettingParts(HeatingZoneSetting setting, ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
if (setting.getTemperature() == null) {
TemperatureObject temperatureObject = getCurrentOrDefaultTemperature(zoneStateProvider);
setting.setTemperature(temperatureObject);
}
}
private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
HeatingZoneSetting zoneSetting = (HeatingZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting != null && zoneSetting.getTemperature() != null) {
return truncateTemperature(zoneSetting.getTemperature());
}
return buildDefaultTemperatureObject(DEFAULT_TEMPERATURE_C, DEFAULT_TEMPERATURE_F);
}
private HeatingZoneSetting heatingSetting(boolean powerOn) {
HeatingZoneSetting setting = new HeatingZoneSetting();
setting.setType(TadoSystemType.HEATING);
setting.setPower(powerOn ? Power.ON : Power.OFF);
return setting;
}
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.temperature;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.HotWaterCapabilities;
import org.openhab.binding.tado.internal.api.model.HotWaterZoneSetting;
import org.openhab.binding.tado.internal.api.model.Power;
import org.openhab.binding.tado.internal.api.model.TadoSystemType;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
/**
* Builder for incremental creation of hot water zone settings.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class HotWaterZoneSettingsBuilder extends ZoneSettingsBuilder {
private static final float DEFAULT_TEMPERATURE_C = 50.0f;
private static final float DEFAULT_TEMPERATURE_F = 122.0f;
@Override
public ZoneSettingsBuilder withSwing(boolean swingOn) {
throw new IllegalArgumentException("Hot Water zones don't support SWING");
}
@Override
public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
throw new IllegalArgumentException("Hot Water zones don't support FAN SPEED");
}
@Override
public GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
throws IOException, ApiException {
if (mode == HvacMode.OFF) {
return hotWaterSetting(false);
}
HotWaterZoneSetting setting = hotWaterSetting(true);
if (temperature != null) {
setting.setTemperature(temperature(temperature, temperatureUnit));
}
addMissingSettingParts(setting, zoneStateProvider, (HotWaterCapabilities) capabilities);
return setting;
}
private void addMissingSettingParts(HotWaterZoneSetting setting, ZoneStateProvider zoneStateProvider,
HotWaterCapabilities capabilities) throws IOException, ApiException {
if (capabilities.isCanSetTemperature() && setting.getTemperature() == null) {
TemperatureObject temperatureObject = getCurrentOrDefaultTemperature(zoneStateProvider);
setting.setTemperature(temperatureObject);
}
}
private TemperatureObject getCurrentOrDefaultTemperature(ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
HotWaterZoneSetting zoneSetting = (HotWaterZoneSetting) zoneStateProvider.getZoneState().getSetting();
if (zoneSetting != null && zoneSetting.getTemperature() != null) {
return truncateTemperature(zoneSetting.getTemperature());
}
return buildDefaultTemperatureObject(DEFAULT_TEMPERATURE_C, DEFAULT_TEMPERATURE_F);
}
private HotWaterZoneSetting hotWaterSetting(boolean powerOn) {
HotWaterZoneSetting setting = new HotWaterZoneSetting();
setting.setType(TadoSystemType.HOT_WATER);
setting.setPower(powerOn ? Power.ON : Power.OFF);
return setting;
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.builder;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.*;
import java.io.IOException;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationConditionType;
import org.openhab.binding.tado.internal.api.model.TimerTerminationCondition;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
/**
* Builder for creation of overlay termination conditions.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TerminationConditionBuilder {
private TadoZoneHandler zoneHandler;
private OverlayTerminationConditionType terminationType = null;
private Integer timerDurationInSeconds = null;
protected TerminationConditionBuilder(TadoZoneHandler zoneHandler) {
this.zoneHandler = zoneHandler;
}
public static TerminationConditionBuilder of(TadoZoneHandler zoneHandler) {
return new TerminationConditionBuilder(zoneHandler);
}
public TerminationConditionBuilder withTerminationType(OverlayTerminationConditionType terminationType) {
this.terminationType = terminationType;
if (terminationType != OverlayTerminationConditionType.TIMER) {
timerDurationInSeconds = null;
}
return this;
}
public TerminationConditionBuilder withTimerDurationInSeconds(Integer timerDurationInSeconds) {
this.terminationType = OverlayTerminationConditionType.TIMER;
this.timerDurationInSeconds = timerDurationInSeconds;
return this;
}
public OverlayTerminationCondition build(ZoneStateProvider zoneStateProvider) throws IOException, ApiException {
OverlayTerminationCondition terminationCondition = null;
if (terminationType != null) {
if (terminationType != OverlayTerminationConditionType.TIMER || timerDurationInSeconds != null) {
terminationCondition = getTerminationCondition(terminationType, timerDurationInSeconds);
} else {
terminationCondition = getCurrentOrDefaultTimerTermination(zoneStateProvider);
}
} else {
ZoneState zoneState = zoneStateProvider.getZoneState();
if (zoneState.getOverlay() != null) {
terminationCondition = cleanTerminationCondition(zoneState.getOverlay().getTermination());
} else {
// Default zone termination condition
terminationCondition = getDefaultTerminationCondition();
}
}
return terminationCondition;
}
private OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
OverlayTerminationCondition defaultTerminationCondition = zoneHandler.getDefaultTerminationCondition();
return defaultTerminationCondition != null ? defaultTerminationCondition : manualTermination();
}
private TimerTerminationCondition getCurrentOrDefaultTimerTermination(ZoneStateProvider zoneStateProvider)
throws IOException, ApiException {
// Timer without duration
int duration = zoneHandler.getFallbackTimerDuration() * 60;
ZoneState zoneState = zoneStateProvider.getZoneState();
// If timer is currently running, use its time
if (zoneState.getOverlay() != null
&& zoneState.getOverlay().getTermination().getType() == OverlayTerminationConditionType.TIMER) {
duration = ((TimerTerminationCondition) zoneState.getOverlay().getTermination()).getDurationInSeconds();
}
return timerTermination(duration);
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.builder;
import java.io.IOException;
import org.openhab.binding.tado.internal.TadoBindingConstants.FanSpeed;
import org.openhab.binding.tado.internal.TadoBindingConstants.HvacMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.GenericZoneSetting;
import org.openhab.binding.tado.internal.api.model.TemperatureObject;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
/**
* Base class for zone settings builder.
*
* @author Dennis Frommknecht - Initial contribution
*/
public abstract class ZoneSettingsBuilder {
public static ZoneSettingsBuilder of(TadoZoneHandler zoneHandler) {
switch (zoneHandler.getZoneType()) {
case HEATING:
return new HeatingZoneSettingsBuilder();
case AIR_CONDITIONING:
return new AirConditioningZoneSettingsBuilder();
case HOT_WATER:
return new HotWaterZoneSettingsBuilder();
default:
throw new IllegalArgumentException("Zone type " + zoneHandler.getZoneType() + " unknown");
}
}
protected HvacMode mode = null;
protected Float temperature = null;
protected TemperatureUnit temperatureUnit = TemperatureUnit.CELSIUS;
protected Boolean swing = null;
protected FanSpeed fanSpeed = null;
public ZoneSettingsBuilder withMode(HvacMode mode) {
this.mode = mode;
return this;
}
public ZoneSettingsBuilder withTemperature(Float temperature, TemperatureUnit temperatureUnit) {
this.temperature = temperature;
this.temperatureUnit = temperatureUnit;
return this;
}
public ZoneSettingsBuilder withSwing(boolean swingOn) {
this.swing = swingOn;
return this;
}
public ZoneSettingsBuilder withFanSpeed(FanSpeed fanSpeed) {
this.fanSpeed = fanSpeed;
return this;
}
public abstract GenericZoneSetting build(ZoneStateProvider zoneStateProvider, GenericZoneCapabilities capabilities)
throws IOException, ApiException;
protected TemperatureObject truncateTemperature(TemperatureObject temperature) {
if (temperature == null) {
return null;
}
TemperatureObject temperatureObject = new TemperatureObject();
if (temperatureUnit == TemperatureUnit.FAHRENHEIT) {
temperatureObject.setFahrenheit(temperature.getFahrenheit());
} else {
temperatureObject.setCelsius(temperature.getCelsius());
}
return temperatureObject;
}
protected TemperatureObject buildDefaultTemperatureObject(float temperatureCelsius, float temperatureFahrenheit) {
TemperatureObject temperatureObject = new TemperatureObject();
if (temperatureUnit == TemperatureUnit.FAHRENHEIT) {
temperatureObject.setFahrenheit(temperatureFahrenheit);
} else {
temperatureObject.setCelsius(temperatureCelsius);
}
return temperatureObject;
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.builder;
import java.io.IOException;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.binding.tado.internal.handler.TadoZoneHandler;
/**
* Wrapper for zone state to support lazy loading.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class ZoneStateProvider {
private TadoZoneHandler zoneHandler;
private ZoneState zoneState;
public ZoneStateProvider(TadoZoneHandler zoneHandler) {
this.zoneHandler = zoneHandler;
}
ZoneState getZoneState() throws IOException, ApiException {
if (this.zoneState == null) {
ZoneState retrievedZoneState = zoneHandler.getZoneState();
// empty zone state behaves like a NULL object
this.zoneState = retrievedZoneState != null ? retrievedZoneState : new ZoneState();
}
return this.zoneState;
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.config;
/**
* Holder-object for home configuration
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoHomeConfig {
public String username;
public String password;
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.config;
/**
* Holder-object for mobile device configuration
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoMobileDeviceConfig {
public int id;
public int refreshInterval;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.config;
/**
* Holder-object for zone configuration
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoZoneConfig {
public long id;
public int refreshInterval;
public int fallbackTimerDuration;
public int hvacChangeDebounce;
}

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.discovery;
import static org.openhab.binding.tado.internal.TadoBindingConstants.*;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.tado.internal.TadoBindingConstants;
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.Zone;
import org.openhab.binding.tado.internal.handler.TadoHomeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery service for zones and mobile devices.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoDiscoveryService extends AbstractDiscoveryService {
private static final int TIMEOUT = 5;
private static final long REFRESH = 600;
private final Logger logger = LoggerFactory.getLogger(TadoDiscoveryService.class);
private ScheduledFuture<?> discoveryFuture;
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_ZONE, THING_TYPE_MOBILE_DEVICE).collect(Collectors.toSet()));
private TadoHomeHandler homeHandler;
public TadoDiscoveryService(TadoHomeHandler tadoHomeHandler) {
super(DISCOVERABLE_THING_TYPES_UIDS, TIMEOUT);
this.homeHandler = tadoHomeHandler;
}
public void activate() {
super.activate(null);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
protected void startScan() {
if (homeHandler.getHomeId() == null) {
return;
}
discoverZones();
discoverMobileDevices();
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Start Tado background discovery");
if (discoveryFuture == null || discoveryFuture.isCancelled()) {
logger.debug("Start Scan");
discoveryFuture = scheduler.scheduleWithFixedDelay(this::startScan, 30, REFRESH, TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stop Tado background discovery");
if (discoveryFuture != null && !discoveryFuture.isCancelled()) {
discoveryFuture.cancel(true);
discoveryFuture = null;
}
}
private void discoverZones() {
Long homeId = homeHandler.getHomeId();
try {
List<Zone> zoneList = homeHandler.getApi().listZones(homeId);
if (zoneList != null) {
for (Zone zone : zoneList) {
notifyZoneDiscovery(homeId, zone);
}
}
} catch (IOException | ApiException e) {
logger.debug("Could not discover tado zones: {}", e.getMessage(), e);
}
}
private void notifyZoneDiscovery(Long homeId, Zone zone) {
Integer zoneId = zone.getId();
ThingUID bridgeUID = this.homeHandler.getThing().getUID();
ThingUID uid = new ThingUID(TadoBindingConstants.THING_TYPE_ZONE, bridgeUID, zoneId.toString());
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_ZONE_ID, zoneId);
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(zone.getName())
.withProperties(properties).build();
thingDiscovered(result);
logger.debug("Discovered zone '{}' with id {} ({})", zone.getName(), zoneId.toString(), uid);
}
private void discoverMobileDevices() {
Long homeId = homeHandler.getHomeId();
try {
List<MobileDevice> mobileDeviceList = homeHandler.getApi().listMobileDevices(homeId);
if (mobileDeviceList != null) {
for (MobileDevice mobileDevice : mobileDeviceList) {
if (mobileDevice.getSettings().isGeoTrackingEnabled()) {
notifyMobileDeviceDiscovery(homeId, mobileDevice);
}
}
}
} catch (IOException | ApiException e) {
logger.debug("Could not discover tado zones: {}", e.getMessage(), e);
}
}
private void notifyMobileDeviceDiscovery(Long homeId, MobileDevice device) {
ThingUID bridgeUID = this.homeHandler.getThing().getUID();
ThingUID uid = new ThingUID(TadoBindingConstants.THING_TYPE_MOBILE_DEVICE, bridgeUID,
device.getId().toString());
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_MOBILE_DEVICE_ID, device.getId());
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(device.getName())
.withProperties(properties).build();
thingDiscovered(result);
logger.debug("Discovered mobile device '{}' with id {} ({})", device.getName(), device.getId().toString(), uid);
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.handler;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
/**
* Common base class for home-based thing-handler.
*
* @author Dennis Frommknecht - Initial contribution
*/
public abstract class BaseHomeThingHandler extends BaseThingHandler {
public BaseHomeThingHandler(Thing thing) {
super(thing);
}
public Long getHomeId() {
TadoHomeHandler handler = getHomeHandler();
return handler != null ? handler.getHomeId() : new Long(0);
}
protected TadoHomeHandler getHomeHandler() {
Bridge bridge = getBridge();
return bridge != null ? (TadoHomeHandler) bridge.getHandler() : null;
}
protected HomeApi getApi() {
TadoHomeHandler handler = getHomeHandler();
return handler != null ? handler.getApi() : null;
}
protected void onSuccessfulOperation() {
// update without error -> we're back online
if (getThing().getStatus() == ThingStatus.OFFLINE) {
updateStatus(ThingStatus.ONLINE);
}
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.handler;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.ControlDevice;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoBatteryChecker} checks the battery state of Tado control
* devices.
*
* @author Andrew Fiddian-Green - Initial contribution
*
*/
public class TadoBatteryChecker {
private final Logger logger = LoggerFactory.getLogger(TadoBatteryChecker.class);
private Map<Long, State> zoneList = new HashMap<>();
private Date refreshTime = new Date();
private TadoHomeHandler homeHandler;
public TadoBatteryChecker(TadoHomeHandler homeHandler) {
this.homeHandler = homeHandler;
}
private synchronized void refreshZoneList() {
Date now = new Date();
if (homeHandler != null && (now.after(refreshTime) || zoneList.isEmpty())) {
// be frugal, we only need to refresh the battery state hourly
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.HOUR, 1);
refreshTime = calendar.getTime();
Long homeId = homeHandler.getHomeId();
if (homeId != null) {
logger.debug("Fetching (battery state) zone list for HomeId {}", homeId);
zoneList.clear();
try {
homeHandler.getApi().listZones(homeId).forEach(zone -> {
boolean batteryLow = !zone.getDevices().stream().map(ControlDevice::getBatteryState)
.filter(Objects::nonNull).allMatch(s -> s.equals("NORMAL"));
zoneList.put(Long.valueOf(zone.getId()), OnOffType.from(batteryLow));
});
} catch (IOException | ApiException e) {
logger.debug("Fetch (battery state) zone list exception");
}
}
}
}
public State getBatteryLowAlarm(long zoneId) {
refreshZoneList();
return zoneList.getOrDefault(zoneId, UnDefType.UNDEF);
}
}

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.handler;
import static org.openhab.binding.tado.internal.TadoBindingConstants.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.openhab.binding.tado.internal.discovery.TadoDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
/**
* The {@link TadoHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Dennis Frommknecht - Initial contribution
*/
@Component(configurationPid = "binding.tado", service = ThingHandlerFactory.class)
public class TadoHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(THING_TYPE_HOME, THING_TYPE_ZONE, THING_TYPE_MOBILE_DEVICE)));
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_HOME)) {
TadoHomeHandler tadoHomeHandler = new TadoHomeHandler((Bridge) thing);
registerTadoDiscoveryService(tadoHomeHandler);
return tadoHomeHandler;
} else if (thingTypeUID.equals(THING_TYPE_ZONE)) {
return new TadoZoneHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_MOBILE_DEVICE)) {
return new TadoMobileDeviceHandler(thing);
}
return null;
}
private synchronized void registerTadoDiscoveryService(TadoHomeHandler tadoHomeHandler) {
TadoDiscoveryService discoveryService = new TadoDiscoveryService(tadoHomeHandler);
ServiceRegistration<?> serviceRegistration = bundleContext.registerService(DiscoveryService.class.getName(),
discoveryService, new Hashtable<>());
discoveryService.activate();
this.discoveryServiceRegs.put(tadoHomeHandler.getThing().getUID(), serviceRegistration);
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof TadoHomeHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
TadoDiscoveryService service = (TadoDiscoveryService) bundleContext
.getService(serviceReg.getReference());
serviceReg.unregister();
if (service != null) {
service.deactivate();
}
}
}
}
}

View File

@@ -0,0 +1,137 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.handler;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.HomeApiFactory;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import org.openhab.binding.tado.internal.api.model.HomeInfo;
import org.openhab.binding.tado.internal.api.model.User;
import org.openhab.binding.tado.internal.config.TadoHomeConfig;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoHomeHandler} is the bridge of all home-based things.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoHomeHandler extends BaseBridgeHandler {
private Logger logger = LoggerFactory.getLogger(TadoHomeHandler.class);
private TadoHomeConfig configuration;
private HomeApi api;
private Long homeId;
private TadoBatteryChecker batteryChecker;
private ScheduledFuture<?> initializationFuture;
public TadoHomeHandler(Bridge bridge) {
super(bridge);
batteryChecker = new TadoBatteryChecker(this);
}
public TemperatureUnit getTemperatureUnit() {
String temperatureUnitStr = this.thing.getProperties().get(TadoBindingConstants.PROPERTY_HOME_TEMPERATURE_UNIT);
return TemperatureUnit.valueOf(temperatureUnitStr);
}
@Override
public void initialize() {
configuration = getConfigAs(TadoHomeConfig.class);
api = new HomeApiFactory().create(configuration.username, configuration.password);
if (this.initializationFuture == null || this.initializationFuture.isDone()) {
initializationFuture = scheduler.scheduleWithFixedDelay(this::initializeBridgeStatusAndPropertiesIfOffline,
0, 300, TimeUnit.SECONDS);
}
}
private void initializeBridgeStatusAndPropertiesIfOffline() {
Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
return;
}
try {
// Get user info to verify successful authentication and connection to server
User user = api.showUser();
if (user == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Cannot connect to server. Username and/or password might be invalid");
return;
}
if (user.getHomes().isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"User does not have access to any home");
return;
}
homeId = user.getHomes().get(0).getId().longValue();
HomeInfo homeInfo = api.showHome(homeId);
TemperatureUnit temperatureUnit = org.openhab.binding.tado.internal.api.model.TemperatureUnit.FAHRENHEIT == homeInfo
.getTemperatureUnit() ? TemperatureUnit.FAHRENHEIT : TemperatureUnit.CELSIUS;
updateProperty(TadoBindingConstants.PROPERTY_HOME_TEMPERATURE_UNIT, temperatureUnit.name());
} catch (IOException | ApiException e) {
logger.debug("Error accessing tado server: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
return;
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void dispose() {
super.dispose();
if (this.initializationFuture != null || !this.initializationFuture.isDone()) {
this.initializationFuture.cancel(true);
this.initializationFuture = null;
}
}
public HomeApi getApi() {
return api;
}
public Long getHomeId() {
return homeId;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Nothing to do for a bridge
}
public State getBatteryLowAlarm(long zoneId) {
return batteryChecker.getBatteryLowAlarm(zoneId);
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.handler;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.model.MobileDevice;
import org.openhab.binding.tado.internal.config.TadoMobileDeviceConfig;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoMobileDeviceHandler} is responsible for handling commands of mobile devices and update their state.
*
* @author Dennis Frommknecht - Initial contribution
*/
public class TadoMobileDeviceHandler extends BaseHomeThingHandler {
private Logger logger = LoggerFactory.getLogger(TadoMobileDeviceHandler.class);
private TadoMobileDeviceConfig configuration;
private ScheduledFuture<?> refreshTimer;
public TadoMobileDeviceHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == RefreshType.REFRESH) {
logger.debug("Refreshing {}", channelUID);
updateState();
} else {
logger.warn("This Thing is read-only and can only handle REFRESH command");
}
}
@Override
public void initialize() {
configuration = getConfigAs(TadoMobileDeviceConfig.class);
if (configuration.refreshInterval <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
+ configuration.id + " of home " + getHomeId() + " must be greater than zero");
return;
}
Bridge bridge = getBridge();
if (bridge != null) {
bridgeStatusChanged(bridge.getStatusInfo());
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
try {
MobileDevice device = getMobileDevice();
updateProperty(TadoBindingConstants.PROPERTY_MOBILE_DEVICE_NAME, device.getName());
if (!device.getSettings().isGeoTrackingEnabled()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Geotracking is disabled on mobile device " + device.getName());
return;
}
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
cancelScheduledStateUpdate();
return;
}
scheduleZoneStateUpdate();
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
cancelScheduledStateUpdate();
}
}
private void updateState() {
try {
MobileDevice device = getMobileDevice();
updateState(TadoBindingConstants.CHANNEL_MOBILE_DEVICE_AT_HOME,
device.getLocation().isAtHome() ? OnOffType.ON : OnOffType.OFF);
} catch (IOException | ApiException e) {
logger.debug("Status update of mobile device with id {} failed: {}", configuration.id, e.getMessage());
}
}
private MobileDevice getMobileDevice() throws IOException, ApiException {
MobileDevice device = null;
try {
device = getApi().listMobileDevices(getHomeId()).stream().filter(m -> m.getId() == configuration.id)
.findFirst().orElse(null);
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
throw e;
}
if (device == null) {
String message = "Mobile device with id " + configuration.id + " unknown or does not belong to home "
+ getHomeId();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
throw new IOException(message);
}
onSuccessfulOperation();
return device;
}
private void scheduleZoneStateUpdate() {
if (refreshTimer == null || refreshTimer.isCancelled()) {
refreshTimer = scheduler.scheduleWithFixedDelay(this::updateState, 5, configuration.refreshInterval,
TimeUnit.SECONDS);
}
}
private void cancelScheduledStateUpdate() {
if (refreshTimer != null) {
refreshTimer.cancel(false);
}
}
}

View File

@@ -0,0 +1,310 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tado.internal.handler;
import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.terminationConditionTemplateToTerminationCondition;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Temperature;
import org.openhab.binding.tado.internal.TadoBindingConstants;
import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
import org.openhab.binding.tado.internal.TadoBindingConstants.ZoneType;
import org.openhab.binding.tado.internal.TadoHvacChange;
import org.openhab.binding.tado.internal.adapter.TadoZoneStateAdapter;
import org.openhab.binding.tado.internal.api.ApiException;
import org.openhab.binding.tado.internal.api.client.HomeApi;
import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
import org.openhab.binding.tado.internal.api.model.Overlay;
import org.openhab.binding.tado.internal.api.model.OverlayTemplate;
import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
import org.openhab.binding.tado.internal.api.model.Zone;
import org.openhab.binding.tado.internal.api.model.ZoneState;
import org.openhab.binding.tado.internal.config.TadoZoneConfig;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TadoZoneHandler} is responsible for handling commands of zones and update their state.
*
* @author Dennis Frommknecht - Initial contribution
* @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
*
*/
public class TadoZoneHandler extends BaseHomeThingHandler {
private Logger logger = LoggerFactory.getLogger(TadoZoneHandler.class);
private TadoZoneConfig configuration;
private ScheduledFuture<?> refreshTimer;
private ScheduledFuture<?> scheduledHvacChange;
private GenericZoneCapabilities capabilities;
TadoHvacChange pendingHvacChange;
public TadoZoneHandler(Thing thing) {
super(thing);
}
public long getZoneId() {
return this.configuration.id;
}
public int getFallbackTimerDuration() {
return this.configuration.fallbackTimerDuration;
}
public ZoneType getZoneType() {
String zoneTypeStr = this.thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE);
return ZoneType.valueOf(zoneTypeStr);
}
public OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
OverlayTemplate overlayTemplate = getApi().showZoneDefaultOverlay(getHomeId(), getZoneId());
return terminationConditionTemplateToTerminationCondition(overlayTemplate.getTerminationCondition());
}
public ZoneState getZoneState() throws IOException, ApiException {
HomeApi api = getApi();
return api != null ? api.showZoneState(getHomeId(), getZoneId()) : null;
}
public GenericZoneCapabilities getZoneCapabilities() {
return this.capabilities;
}
public TemperatureUnit getTemperatureUnit() {
return getHomeHandler().getTemperatureUnit();
}
public Overlay setOverlay(Overlay overlay) throws IOException, ApiException {
logger.debug("Setting overlay of home {} and zone {}", getHomeId(), getZoneId());
return getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay);
}
public void removeOverlay() throws IOException, ApiException {
logger.debug("Removing overlay of home {} and zone {}", getHomeId(), getZoneId());
getApi().deleteZoneOverlay(getHomeId(), getZoneId());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String id = channelUID.getId();
if (command == RefreshType.REFRESH) {
updateZoneState(false);
return;
}
switch (id) {
case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE:
pendingHvacChange.withHvacMode(((StringType) command).toFullString());
scheduleHvacChange();
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) {
pendingHvacChange.withTemperature(stateInTargetUnit.floatValue());
scheduleHvacChange();
}
break;
case TadoBindingConstants.CHANNEL_ZONE_SWING:
pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON);
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED:
pendingHvacChange.withFanSpeed(((StringType) command).toFullString());
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE:
String operationMode = ((StringType) command).toFullString();
pendingHvacChange.withOperationMode(OperationMode.valueOf(operationMode));
scheduleHvacChange();
break;
case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION:
pendingHvacChange.activeFor(((DecimalType) command).intValue());
scheduleHvacChange();
break;
}
}
@Override
public void initialize() {
configuration = getConfigAs(TadoZoneConfig.class);
if (configuration.refreshInterval <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
+ getZoneId() + " of home " + getHomeId() + " must be greater than zero");
return;
} else if (configuration.fallbackTimerDuration <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Fallback timer duration of zone "
+ getZoneId() + " of home " + getHomeId() + " must be greater than zero");
return;
} else if (configuration.hvacChangeDebounce <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "HVAC change debounce of zone "
+ getZoneId() + " of home " + getHomeId() + " must be greater than zero");
return;
}
Bridge bridge = getBridge();
if (bridge != null) {
bridgeStatusChanged(bridge.getStatusInfo());
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
try {
Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId());
GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId());
if (zoneDetails == null || capabilities == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Can not access zone " + getZoneId() + " of home " + getHomeId());
return;
}
updateProperty(TadoBindingConstants.PROPERTY_ZONE_NAME, zoneDetails.getName());
updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
this.capabilities = capabilities;
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
cancelScheduledZoneStateUpdate();
return;
}
scheduleZoneStateUpdate();
pendingHvacChange = new TadoHvacChange(getThing());
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
cancelScheduledZoneStateUpdate();
}
}
private void updateZoneState(boolean forceUpdate) {
// No update during HVAC change debounce
if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) {
return;
}
try {
ZoneState zoneState = getZoneState();
if (zoneState == null) {
return;
}
logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId());
TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower());
updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower());
updateState(TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE, state.getOperationMode());
updateState(TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE, state.getMode());
updateState(TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE, state.getTargetTemperature());
updateState(TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED, state.getFanSpeed());
updateState(TadoBindingConstants.CHANNEL_ZONE_SWING, state.getSwing());
updateState(TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION, state.getRemainingTimerDuration());
updateState(TadoBindingConstants.CHANNEL_ZONE_OVERLAY_EXPIRY, state.getOverlayExpiration());
updateState(TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED, state.getOpenWindowDetected());
onSuccessfulOperation();
} catch (IOException | ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to server due to " + e.getMessage());
}
TadoHomeHandler home = getHomeHandler();
if (home != null) {
updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM, home.getBatteryLowAlarm(getZoneId()));
}
}
private void scheduleZoneStateUpdate() {
if (refreshTimer == null || refreshTimer.isCancelled()) {
refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
updateZoneState(false);
}
}, 5, configuration.refreshInterval, TimeUnit.SECONDS);
}
}
private void cancelScheduledZoneStateUpdate() {
if (refreshTimer != null) {
refreshTimer.cancel(false);
}
}
private void scheduleHvacChange() {
if (scheduledHvacChange != null) {
scheduledHvacChange.cancel(false);
}
scheduledHvacChange = scheduler.schedule(() -> {
try {
TadoHvacChange change = this.pendingHvacChange;
this.pendingHvacChange = new TadoHvacChange(getThing());
change.apply();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (ApiException e) {
logger.warn("Could not apply HVAC change on home {} and zone {}: {}", getHomeId(), getZoneId(),
e.getMessage(), e);
} finally {
updateZoneState(true);
}
}, configuration.hvacChangeDebounce, TimeUnit.SECONDS);
}
private void updateStateIfNotNull(String channelID, State state) {
if (state != null) {
updateState(channelID, state);
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="tado" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>tado° Binding</name>
<description>Binding for tado° devices</description>
<author>Dennis Frommknecht</author>
</binding:binding>

View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tado"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- my.tado API Gateway -->
<bridge-type id="home">
<label>Tado Home</label>
<description>The user's tado home</description>
<config-description>
<parameter name="username" type="text" required="true">
<label>User Name</label>
<description>User name of tado login used for API access</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>Password of tado login used for API access</description>
<context>password</context>
</parameter>
</config-description>
</bridge-type>
<thing-type id="zone">
<supported-bridge-type-refs>
<bridge-type-ref id="home"/>
</supported-bridge-type-refs>
<label>Zone</label>
<description>A zone of a home</description>
<channels>
<channel typeId="currentTemperature" id="currentTemperature"></channel>
<channel typeId="humidity" id="humidity"></channel>
<channel typeId="heatingPower" id="heatingPower"></channel>
<channel typeId="hvacMode" id="hvacMode"></channel>
<channel typeId="targetTemperature" id="targetTemperature"></channel>
<channel typeId="fanspeed" id="fanspeed"></channel>
<channel typeId="swing" id="swing"></channel>
<channel typeId="overlayExpiry" id="overlayExpiry"></channel>
<channel typeId="timerDuration" id="timerDuration"></channel>
<channel typeId="operationMode" id="operationMode"></channel>
<channel typeId="system.low-battery" id="batteryLowAlarm">
<label>Battery Low Alarm</label>
<description>ON if one or more devices in the zone have a low battery</description>
</channel>
<channel typeId="acPower" id="acPower"></channel>
<channel typeId="openWindowDetected" id="openWindowDetected"></channel>
</channels>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Zone Id</label>
<description>Id of the zone</description>
</parameter>
<parameter name="refreshInterval" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval of home data</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="fallbackTimerDuration" type="integer">
<label>Fallback Timer Duration</label>
<description>Timer duration used if no other duration can be determined</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="hvacChangeDebounce" type="integer">
<label>HVAC Change Debounce Delay</label>
<description>Duration in seconds to combine multiple HVAC changes into one.</description>
<default>5</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="mobiledevice">
<supported-bridge-type-refs>
<bridge-type-ref id="home"/>
</supported-bridge-type-refs>
<label>Mobile Device</label>
<description>Mobile device of a home</description>
<channels>
<channel typeId="atHome" id="atHome"></channel>
</channels>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Mobile Device Id</label>
<description>Id of the mobile device</description>
</parameter>
<parameter name="refreshInterval" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval of location state</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="currentTemperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"></state>
</channel-type>
<channel-type id="humidity">
<item-type>Number</item-type>
<label>Humidity</label>
<description>Current humidity in %</description>
<category>Humidity</category>
<state readOnly="true" pattern="%.1f %%"></state>
</channel-type>
<channel-type id="heatingPower">
<item-type>Number</item-type>
<label>Heating Power</label>
<description>Current heating power</description>
<state readOnly="true" pattern="%.0f %%"></state>
</channel-type>
<channel-type id="hvacMode">
<item-type>String</item-type>
<label>HVAC Mode</label>
<description>Mode of the device (OFF, HEAT, COOL, DRY, FAN, AUTO - if supported)</description>
<state readOnly="false">
<options>
<option value="OFF">Off</option>
<option value="HEAT">Heat</option>
<option value="COOL">Cool</option>
<option value="DRY">Dry</option>
<option value="FAN">Fan</option>
<option value="AUTO">Auto</option>
</options>
</state>
</channel-type>
<channel-type id="targetTemperature">
<item-type>Number:Temperature</item-type>
<label>Target Temperature</label>
<description>Thermostat temperature setpoint</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%" readOnly="false"></state>
</channel-type>
<channel-type id="fanspeed">
<item-type>String</item-type>
<label>Fan Speed</label>
<description>AC fan speed (only if supported by AC)</description>
<state readOnly="false">
<options>
<option value="LOW">Low</option>
<option value="MIDDLE">Middle</option>
<option value="HIGH">High</option>
<option value="AUTO">Auto</option>
</options>
</state>
</channel-type>
<channel-type id="swing">
<item-type>Switch</item-type>
<label>Swing</label>
<description>State of AC swing (only if supported by AC)</description>
</channel-type>
<channel-type id="operationMode">
<item-type>String</item-type>
<label>Zone Operation Mode</label>
<description>Active operation mode (schedule, manual, timer or until next change)</description>
<state readOnly="false">
<options>
<option value="SCHEDULE">Schedule</option>
<option value="MANUAL">Manual</option>
<option value="UNTIL_CHANGE">Until change</option>
<option value="TIMER">Timer</option>
</options>
</state>
</channel-type>
<channel-type id="timerDuration">
<item-type>Number</item-type>
<label>Timer Duration</label>
<description>Total duration of a timer</description>
<state min="0" step="1" pattern="%d min" readOnly="false"></state>
</channel-type>
<channel-type id="overlayExpiry">
<item-type>DateTime</item-type>
<label>Overlay End Time</label>
<description>Time until when the overlay is active. Null if no overlay is set or overlay type is manual.</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="atHome">
<item-type>Switch</item-type>
<label>At Home</label>
<description>ON if at home, OFF if away</description>
</channel-type>
<channel-type id="acPower">
<item-type>Switch</item-type>
<label>AirCon Power State</label>
<description>Indicates if the air-conditioning is Off or On</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="openWindowDetected">
<item-type>Switch</item-type>
<label>Open Window Detected</label>
<description>Indicates if an open window has been detected</description>
<category>window</category>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>