[somfytahoma] [Improvement] Fix cozytouch support and add waterheatersystem support (#11855)

* Add support for Cozytouch and WaterHeaterSystem

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
This commit is contained in:
Benjamin Lafois 2022-01-26 20:12:46 +01:00 committed by GitHub
parent 56b2f47be6
commit d6da67435a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 642 additions and 9 deletions

View File

@ -40,6 +40,7 @@ Any home automation system based on the OverKiz API is potentially supported.
- sirens (battery status full/low/normal/verylow, siren control ON/OFF, setting memorized volume) - sirens (battery status full/low/normal/verylow, siren control ON/OFF, setting memorized volume)
- action groups (scenarios which can execute predefined Tahoma group of steps, e.g. send to all roller shutters DOWN command, one by one) - action groups (scenarios which can execute predefined Tahoma group of steps, e.g. send to all roller shutters DOWN command, one by one)
- thermostats (read status and battery level) - thermostats (read status and battery level)
- water heater system (monitor and control)
Both Somfy Tahoma and Somfy Connexoon gateways have been confirmed working. Both Somfy Tahoma and Somfy Connexoon gateways have been confirmed working.
@ -123,6 +124,18 @@ Please see the example below.
| myfox camera, myfox alarm | cloud_status | cloud connection status | | myfox camera, myfox alarm | cloud_status | cloud connection status |
| myfox camera | shutter | controlling of the camera shutter | | myfox camera | shutter | controlling of the camera shutter |
| myfox alarm | myfox_alarm_command | used for sending commands to Somfy Myfox alarm device | | myfox alarm | myfox_alarm_command | used for sending commands to Somfy Myfox alarm device |
| waterheatersystem | middlewater_temperature | Number:Temperature indicating the temperature of the water at the middle of the heater |
| waterheatersystem | boost_mode | Switch allowing to enable or disable the booster. When switching to ON, by default, the Boost duration will be set for 1 day.|
| waterheatersystem | away_mode | Defines if away mode is On or Off (no water heating) |
| waterheatersystem | away_mode_duration | Defines if away mode the duration in days. |
| waterheatersystem | boost_mode_duration | The duration of the Boost mode in days. Valid from 1 to 7. |
| waterheatersystem | power_heatpump | Current consumption/power of the heatpump in Watts. |
| waterheatersystem | power_heatelec | Current consumption/power of the electric resistance in Watts. |
| waterheatersystem | showers | Virtual channel, representing the number of desired showers - between 3 to 5. It actually switches the desired temperature to 50.0, 54.5 or 62.0 Celcius degrees. Please note that in ECO mode, only 3 and 4 showers are allowed. |
| waterheatersystem | heat_pump_operating_time | Number of hours the heatpump has been operating |
| waterheatersystem | electric_booster_operating_time | number of hours the electric booster has been operating. |
| waterheatersystem | mode | The current mode of the boiler. Can be: autoMode / manualEcoInactive / manualEcoActive |
| waterheatersystem | target_temperature | Water target temperature in degrees. Read only. Temperature desired is managed through mode and showers channels. |
To run a scenario inside a rule for example, the ID of the scenario will be required. To run a scenario inside a rule for example, the ID of the scenario will be required.
You can list all the scenarios IDs with the following console command: `somfytahoma <bridgeUID> scenarios`. You can list all the scenarios IDs with the following console command: `somfytahoma <bridgeUID> scenarios`.

View File

@ -144,6 +144,9 @@ public class SomfyTahomaBindingConstants {
// Electricity sensor // Electricity sensor
public static final ThingTypeUID THING_TYPE_ELECTRICITYSENSOR = new ThingTypeUID(BINDING_ID, "electricitysensor"); public static final ThingTypeUID THING_TYPE_ELECTRICITYSENSOR = new ThingTypeUID(BINDING_ID, "electricitysensor");
// Water Heating System
public static final ThingTypeUID THING_TYPE_WATERHEATINGSYSTEM = new ThingTypeUID(BINDING_ID, "waterheatingsystem");
// Dock // Dock
public static final ThingTypeUID THING_TYPE_DOCK = new ThingTypeUID(BINDING_ID, "dock"); public static final ThingTypeUID THING_TYPE_DOCK = new ThingTypeUID(BINDING_ID, "dock");
@ -260,6 +263,20 @@ public class SomfyTahomaBindingConstants {
// ElectricitySensor // ElectricitySensor
public static final String ENERGY_CONSUMPTION = "energy_consumption"; public static final String ENERGY_CONSUMPTION = "energy_consumption";
// WaterHeaterSystem
public static final String MIDDLEWATER_TEMPERATURE = "middlewater_temperature";
public static final String BOOST_MODE = "boost_mode";
public static final String AWAY_MODE = "away_mode";
public static final String BOOST_MODE_DURATION = "boost_mode_duration";
public static final String AWAY_MODE_DURATION = "away_mode_duration";
public static final String HEAT_PUMP_OPERATING_TIME = "heat_pump_operating_time";
public static final String POWER_HEAT_PUMP = "power_heatpump";
public static final String POWER_HEAT_ELEC = "power_heatelec";
public static final String WATER_HEATER_MODE = "mode";
public static final String WATER_TEMPERATURE = "water_temperature";
public static final String ELECTRIC_BOOSTER_OPERATING_TIME = "electric_booster_operating_time";
public static final String SHOWERS = "showers";
// Dock // Dock
public static final String BATTERY_STATUS = "battery_status"; public static final String BATTERY_STATUS = "battery_status";
public static final String SIREN_STATUS = "siren_status"; public static final String SIREN_STATUS = "siren_status";
@ -281,7 +298,12 @@ public class SomfyTahomaBindingConstants {
public static final String SHUTTER = "shutter"; public static final String SHUTTER = "shutter";
// Constants // Constants
public static final String COZYTOUCH_PORTAL = "ha110-1.overkiz.com";
public static final String TAHOMA_PORTAL = "www.tahomalink.com"; public static final String TAHOMA_PORTAL = "www.tahomalink.com";
public static final String COZYTOUCH_OAUTH2_URL = "api.groupe-atlantic.com";
public static final String COZYTOUCH_OAUTH2_BASICAUTH = "czduc0RZZXdWbjVGbVV4UmlYN1pVSUM3ZFI4YTphSDEzOXZmbzA1ZGdqeDJkSFVSQkFTbmhCRW9h";
public static final String COZYTOUCH_OAUTH2_TOKEN_URL = "/token";
public static final String COZYTOUCH_OAUTH2_JWT_URL = "/gacoma/gacomawcfservice/accounts/jwt";
public static final String API_BASE_URL = "/enduser-mobile-web/enduserAPI/"; public static final String API_BASE_URL = "/enduser-mobile-web/enduserAPI/";
public static final String EVENTS_URL = "events/"; public static final String EVENTS_URL = "events/";
public static final String SETUP_URL = "setup/"; public static final String SETUP_URL = "setup/";
@ -300,6 +322,7 @@ public class SomfyTahomaBindingConstants {
public static final int TYPE_BOOLEAN = 6; public static final int TYPE_BOOLEAN = 6;
public static final String UNAVAILABLE = "unavailable"; public static final String UNAVAILABLE = "unavailable";
public static final String AUTHENTICATION_CHALLENGE = "HTTP protocol violation: Authentication challenge without WWW-Authenticate header"; public static final String AUTHENTICATION_CHALLENGE = "HTTP protocol violation: Authentication challenge without WWW-Authenticate header";
public static final String AUTHENTICATION_OAUTH_GRANT_ERROR = "Provided Authorization Grant is invalid.";
public static final String TOO_MANY_REQUESTS = "Too many requests, try again later"; public static final String TOO_MANY_REQUESTS = "Too many requests, try again later";
public static final int SUSPEND_TIME = 120; public static final int SUSPEND_TIME = 120;
public static final int RECONCILIATION_TIME = 600; public static final int RECONCILIATION_TIME = 600;
@ -324,8 +347,24 @@ public class SomfyTahomaBindingConstants {
public static final String COMMAND_STOP = "stop"; public static final String COMMAND_STOP = "stop";
public static final String COMMAND_OFF = "off"; public static final String COMMAND_OFF = "off";
public static final String COMMAND_CHECK_TRIGGER = "checkEventTrigger"; public static final String COMMAND_CHECK_TRIGGER = "checkEventTrigger";
public static final String COMMAND_SET_BOOST_MODE_DURATION = "setBoostModeDuration";
public static final String COMMAND_SET_WATER_HEATER_MODE = "setDHWMode";
public static final String COMMAND_SET_AWAY_MODE_DURATION = "setAwayModeDuration";
public static final String COMMAND_SET_CURRENT_OPERATING_MODE = "setCurrentOperatingMode";
public static final String COMMAND_SET_TARGET_TEMPERATURE = "setTargetTemperature";
public static final String COMMAND_REFRESH_DHWMODE = "refreshDHWMode";
public static final String COMMAND_REFRESH_BOOST_MODE_DURATION = "refreshBoostModeDuration";
// States // States
public static final String OPERATING_MODE_STATE = "core:OperatingModeState";
public static final String ELECTRIC_BOOSTER_OPERATING_TIME_STATE = "io:ElectricBoosterOperatingTimeState";
public static final String WATER_HEATER_MODE_STATE = "io:DHWModeState";
public static final String POWER_HEAT_ELEC_STATE = "io:PowerHeatElectricalState";
public static final String POWER_HEAT_PUMP_STATE = "io:PowerHeatPumpState";
public static final String HEAT_PUMP_OPERATING_TIME_STATE = "io:HeatPumpOperatingTimeState";
public static final String BOOST_MODE_DURATION_STATE = "core:BoostModeDurationState";
public static final String AWAY_MODE_DURATION_STATE = "io:AwayModeDurationState";
public static final String MIDDLE_WATER_TEMPERATURE_STATE = "io:MiddleWaterTemperatureState";
public static final String NAME_STATE = "core:NameState"; public static final String NAME_STATE = "core:NameState";
public static final String RSSI_LEVEL_STATE = "core:RSSILevelState"; public static final String RSSI_LEVEL_STATE = "core:RSSILevelState";
public static final String STATUS_STATE = "core:StatusState"; public static final String STATUS_STATE = "core:StatusState";
@ -341,6 +380,7 @@ public class SomfyTahomaBindingConstants {
public static final String BATTERY_LEVEL_STATE = "core:BatteryLevelState"; public static final String BATTERY_LEVEL_STATE = "core:BatteryLevelState";
public static final String SIREN_STATUS_STATE = "internal:SirenStatusState"; public static final String SIREN_STATUS_STATE = "internal:SirenStatusState";
public static final String TARGET_TEMPERATURE_STATE = "core:TargetTemperatureState"; public static final String TARGET_TEMPERATURE_STATE = "core:TargetTemperatureState";
public static final String TEMPERATURE_STATE = "core:TemperatureState";
public static final String TARGET_ROOM_TEMPERATURE_STATE = "core:TargetRoomTemperatureState"; public static final String TARGET_ROOM_TEMPERATURE_STATE = "core:TargetRoomTemperatureState";
public static final String SMOKE_STATE = "core:SmokeState"; public static final String SMOKE_STATE = "core:SmokeState";
public static final String SENSOR_DEFECT_STATE = "core:SensorDefectState"; public static final String SENSOR_DEFECT_STATE = "core:SensorDefectState";
@ -379,6 +419,7 @@ public class SomfyTahomaBindingConstants {
public static final String CLASS_SIREN = "Siren"; public static final String CLASS_SIREN = "Siren";
public static final String CLASS_ADJUSTABLE_SLATS_ROLLER_SHUTTER = "AdjustableSlatsRollerShutter"; public static final String CLASS_ADJUSTABLE_SLATS_ROLLER_SHUTTER = "AdjustableSlatsRollerShutter";
public static final String CLASS_CAMERA = "Camera"; public static final String CLASS_CAMERA = "Camera";
public static final String CLASS_WATER_HEATING_SYSTEM = "WaterHeatingSystem";
// unsupported uiClasses // unsupported uiClasses
public static final String THING_PROTOCOL_GATEWAY = "ProtocolGateway"; public static final String THING_PROTOCOL_GATEWAY = "ProtocolGateway";
@ -401,7 +442,7 @@ public class SomfyTahomaBindingConstants {
THING_TYPE_ADJUSTABLE_SLATS_ROLLERSHUTTER, THING_TYPE_MYFOX_CAMERA, THING_TYPE_ROLLERSHUTTER_UNO, THING_TYPE_ADJUSTABLE_SLATS_ROLLERSHUTTER, THING_TYPE_MYFOX_CAMERA, THING_TYPE_ROLLERSHUTTER_UNO,
THING_TYPE_WATERSENSOR, THING_TYPE_HUMIDITYSENSOR, THING_TYPE_MYFOX_ALARM, THING_TYPE_THERMOSTAT, THING_TYPE_WATERSENSOR, THING_TYPE_HUMIDITYSENSOR, THING_TYPE_MYFOX_ALARM, THING_TYPE_THERMOSTAT,
THING_TYPE_DIMMER_LIGHT, THING_TYPE_EXTERIOR_HEATING_SYSTEM, THING_TYPE_VALVE_HEATING_SYSTEM, THING_TYPE_DIMMER_LIGHT, THING_TYPE_EXTERIOR_HEATING_SYSTEM, THING_TYPE_VALVE_HEATING_SYSTEM,
THING_TYPE_BIOCLIMATIC_PERGOLA)); THING_TYPE_BIOCLIMATIC_PERGOLA, THING_TYPE_WATERHEATINGSYSTEM));
// somfy gateways // somfy gateways
public static Map<Integer, String> gatewayTypes = new HashMap<Integer, String>() { public static Map<Integer, String> gatewayTypes = new HashMap<Integer, String>() {

View File

@ -50,6 +50,7 @@ import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaThermostatHan
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaUnoRollerShutterHandler; import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaUnoRollerShutterHandler;
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaValveHeatingSystemHandler; import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaValveHeatingSystemHandler;
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaVenetianBlindHandler; import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaVenetianBlindHandler;
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWaterHeatingSystemHandler;
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWaterSensorHandler; import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWaterSensorHandler;
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWindowHandleHandler; import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWindowHandleHandler;
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWindowHandler; import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWindowHandler;
@ -184,6 +185,8 @@ public class SomfyTahomaHandlerFactory extends BaseThingHandlerFactory {
return new SomfyTahomaMyfoxAlarmHandler(thing); return new SomfyTahomaMyfoxAlarmHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_THERMOSTAT)) { } else if (thingTypeUID.equals(THING_TYPE_THERMOSTAT)) {
return new SomfyTahomaThermostatHandler(thing); return new SomfyTahomaThermostatHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_WATERHEATINGSYSTEM)) {
return new SomfyTahomaWaterHeatingSystemHandler(thing);
} else { } else {
return null; return null;
} }

View File

@ -300,6 +300,14 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
logUnsupportedDevice(device); logUnsupportedDevice(device);
} }
break; break;
case CLASS_WATER_HEATING_SYSTEM:
// widget: DomesticHotWaterProduction
if ("DomesticHotWaterProduction".equals(device.getWidget())) {
deviceDiscovered(device, THING_TYPE_WATERHEATINGSYSTEM, place);
} else {
logUnsupportedDevice(device);
}
break;
case CLASS_DOCK: case CLASS_DOCK:
// widget: Dock // widget: Dock
deviceDiscovered(device, THING_TYPE_DOCK, place); deviceDiscovered(device, THING_TYPE_DOCK, place);
@ -323,6 +331,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
// widget: AlarmRemoteController // widget: AlarmRemoteController
case THING_NETWORK_COMPONENT: case THING_NETWORK_COMPONENT:
break; break;
default: default:
logUnsupportedDevice(device); logUnsupportedDevice(device);
} }

