]> git.basschouten.com Git - openhab-addons.git/commitdiff
[somfytahoma] [Improvement] Fix cozytouch support and add waterheatersystem support...
authorBenjamin Lafois <benjamin.lafois@gmail.com>
Wed, 26 Jan 2022 19:12:46 +0000 (20:12 +0100)
committerGitHub <noreply@github.com>
Wed, 26 Jan 2022 19:12:46 +0000 (20:12 +0100)
* Add support for Cozytouch and WaterHeaterSystem

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
bundles/org.openhab.binding.somfytahoma/README.md
bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java
bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaHandlerFactory.java
bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/discovery/SomfyTahomaItemDiscoveryService.java
bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java
bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaWaterHeatingSystemHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Error.java [new file with mode: 0644]
bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Reponse.java [new file with mode: 0644]
bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/i18n/somfytahoma.properties
bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/channels.xml
bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/waterheatingsystem.xml [new file with mode: 0644]

index be25f76e8199c577f1c14d44ed48236e05ac5c39..360182de3880be5d0972553d40e8c11c0bea5bd9 100644 (file)
@@ -40,17 +40,18 @@ 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)
 - 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)
+- water heater system (monitor and control)
 
 Both Somfy Tahoma and Somfy Connexoon gateways have been confirmed working.
 
 ## Discovery
 
 To start a discovery, just
+
 - Add a new bridge thing.
 - Configure the bridge selecting your cloud portal (www.tahomalink.com by default) and setting your email (login) and password to the cloud portal.
-If the supplied credentials are correct, the automatic discovery can be used to scan and detect roller shutters, awnings, switches and action groups that will appear in your Inbox. 
+
+If the supplied credentials are correct, the automatic discovery can be used to scan and detect roller shutters, awnings, switches and action groups that will appear in your Inbox.
 
 ## Thing Configuration
 
@@ -123,6 +124,18 @@ Please see the example below.
 | myfox camera, myfox alarm                                                     | cloud_status                 | cloud connection status                                                                                                     |
 | myfox camera                                                                  | shutter                      | controlling of the camera shutter                                                                                           |
 | 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.
 You can list all the scenarios IDs with the following console command: `somfytahoma <bridgeUID> scenarios`.
