[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,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:

View 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>() {

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.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;
}

View 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);
}

View 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);

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.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

View File

@ -424,4 +424,87 @@
<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>

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>