View File

@ -28,6 +28,8 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import javax.ws.rs.core.MediaType;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
@ -44,6 +46,8 @@ import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaApplyResponse;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaDevice; import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaDevice;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaEvent; import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaEvent;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaLoginResponse; import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaLoginResponse;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaOauth2Error;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaOauth2Reponse;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaRegisterEventsResponse; import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaRegisterEventsResponse;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaSetup; import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaSetup;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState; import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState;
@ -212,8 +216,17 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
reLoginNeeded = false; reLoginNeeded = false;
try { try {
String urlParameters = "userId=" + urlEncode(thingConfig.getEmail()) + "&userPassword="
+ urlEncode(thingConfig.getPassword()); String urlParameters = "";
// if cozytouch, must use oauth server
if (thingConfig.getCloudPortal().equalsIgnoreCase(COZYTOUCH_PORTAL)) {
logger.debug("CozyTouch Oauth2 authentication flow");
urlParameters = "jwt=" + loginCozytouch();
} else {
urlParameters = "userId=" + urlEncode(thingConfig.getEmail()) + "&userPassword="
+ urlEncode(thingConfig.getPassword());
}
ContentResponse response = sendRequestBuilder("login", HttpMethod.POST) ContentResponse response = sendRequestBuilder("login", HttpMethod.POST)
.content(new StringContentProvider(urlParameters), .content(new StringContentProvider(urlParameters),
@ -250,7 +263,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
logger.debug("Received invalid data (login)", e); logger.debug("Received invalid data (login)", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)"); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)");
} catch (ExecutionException e) { } catch (ExecutionException e) {
if (isAuthenticationChallenge(e)) { if (isAuthenticationChallenge(e) || isOAuthGrantError(e)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error logging in (check your credentials)"); "Error logging in (check your credentials)");
setTooManyRequests(); setTooManyRequests();
@ -646,6 +659,63 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
.agent(TAHOMA_AGENT); .agent(TAHOMA_AGENT);
} }
/**
* Performs the login for Cozytouch using OAUTH2 authorization.
*
* @return JSESSION ID cookie value.
* @throws ExecutionException
* @throws TimeoutException
* @throws InterruptedException
* @throws JsonSyntaxException
*/
private String loginCozytouch()
throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
String authBaseUrl = "https://" + COZYTOUCH_OAUTH2_URL;
String urlParameters = "grant_type=password&username=" + urlEncode(thingConfig.getEmail()) + "&password="
+ urlEncode(thingConfig.getPassword());
ContentResponse response = httpClient.newRequest(authBaseUrl + COZYTOUCH_OAUTH2_TOKEN_URL)
.method(HttpMethod.POST).header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en")
.header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate").header("X-Requested-With", "XMLHttpRequest")
.header(HttpHeader.AUTHORIZATION, "Basic " + COZYTOUCH_OAUTH2_BASICAUTH)
.timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS).agent(TAHOMA_AGENT)
.content(new StringContentProvider(urlParameters), "application/x-www-form-urlencoded; charset=UTF-8")
.send();
if (response.getStatus() != 200) {
// Login error
if (response.getHeaders().getField(HttpHeader.CONTENT_TYPE).getValue()
.equalsIgnoreCase(MediaType.APPLICATION_JSON)) {
try {
SomfyTahomaOauth2Error error = gson.fromJson(response.getContentAsString(),
SomfyTahomaOauth2Error.class);
throw new ExecutionException(error.getErrorDescription(), null);
} catch (JsonSyntaxException e) {
}
}
throw new ExecutionException("Unknown error while attempting to log in.", null);
}
SomfyTahomaOauth2Reponse oauth2response = gson.fromJson(response.getContentAsString(),
SomfyTahomaOauth2Reponse.class);
logger.debug("OAuth2 Access Token: {}", oauth2response.getAccessToken());
response = httpClient.newRequest(authBaseUrl + COZYTOUCH_OAUTH2_JWT_URL).method(HttpMethod.GET)
.header(HttpHeader.AUTHORIZATION, "Bearer " + oauth2response.getAccessToken())
.timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS).send();
if (response.getStatus() == 200) {
String jwt = response.getContentAsString();
return jwt.replace("\"", "");
} else {
throw new ExecutionException(String.format("Failed to retrieve JWT token. ResponseCode=%d, ResponseText=%s",
response.getStatus(), response.getContentAsString()), null);
}
}
private String getApiFullUrl(String subUrl) { private String getApiFullUrl(String subUrl) {
return "https://" + thingConfig.getCloudPortal() + API_BASE_URL + subUrl; return "https://" + thingConfig.getCloudPortal() + API_BASE_URL + subUrl;
} }
@ -796,6 +866,11 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
return msg != null && msg.contains(AUTHENTICATION_CHALLENGE); return msg != null && msg.contains(AUTHENTICATION_CHALLENGE);
} }
private boolean isOAuthGrantError(Exception ex) {
String msg = ex.getMessage();
return msg != null && msg.contains(AUTHENTICATION_OAUTH_GRANT_ERROR);
}
@Override @Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) { public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
super.handleConfigurationUpdate(configurationParameters); super.handleConfigurationUpdate(configurationParameters);

View File

@ -0,0 +1,227 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somfytahoma.internal.handler;
import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SomfyTahomaWaterHeatingSystemHandler} is responsible for handling commands,
* which are sent to one of the channels of the Water Heating system thing.
*
* @author Benjamin Lafois - Initial contribution
*/
@NonNullByDefault
public class SomfyTahomaWaterHeatingSystemHandler extends SomfyTahomaBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SomfyTahomaWaterHeatingSystemHandler.class);
private boolean boostMode = false;
private boolean awayMode = false;
public SomfyTahomaWaterHeatingSystemHandler(Thing thing) {
super(thing);
stateNames.put(MIDDLEWATER_TEMPERATURE, MIDDLE_WATER_TEMPERATURE_STATE);
stateNames.put(TARGET_TEMPERATURE, TARGET_TEMPERATURE_STATE);
stateNames.put(WATER_HEATER_MODE, WATER_HEATER_MODE_STATE);
stateNames.put(BOOST_MODE_DURATION, BOOST_MODE_DURATION_STATE);
// override state type because the cloud sends consumption in percent
cacheStateType(BOOST_MODE_DURATION_STATE, TYPE_DECIMAL);
stateNames.put(AWAY_MODE_DURATION, AWAY_MODE_DURATION_STATE);
// override state type because the cloud sends consumption in percent
cacheStateType(AWAY_MODE_DURATION_STATE, TYPE_DECIMAL);
stateNames.put(HEAT_PUMP_OPERATING_TIME, HEAT_PUMP_OPERATING_TIME_STATE);
// override state type because the cloud sends consumption in percent
cacheStateType(HEAT_PUMP_OPERATING_TIME_STATE, TYPE_DECIMAL);
stateNames.put(ELECTRIC_BOOSTER_OPERATING_TIME, ELECTRIC_BOOSTER_OPERATING_TIME_STATE);
// override state type because the cloud sends consumption in percent
cacheStateType(ELECTRIC_BOOSTER_OPERATING_TIME_STATE, TYPE_DECIMAL);
stateNames.put(POWER_HEAT_PUMP, POWER_HEAT_PUMP_STATE);
// override state type because the cloud sends consumption in percent
cacheStateType(POWER_HEAT_PUMP_STATE, TYPE_DECIMAL);
stateNames.put(POWER_HEAT_ELEC, POWER_HEAT_ELEC_STATE);
// override state type because the cloud sends consumption in percent
cacheStateType(POWER_HEAT_ELEC_STATE, TYPE_DECIMAL);
}
@Override
public void updateThingChannels(SomfyTahomaState state) {
if (OPERATING_MODE_STATE.equals(state.getName()) && state.getValue() instanceof Map) {
logger.debug("Operating Mode State: {} {}", state.getValue().getClass().getName(), state.getValue());
Map<String, String> data = (Map<String, String>) state.getValue();
Object relaunchValue = data.get("relaunch");
if (relaunchValue != null) {
this.boostMode = relaunchValue.toString().equalsIgnoreCase("on");
logger.debug("Boost Value: {}", this.boostMode);
updateState(BOOST_MODE, OnOffType.from(this.boostMode));
}
Object awayValue = data.get("absence");
if (awayValue != null) {
this.awayMode = awayValue.toString().equalsIgnoreCase("on");
logger.debug("Away Value: {}", this.awayMode);
updateState(AWAY_MODE, OnOffType.from(this.awayMode));
}
} else if (TARGET_TEMPERATURE_STATE.equals(state.getName())) {
logger.debug("Target Temperature: {}", state.getValue());
// 50 -> 3
// 54.5 -> 4
// 62 -> 5
Double temp = null;
try {
temp = Double.parseDouble(state.getValue().toString());
int v = 0;
if (temp == 50) {
v = 3;
} else if (temp == 54.5) {
v = 4;
} else if (temp == 62) {
v = 5;
}
updateState(SHOWERS, new DecimalType(v));
} catch (NumberFormatException e) {
logger.warn("Unexpected pre-defined value for Target State Temperature: {}", state.getValue());
return;
}
}
super.updateThingChannels(state);
}
private void sendOperatingMode() {
sendCommand(COMMAND_SET_CURRENT_OPERATING_MODE, String.format("[ { \"relaunch\":\"%s\", \"absence\":\"%s\"} ]",
(this.boostMode ? "on" : "off"), (this.awayMode ? "on" : "off")));
}
private void sendBoostDuration(int duration) {
sendCommand(COMMAND_SET_BOOST_MODE_DURATION, "[ " + duration + " ]");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
if (command instanceof RefreshType) {
return;
} else {
logger.debug("Command received: {}/{}", channelUID.getId(), command.toString());
if (BOOST_MODE_DURATION.equals(channelUID.getId())) {
int duration = 0;
try {
duration = Integer.parseInt(command.toString());
} catch (NumberFormatException e) {
logger.debug("Invalid value received for boost mode duration: {}", command);
return;
}
if (duration == 0) {
this.boostMode = false;
sendOperatingMode();
} else if (duration > 0 && duration < 8) {
this.boostMode = true;
sendOperatingMode();
sendBoostDuration(duration);
}
} else if (WATER_HEATER_MODE.equals(channelUID.getId())) {
sendCommand(COMMAND_SET_WATER_HEATER_MODE, "[ \"" + command.toString() + "\" ]");
} else if (AWAY_MODE_DURATION.equals(channelUID.getId())) {
sendCommand(COMMAND_SET_AWAY_MODE_DURATION, "[ \"" + command.toString() + "\" ]");
} else if (BOOST_MODE.equals(channelUID.getId()) && command instanceof OnOffType) {
if (command == OnOffType.ON) {
if (this.boostMode) {
return;
}
this.boostMode = true;
scheduler.execute(() -> {
sendBoostDuration(1); // by default, boost for 1 day
});
scheduler.schedule(() -> {
sendCommand(COMMAND_REFRESH_DHWMODE, "[ ]");
}, 1, TimeUnit.SECONDS);
scheduler.schedule(() -> {
sendOperatingMode();
}, 2, TimeUnit.SECONDS);
scheduler.schedule(() -> {
sendCommand(COMMAND_REFRESH_BOOST_MODE_DURATION, "[ ]");
}, 3, TimeUnit.SECONDS);
} else {
this.boostMode = false;
sendOperatingMode();
}
} else if (AWAY_MODE.equals(channelUID.getId()) && command instanceof OnOffType) {
if (command == OnOffType.ON) {
this.boostMode = false;
this.awayMode = true;
} else {
this.awayMode = false;
}
sendOperatingMode();
} else if (SHOWERS.equals(channelUID.getId())) {
int showers = 0;
try {
showers = Integer.parseInt(command.toString());
} catch (NumberFormatException e) {
logger.info("Received an invalid value for desired number of showers: {}", command);
return;
}
Double value = 0.0;
switch (showers) {
case 3:
value = 50.0;
break;
case 4:
value = 54.5;
break;
case 5:
value = 62.0;
break;
default:
break;
}
sendCommand(COMMAND_SET_TARGET_TEMPERATURE, "[ " + value.toString() + " ]");
}
}
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somfytahoma.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SomfyTahomaOauth2Error} is used to parse login error from API server.
*
* @author Benjamin Lafois - Initial contribution
*/
@NonNullByDefault
public class SomfyTahomaOauth2Error {
private String error = "";
@SerializedName("error_description")
private String errorDescription = "";
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getErrorDescription() {
return errorDescription;
}
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
}

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somfytahoma.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SomfyTahomaOauth2Reponse} holds information about Oauth2 login
* response to your CozyTouch account.
*
* @author Benjamin Lafois - Initial contribution
*/
@NonNullByDefault
public class SomfyTahomaOauth2Reponse {
private String scope = "";
@SerializedName("token_type")
private String tokenType = "";
@SerializedName("expires_in")
private int expiresIn = 0;
@SerializedName("refresh_token")
private String refreshToken = "";
@SerializedName("access_token")
private String accessToken = "";
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
}