@@ -174,7 +187,7 @@ Bridge somfytahoma:bridge:237dbae7 "Somfy Tahoma Bridge" [ email="my@email.com",
 }
 ```
 
-Awnings, garage doors, screens, blinds, and windows things have the same notation as roller shutters. Just use "awning", "garagedoor", "screen", "blind" or "window" instead of "rolleshutter" in thing definition. 
+Awnings, garage doors, screens, blinds, and windows things have the same notation as roller shutters. Just use "awning", "garagedoor", "screen", "blind" or "window" instead of "rolleshutter" in thing definition.
 
 .items file
 
@@ -267,7 +280,7 @@ Slider item=HeatingLevel
 
 ## Alexa compatibility
 
-This binding is compatible with the official Alexa Smart Home Skill. 
+This binding is compatible with the official Alexa Smart Home Skill.
 Since Rolleshutter items are unsupported, only Dimmer with control channel can be used.
 Syntax in .item file is as follows:
 
index 943505675e20f239a9b05dcc2178735237a84369..ee4648ba35b1661b88c2d1940bc75699eacd080c 100644 (file)
@@ -144,6 +144,9 @@ public class SomfyTahomaBindingConstants {
     // Electricity sensor
     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
     public static final ThingTypeUID THING_TYPE_DOCK = new ThingTypeUID(BINDING_ID, "dock");
 
@@ -260,6 +263,20 @@ public class SomfyTahomaBindingConstants {
     // ElectricitySensor
     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
     public static final String BATTERY_STATUS = "battery_status";
     public static final String SIREN_STATUS = "siren_status";
@@ -281,7 +298,12 @@ public class SomfyTahomaBindingConstants {
     public static final String SHUTTER = "shutter";
 
     // Constants
+    public static final String COZYTOUCH_PORTAL = "ha110-1.overkiz.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 EVENTS_URL = "events/";
     public static final String SETUP_URL = "setup/";
@@ -300,6 +322,7 @@ public class SomfyTahomaBindingConstants {
     public static final int TYPE_BOOLEAN = 6;
     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_OAUTH_GRANT_ERROR = "Provided Authorization Grant is invalid.";
     public static final String TOO_MANY_REQUESTS = "Too many requests, try again later";
     public static final int SUSPEND_TIME = 120;
     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_OFF = "off";
     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
+    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 RSSI_LEVEL_STATE = "core:RSSILevelState";
     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 SIREN_STATUS_STATE = "internal:SirenStatusState";
     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 SMOKE_STATE = "core:SmokeState";
     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_ADJUSTABLE_SLATS_ROLLER_SHUTTER = "AdjustableSlatsRollerShutter";
     public static final String CLASS_CAMERA = "Camera";
+    public static final String CLASS_WATER_HEATING_SYSTEM = "WaterHeatingSystem";
 
     // unsupported uiClasses
     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_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_BIOCLIMATIC_PERGOLA));
+            THING_TYPE_BIOCLIMATIC_PERGOLA, THING_TYPE_WATERHEATINGSYSTEM));
 
     // somfy gateways
     public static Map<Integer, String> gatewayTypes = new HashMap<Integer, String>() {
index 9daa7ef1f5f6b0ba18bf80662e281734a07d34ea..ab4c978ee63da9e62c7a03504eef286de6803025 100644 (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.SomfyTahomaValveHeatingSystemHandler;
 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.SomfyTahomaWindowHandleHandler;
 import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaWindowHandler;
@@ -184,6 +185,8 @@ public class SomfyTahomaHandlerFactory extends BaseThingHandlerFactory {
             return new SomfyTahomaMyfoxAlarmHandler(thing);
         } else if (thingTypeUID.equals(THING_TYPE_THERMOSTAT)) {
             return new SomfyTahomaThermostatHandler(thing);
+        } else if (thingTypeUID.equals(THING_TYPE_WATERHEATINGSYSTEM)) {
+            return new SomfyTahomaWaterHeatingSystemHandler(thing);
         } else {
             return null;
         }
index 7b4e4e6def6131793476faffb115a27f45978c6d..04af86178f8e5341b8432bd687da5ca871ffd414 100644 (file)
@@ -300,6 +300,14 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
                     logUnsupportedDevice(device);
                 }
                 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:
                 // widget: Dock
                 deviceDiscovered(device, THING_TYPE_DOCK, place);
@@ -323,6 +331,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
                 // widget: AlarmRemoteController
             case THING_NETWORK_COMPONENT:
                 break;
+
             default:
                 logUnsupportedDevice(device);
         }
index 7612af5cb39f16d8d4089a802276f9b639cf9e1b..64203ffb6f14dfc241a955536b81d2ef3a9d9011 100644 (file)
@@ -28,6 +28,8 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import javax.ws.rs.core.MediaType;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 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.SomfyTahomaEvent;
 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.SomfyTahomaSetup;
 import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState;
@@ -212,8 +216,17 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
         reLoginNeeded = false;
 
         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)
                     .content(new StringContentProvider(urlParameters),
@@ -250,7 +263,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
             logger.debug("Received invalid data (login)", e);
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)");
         } catch (ExecutionException e) {
-            if (isAuthenticationChallenge(e)) {
+            if (isAuthenticationChallenge(e) || isOAuthGrantError(e)) {
                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                         "Error logging in (check your credentials)");
                 setTooManyRequests();
@@ -646,6 +659,63 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
                 .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) {
         return "https://" + thingConfig.getCloudPortal() + API_BASE_URL + subUrl;
     }
@@ -796,6 +866,11 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
         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
     public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
         super.handleConfigurationUpdate(configurationParameters);
diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaWaterHeatingSystemHandler.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaWaterHeatingSystemHandler.java
new file mode 100644 (file)
index 0000000..183555f
--- /dev/null
@@ -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() + " ]");
+            }
+
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Error.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Error.java
new file mode 100644 (file)
index 0000000..49e7b8b
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Reponse.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Reponse.java
new file mode 100644 (file)
index 0000000..26684b3
--- /dev/null
@@ -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;
+    }
+}
index 804228087325cb920c5feabbc441429dbf8c0880..97f23a0c862afd4a84485276a9ddb1f6afc66d67 100644 (file)
@@ -47,6 +47,7 @@ thing-type.somfytahoma.temperaturesensor.label = Somfy Temperature Sensor
 thing-type.somfytahoma.thermostat.label = Somfy Thermostat
 thing-type.somfytahoma.valveheatingsystem.label = Somfy Thermostatic Valve
 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.window.label = Somfy Window
 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_state.label = Alarm State
 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.description = Battery Condition State (full, low, normal, verylow)
 channel-type.somfytahoma.battery_status.label = 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.description = A channel for setting closure and orientation of the blind
 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.manual = Manual
 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.description = The energy consumption reported by the sensor
 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.handle_state.label = Handle State
 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.description = The level of the heating
 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.state.option.normal = NORMAL
 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.description = A channel used for sending commands to Somfy Myfox Alarm device
 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.openSlats = Open slats
 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.description = State of the radio part of the Somfy sensor
 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.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.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.description = A channel for controlling the shutter
 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.temperature.label = Temperature
 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
index 5b607db82ce868dbcbf6567966a362094adaed16..9b72a9c2868062b2ade6b7ce704e70cfc4689af6 100644 (file)
                <state pattern="%.1f" readOnly="true"/>
        </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>
diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/waterheatingsystem.xml b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/waterheatingsystem.xml
new file mode 100644 (file)
index 0000000..c8d970b
--- /dev/null
@@ -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>