diff --git a/bundles/org.openhab.binding.somfytahoma/README.md b/bundles/org.openhab.binding.somfytahoma/README.md index be25f76e8..360182de3 100644 --- a/bundles/org.openhab.binding.somfytahoma/README.md +++ b/bundles/org.openhab.binding.somfytahoma/README.md @@ -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 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: diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java index 943505675..ee4648ba3 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java @@ -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 gatewayTypes = new HashMap() { diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaHandlerFactory.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaHandlerFactory.java index 9daa7ef1f..ab4c978ee 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaHandlerFactory.java +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaHandlerFactory.java @@ -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; } diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/discovery/SomfyTahomaItemDiscoveryService.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/discovery/SomfyTahomaItemDiscoveryService.java index 7b4e4e6de..04af86178 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/discovery/SomfyTahomaItemDiscoveryService.java +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/discovery/SomfyTahomaItemDiscoveryService.java @@ -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); } diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java index 7612af5cb..64203ffb6 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java @@ -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 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 index 000000000..183555f3b --- /dev/null +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaWaterHeatingSystemHandler.java @@ -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 data = (Map) 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 index 000000000..49e7b8b9d --- /dev/null +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Error.java @@ -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 index 000000000..26684b3e4 --- /dev/null +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/model/SomfyTahomaOauth2Reponse.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/i18n/somfytahoma.properties b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/i18n/somfytahoma.properties index 804228087..97f23a0c8 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/i18n/somfytahoma.properties +++ b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/i18n/somfytahoma.properties @@ -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 diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/channels.xml index 5b607db82..9b72a9c28 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/channels.xml @@ -424,4 +424,87 @@ + + Number + + Duration for Boost Mode + + + + + Number + + Duration for Away Mode + + + + + Number + + Time the Heat Pump has been operating in hours + + + + + Number + + Time the Electric Bosster has been operating in hours + + + + + Number + + Indicates current status of heatpump in watts + + + + + Number + + Indicates current status of electrical heater in watts + + + + + + String + + Describes the water heater mode + + + + + + + + + + + Number + + 3 to 5 showers + + + + + Number:Temperature + + Describes the temperature at the middle sensor + + + + + Switch + + Boost Mode (ON or OFF) + + + + Switch + + Away Mode (ON or OFF) + + + 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 index 000000000..c8d970b72 --- /dev/null +++ b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/waterheatingsystem.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + url + + + +