View File

@ -47,6 +47,7 @@ thing-type.somfytahoma.temperaturesensor.label = Somfy Temperature Sensor
thing-type.somfytahoma.thermostat.label = Somfy Thermostat thing-type.somfytahoma.thermostat.label = Somfy Thermostat
thing-type.somfytahoma.valveheatingsystem.label = Somfy Thermostatic Valve thing-type.somfytahoma.valveheatingsystem.label = Somfy Thermostatic Valve
thing-type.somfytahoma.venetianblind.label = Somfy Venetian Blind thing-type.somfytahoma.venetianblind.label = Somfy Venetian Blind
thing-type.somfytahoma.waterheatingsystem.label = Somfy Water Heating System
thing-type.somfytahoma.watersensor.label = Somfy Water Sensor thing-type.somfytahoma.watersensor.label = Somfy Water Sensor
thing-type.somfytahoma.window.label = Somfy Window thing-type.somfytahoma.window.label = Somfy Window
thing-type.somfytahoma.windowhandle.label = Somfy Window Handle thing-type.somfytahoma.windowhandle.label = Somfy Window Handle
@ -95,10 +96,18 @@ channel-type.somfytahoma.alarm_command.state.option.alarmPartial1 = ARM_PARTIAL_
channel-type.somfytahoma.alarm_command.state.option.alarmPartial2 = ARM_PARTIAL_2 channel-type.somfytahoma.alarm_command.state.option.alarmPartial2 = ARM_PARTIAL_2
channel-type.somfytahoma.alarm_state.label = Alarm State channel-type.somfytahoma.alarm_state.label = Alarm State
channel-type.somfytahoma.alarm_state.description = A state of the Somfy Alarm channel-type.somfytahoma.alarm_state.description = A state of the Somfy Alarm
channel-type.somfytahoma.away_mode.label = Away
channel-type.somfytahoma.away_mode.description = Away Mode (ON or OFF)
channel-type.somfytahoma.away_mode_duration.label = Away Mode Duration
channel-type.somfytahoma.away_mode_duration.description = Duration for Away Mode
channel-type.somfytahoma.battery.label = Battery State channel-type.somfytahoma.battery.label = Battery State
channel-type.somfytahoma.battery.description = Battery Condition State (full, low, normal, verylow) channel-type.somfytahoma.battery.description = Battery Condition State (full, low, normal, verylow)
channel-type.somfytahoma.battery_status.label = Battery Status State channel-type.somfytahoma.battery_status.label = Battery Status State
channel-type.somfytahoma.battery_status.description = Battery Status State channel-type.somfytahoma.battery_status.description = Battery Status State
channel-type.somfytahoma.boost_mode.label = Boost
channel-type.somfytahoma.boost_mode.description = Boost Mode (ON or OFF)
channel-type.somfytahoma.boost_mode_duration.label = Boost Mode Duration
channel-type.somfytahoma.boost_mode_duration.description = Duration for Boost Mode
channel-type.somfytahoma.closure_orientation.label = Set Closure And Orientation channel-type.somfytahoma.closure_orientation.label = Set Closure And Orientation
channel-type.somfytahoma.closure_orientation.description = A channel for setting closure and orientation of the blind channel-type.somfytahoma.closure_orientation.description = A channel for setting closure and orientation of the blind
channel-type.somfytahoma.cloud_status.label = Cloud Status State channel-type.somfytahoma.cloud_status.label = Cloud Status State
@ -127,6 +136,8 @@ channel-type.somfytahoma.derogation_heating_mode.command.option.comfort = Home
channel-type.somfytahoma.derogation_heating_mode.command.option.frost\ protection = Frost protection channel-type.somfytahoma.derogation_heating_mode.command.option.frost\ protection = Frost protection
channel-type.somfytahoma.derogation_heating_mode.command.option.manual = Manual channel-type.somfytahoma.derogation_heating_mode.command.option.manual = Manual
channel-type.somfytahoma.derogation_heating_mode.command.option.eco = Night channel-type.somfytahoma.derogation_heating_mode.command.option.eco = Night
channel-type.somfytahoma.electric_booster_operating_time.label = Electric Bosster Operating Time
channel-type.somfytahoma.electric_booster_operating_time.description = Time the Electric Bosster has been operating in hours
channel-type.somfytahoma.energy_consumption.label = Energy Consumption channel-type.somfytahoma.energy_consumption.label = Energy Consumption
channel-type.somfytahoma.energy_consumption.description = The energy consumption reported by the sensor channel-type.somfytahoma.energy_consumption.description = The energy consumption reported by the sensor
channel-type.somfytahoma.execute_action.label = Somfy Action Group Trigger channel-type.somfytahoma.execute_action.label = Somfy Action Group Trigger
@ -143,6 +154,8 @@ channel-type.somfytahoma.gate_state.label = Gate Status
channel-type.somfytahoma.gate_state.description = A channel used for getting the gate state (open, closed, pedestrian) channel-type.somfytahoma.gate_state.description = A channel used for getting the gate state (open, closed, pedestrian)
channel-type.somfytahoma.handle_state.label = Handle State channel-type.somfytahoma.handle_state.label = Handle State
channel-type.somfytahoma.handle_state.description = A state of the Somfy Window Handle channel-type.somfytahoma.handle_state.description = A state of the Somfy Window Handle
channel-type.somfytahoma.heat_pump_operating_time.label = Heat Pump Operating Time
channel-type.somfytahoma.heat_pump_operating_time.description = Time the Heat Pump has been operating in hours
channel-type.somfytahoma.heating_level.label = Heating Level channel-type.somfytahoma.heating_level.label = Heating Level
channel-type.somfytahoma.heating_level.description = The level of the heating channel-type.somfytahoma.heating_level.description = The level of the heating
channel-type.somfytahoma.heating_mode.label = Heating Mode channel-type.somfytahoma.heating_mode.label = Heating Mode
@ -171,6 +184,8 @@ channel-type.somfytahoma.memorized_volume.label = Memorized Volume
channel-type.somfytahoma.memorized_volume.description = A channel used for controlling siren's volume state channel-type.somfytahoma.memorized_volume.description = A channel used for controlling siren's volume state
channel-type.somfytahoma.memorized_volume.state.option.normal = NORMAL channel-type.somfytahoma.memorized_volume.state.option.normal = NORMAL
channel-type.somfytahoma.memorized_volume.state.option.highest = HIGHEST channel-type.somfytahoma.memorized_volume.state.option.highest = HIGHEST
channel-type.somfytahoma.middlewater_temperature.label = Middle Water Temperature
channel-type.somfytahoma.middlewater_temperature.description = Describes the temperature at the middle sensor
channel-type.somfytahoma.myfox_alarm_command.label = Command channel-type.somfytahoma.myfox_alarm_command.label = Command
channel-type.somfytahoma.myfox_alarm_command.description = A channel used for sending commands to Somfy Myfox Alarm device channel-type.somfytahoma.myfox_alarm_command.description = A channel used for sending commands to Somfy Myfox Alarm device
channel-type.somfytahoma.myfox_alarm_command.state.option.arm = ARM channel-type.somfytahoma.myfox_alarm_command.state.option.arm = ARM
@ -189,6 +204,10 @@ channel-type.somfytahoma.pergola_command.description = A channel used for sendin
channel-type.somfytahoma.pergola_command.state.option.closeSlats = Close slats channel-type.somfytahoma.pergola_command.state.option.closeSlats = Close slats
channel-type.somfytahoma.pergola_command.state.option.openSlats = Open slats channel-type.somfytahoma.pergola_command.state.option.openSlats = Open slats
channel-type.somfytahoma.pergola_command.state.option.stop = Stop channel-type.somfytahoma.pergola_command.state.option.stop = Stop
channel-type.somfytahoma.power_heatelec.label = Electrical Heater Active
channel-type.somfytahoma.power_heatelec.description = Indicates current status of electrical heater in watts
channel-type.somfytahoma.power_heatpump.label = Heat Pump Power
channel-type.somfytahoma.power_heatpump.description = Indicates current status of heatpump in watts
channel-type.somfytahoma.radio_battery.label = Radio Part Battery State channel-type.somfytahoma.radio_battery.label = Radio Part Battery State
channel-type.somfytahoma.radio_battery.description = State of the radio part of the Somfy sensor channel-type.somfytahoma.radio_battery.description = State of the radio part of the Somfy sensor
channel-type.somfytahoma.rocker.label = Rocker Position channel-type.somfytahoma.rocker.label = Rocker Position
@ -203,6 +222,8 @@ channel-type.somfytahoma.sensor_defect.label = Sensor Defect State
channel-type.somfytahoma.sensor_defect.description = State of the Somfy sensor (dead, lowBattery, noDefect...) channel-type.somfytahoma.sensor_defect.description = State of the Somfy sensor (dead, lowBattery, noDefect...)
channel-type.somfytahoma.short_beep.label = Dock Short Beep Test channel-type.somfytahoma.short_beep.label = Dock Short Beep Test
channel-type.somfytahoma.short_beep.description = A channel for testing the dock's short beeping channel-type.somfytahoma.short_beep.description = A channel for testing the dock's short beeping
channel-type.somfytahoma.showers.label = Number of showers
channel-type.somfytahoma.showers.description = 3 to 5 showers
channel-type.somfytahoma.shutter.label = Myfox Shutter channel-type.somfytahoma.shutter.label = Myfox Shutter
channel-type.somfytahoma.shutter.description = A channel for controlling the shutter channel-type.somfytahoma.shutter.description = A channel for controlling the shutter
channel-type.somfytahoma.siren_status.label = Siren Status State channel-type.somfytahoma.siren_status.label = Siren Status State
@ -224,3 +245,8 @@ channel-type.somfytahoma.target_temperature.label = Target Temperature
channel-type.somfytahoma.target_temperature.description = The target temperature of the heating system channel-type.somfytahoma.target_temperature.description = The target temperature of the heating system
channel-type.somfytahoma.temperature.label = Temperature channel-type.somfytahoma.temperature.label = Temperature
channel-type.somfytahoma.temperature.description = The temperature value of the sensor channel-type.somfytahoma.temperature.description = The temperature value of the sensor
channel-type.somfytahoma.water_heater_mode.label = Mode
channel-type.somfytahoma.water_heater_mode.description = Describes the water heater mode
channel-type.somfytahoma.water_heater_mode.state.option.manualEcoActive = Manual - Eco
channel-type.somfytahoma.water_heater_mode.state.option.manualEcoInactive = Manual
channel-type.somfytahoma.water_heater_mode.state.option.autoMode = Auto

View File

@ -424,4 +424,87 @@
<state pattern="%.1f" readOnly="true"/> <state pattern="%.1f" readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="boost_mode_duration">
<item-type>Number</item-type>
<label>Boost Mode Duration</label>
<description>Duration for Boost Mode</description>
<state min="0" max="7" step="1" pattern="%d"/>
</channel-type>
<channel-type id="away_mode_duration">
<item-type>Number</item-type>
<label>Away Mode Duration</label>
<description>Duration for Away Mode</description>
<state min="0" max="7" step="1" pattern="%d"/>
</channel-type>
<channel-type id="heat_pump_operating_time">
<item-type>Number</item-type>
<label>Heat Pump Operating Time</label>
<description>Time the Heat Pump has been operating in hours</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="electric_booster_operating_time">
<item-type>Number</item-type>
<label>Electric Bosster Operating Time</label>
<description>Time the Electric Bosster has been operating in hours</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="power_heatpump">
<item-type>Number</item-type>
<label>Heat Pump Power</label>
<description>Indicates current status of heatpump in watts</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="power_heatelec">
<item-type>Number</item-type>
<label>Electrical Heater Active</label>
<description>Indicates current status of electrical heater in watts</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="water_heater_mode">
<item-type>String</item-type>
<label>Mode</label>
<description>Describes the water heater mode</description>
<state>
<options>
<option value="manualEcoActive">Manual - Eco</option>
<option value="manualEcoInactive">Manual</option>
<option value="autoMode">Auto</option>
</options>
</state>
</channel-type>
<channel-type id="showers">
<item-type>Number</item-type>
<label>Number of showers</label>
<description>3 to 5 showers</description>
<state min="3" max="5" step="1" pattern="%d"/>
</channel-type>
<channel-type id="middlewater_temperature">
<item-type>Number:Temperature</item-type>
<label>Middle Water Temperature</label>
<description>Describes the temperature at the middle sensor</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="boost_mode">
<item-type>Switch</item-type>
<label>Boost</label>
<description>Boost Mode (ON or OFF)</description>
</channel-type>
<channel-type id="away_mode">
<item-type>Switch</item-type>
<label>Away</label>
<description>Away Mode (ON or OFF)</description>
</channel-type>
</thing:thing-descriptions> </thing:thing-descriptions>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="somfytahoma"
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">
<thing-type id="waterheatingsystem">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Somfy Water Heating System</label>
<channels>
<channel id="target_temperature" typeId="temperature"></channel>
<channel id="middlewater_temperature" typeId="middlewater_temperature"></channel>
<channel id="boost_mode" typeId="boost_mode"></channel>
<channel id="away_mode" typeId="away_mode"></channel>
<channel id="boost_mode_duration" typeId="boost_mode_duration"></channel>
<channel id="away_mode_duration" typeId="away_mode_duration"></channel>
<channel id="heat_pump_operating_time" typeId="heat_pump_operating_time"></channel>
<channel id="electric_booster_operating_time" typeId="electric_booster_operating_time"></channel>
<channel id="power_heatpump" typeId="power_heatpump"></channel>
<channel id="power_heatelec" typeId="power_heatelec"></channel>
<channel id="mode" typeId="water_heater_mode"></channel>
<channel id="showers" typeId="showers"></channel>
</channels>
<representation-property>url</representation-property>
<config-description-ref uri="thing-type:somfytahoma:device"/>
</thing-type>
</thing:thing-descriptions>