From 892221ccad0157771c64bb2acbb25b4f2910cfdb Mon Sep 17 00:00:00 2001 From: lolodomo Date: Sun, 9 May 2021 20:16:14 +0200 Subject: [PATCH] [somfytahoma] Open to other portals (#10611) * [somfytahoma] Open to other portals Signed-off-by: Laurent Garnier * Review comment: suppress the advanced setting for cookie handling Signed-off-by: Laurent Garnier --- .../org.openhab.binding.somfytahoma/README.md | 13 +++-- .../internal/SomfyTahomaBindingConstants.java | 11 ++-- .../internal/config/SomfyTahomaConfig.java | 11 ++++ .../handler/SomfyTahomaBridgeHandler.java | 58 ++++++++++--------- .../main/resources/OH-INF/binding/binding.xml | 3 +- .../main/resources/OH-INF/config/config.xml | 24 ++++++-- .../main/resources/OH-INF/thing/bridge.xml | 4 +- 7 files changed, 80 insertions(+), 44 deletions(-) diff --git a/bundles/org.openhab.binding.somfytahoma/README.md b/bundles/org.openhab.binding.somfytahoma/README.md index bb2536fec..f44a5ed69 100644 --- a/bundles/org.openhab.binding.somfytahoma/README.md +++ b/bundles/org.openhab.binding.somfytahoma/README.md @@ -5,13 +5,14 @@ This binding integrates the and [Somfy Connexoon](https://www.somfy.fr/produits/1811429/) home automation systems. +Any home automation system based on the OverKiz API is potentially supported. ## Supported Things Currently these things are supported: -- bridge (Somfy Tahoma bridge, which can discover gateways, roller shutters, awnings, switches and action groups) -- gateways (Somfy Tahoma gateway - gateway status) +- bridge (cloud bridge, which can discover gateways, roller shutters, awnings, switches and action groups) +- gateways (gateway status) - gates (control gate, get state) - roller shutters (UP, DOWN, STOP control of a roller shutter). IO Homecontrol devices are allowed to set exact position of a shutter (0-100%) - blinds (UP, DOWN, STOP control of a blind). IO Homecontrol devices are allowed to set exact position of a blinds (0-100%) as well as orientation of slats (0-100%) @@ -46,10 +47,10 @@ Both Somfy Tahoma and Somfy Connexoon gateways have been confirmed working. To start a discovery, just -- Add a new Somfy Tahoma bridge thing. -- Configure the bridge with your email (login) and password to the TahomaLink cloud portal. +- 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 TahomaLink 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 @@ -61,7 +62,7 @@ Please see the example below. | Thing | Channel | Note | |-------------------------------------------------------------------------------|------------------------------|-----------------------------------------------------------------------------------------------------------------------------| | bridge | N.A | bridge does not expose any channel | -| gateway | status | status of your Tahoma gateway | +| gateway | status | status of your gateway | | gateway | scenarios | used to run the scenarios defined in the cloud portal | | gate | gate_command | used for controlling your gate (open, close, stop, pedestrian) | | gate | gate_state | get state of your gate (open, closed, pedestrian) | 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 5e5fe9561..8d27c2f73 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 @@ -26,6 +26,7 @@ import org.openhab.core.thing.ThingTypeUID; * used across the whole binding. * * @author Ondrej Pecta - Initial contribution + * @author Laurent Garnier - Other portals integration */ @NonNullByDefault public class SomfyTahomaBindingConstants { @@ -280,13 +281,14 @@ public class SomfyTahomaBindingConstants { public static final String SHUTTER = "shutter"; // Constants - public static final String TAHOMA_API_URL = "https://www.tahomalink.com/enduser-mobile-web/enduserAPI/"; - public static final String TAHOMA_EVENTS_URL = TAHOMA_API_URL + "events/"; - public static final String SETUP_URL = TAHOMA_API_URL + "setup/"; + public static final String TAHOMA_PORTAL = "www.tahomalink.com"; + 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/"; public static final String GATEWAYS_URL = SETUP_URL + "gateways/"; public static final String DEVICES_URL = SETUP_URL + "devices/"; public static final String REFRESH_URL = DEVICES_URL + "states/refresh"; - public static final String EXEC_URL = TAHOMA_API_URL + "exec/"; + public static final String EXEC_URL = "exec/"; public static final String DELETE_URL = EXEC_URL + "current/setup/"; public static final String TAHOMA_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"; public static final int TAHOMA_TIMEOUT = 5; @@ -415,6 +417,7 @@ public class SomfyTahomaBindingConstants { put(29, "TAHOMA_V2"); put(30, "KIZBOX_V2_3H"); put(31, "KIZBOX_V2_2H"); + put(32, "COZYTOUCH"); put(34, "CONNEXOON"); put(35, "JSW_CAMERA"); put(37, "KIZBOX_MINI_DAUGHTERBOARD"); diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/config/SomfyTahomaConfig.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/config/SomfyTahomaConfig.java index 5ead6ad4d..8f3775218 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/config/SomfyTahomaConfig.java +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/config/SomfyTahomaConfig.java @@ -13,15 +13,18 @@ package org.openhab.binding.somfytahoma.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants; /** * The {@link SomfyTahomaConfig} is is the base class for configuration * information held by devices and modules. * * @author Ondrej Pecta - Initial contribution + * @author Laurent Garnier - new parameter portalUrl */ @NonNullByDefault public class SomfyTahomaConfig { + private String cloudPortal = SomfyTahomaBindingConstants.TAHOMA_PORTAL; private String email = ""; private String password = ""; private int refresh = 30; @@ -29,6 +32,10 @@ public class SomfyTahomaConfig { private int retries = 10; private int retryDelay = 1000; + public String getCloudPortal() { + return cloudPortal; + } + public String getEmail() { return email; } @@ -53,6 +60,10 @@ public class SomfyTahomaConfig { return retryDelay; } + public void setCloudPortal(String cloudPortal) { + this.cloudPortal = cloudPortal; + } + public void setEmail(String email) { this.email = email; } 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 daa3b4c41..e722b74ca 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 @@ -73,6 +73,7 @@ import com.google.gson.JsonSyntaxException; * sent to one of the channels. * * @author Ondrej Pecta - Initial contribution + * @author Laurent Garnier - Other portals integration */ @NonNullByDefault public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { @@ -193,8 +194,6 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { } public synchronized void login() { - String url; - if (thingConfig.getEmail().isEmpty() || thingConfig.getPassword().isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Can not access device as username and/or password are null"); @@ -214,11 +213,10 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { reLoginNeeded = false; try { - url = TAHOMA_API_URL + "login"; String urlParameters = "userId=" + urlEncode(thingConfig.getEmail()) + "&userPassword=" + urlEncode(thingConfig.getPassword()); - ContentResponse response = sendRequestBuilder(url, HttpMethod.POST) + ContentResponse response = sendRequestBuilder("login", HttpMethod.POST) .content(new StringContentProvider(urlParameters), "application/x-www-form-urlencoded; charset=UTF-8") .send(); @@ -235,7 +233,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { } else if (data.isSuccess()) { logger.debug("SomfyTahoma version: {}", data.getVersion()); String id = registerEvents(); - if (id != null && !id.equals(UNAUTHORIZED)) { + if (id != null && !UNAUTHORIZED.equals(id)) { eventsId = id; logger.debug("Events id: {}", eventsId); updateStatus(ThingStatus.ONLINE); @@ -254,7 +252,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)"); } catch (ExecutionException e) { if (isAuthenticationChallenge(e)) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Authentication challenge"); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Error logging in (check your credentials)"); setTooManyRequests(); } else { logger.debug("Cannot get login cookie", e); @@ -270,14 +269,15 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { } private void setTooManyRequests() { - logger.debug("Too many requests error, suspending activity for {} seconds", SUSPEND_TIME); + logger.debug("Too many requests or bad credentials for the cloud portal, suspending activity for {} seconds", + SUSPEND_TIME); tooManyRequests = true; scheduler.schedule(this::enableLogin, SUSPEND_TIME, TimeUnit.SECONDS); } private @Nullable String registerEvents() { - SomfyTahomaRegisterEventsResponse response = invokeCallToURL(TAHOMA_EVENTS_URL + "register", "", - HttpMethod.POST, SomfyTahomaRegisterEventsResponse.class); + SomfyTahomaRegisterEventsResponse response = invokeCallToURL(EVENTS_URL + "register", "", HttpMethod.POST, + SomfyTahomaRegisterEventsResponse.class); return response != null ? response.getId() : null; } @@ -294,8 +294,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { } private List getEvents() { - SomfyTahomaEvent[] response = invokeCallToURL(TAHOMA_API_URL + "events/" + eventsId + "/fetch", "", - HttpMethod.POST, SomfyTahomaEvent[].class); + SomfyTahomaEvent[] response = invokeCallToURL(EVENTS_URL + eventsId + "/fetch", "", HttpMethod.POST, + SomfyTahomaEvent[].class); return response != null ? List.of(response) : List.of(); } @@ -357,13 +357,13 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { } public List listActionGroups() { - SomfyTahomaActionGroup[] list = invokeCallToURL(TAHOMA_API_URL + "actionGroups", "", HttpMethod.GET, + SomfyTahomaActionGroup[] list = invokeCallToURL("actionGroups", "", HttpMethod.GET, SomfyTahomaActionGroup[].class); return list != null ? List.of(list) : List.of(); } public @Nullable SomfyTahomaSetup getSetup() { - SomfyTahomaSetup setup = invokeCallToURL(TAHOMA_API_URL + "setup", "", HttpMethod.GET, SomfyTahomaSetup.class); + SomfyTahomaSetup setup = invokeCallToURL("setup", "", HttpMethod.GET, SomfyTahomaSetup.class); if (setup != null) { saveDevicePlaces(setup.getDevices()); } @@ -591,7 +591,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { private void logout() { try { eventsId = ""; - sendGetToTahomaWithCookie(TAHOMA_API_URL + "logout"); + sendGetToTahomaWithCookie("logout"); } catch (ExecutionException | TimeoutException e) { logger.debug("Cannot send logout command!", e); } catch (InterruptedException e) { @@ -626,7 +626,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { private String sendMethodToTahomaWithCookie(String url, HttpMethod method, String urlParameters) throws InterruptedException, ExecutionException, TimeoutException { - logger.trace("Sending {} to url: {} with data: {}", method.asString(), url, urlParameters); + logger.trace("Sending {} to url: {} with data: {}", method.asString(), getApiFullUrl(url), urlParameters); Request request = sendRequestBuilder(url, method); if (!urlParameters.isEmpty()) { request = request.content(new StringContentProvider(urlParameters), "application/json;charset=UTF-8"); @@ -644,10 +644,15 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { return response.getContentAsString(); } - private Request sendRequestBuilder(String url, HttpMethod method) { - return httpClient.newRequest(url).method(method).header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en") - .header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate").header("X-Requested-With", "XMLHttpRequest") - .timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS).agent(TAHOMA_AGENT); + private Request sendRequestBuilder(String subUrl, HttpMethod method) { + return httpClient.newRequest(getApiFullUrl(subUrl)).method(method) + .header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en").header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate") + .header("X-Requested-With", "XMLHttpRequest").timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS) + .agent(TAHOMA_AGENT); + } + + private String getApiFullUrl(String subUrl) { + return "https://" + thingConfig.getCloudPortal() + API_BASE_URL + subUrl; } public void sendCommand(String io, String command, String params, String url) { @@ -672,7 +677,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { } private boolean sendCommandInternal(String io, String command, String params, String url) { - String value = params.equals("[]") ? command : command + " " + params.replace("\"", ""); + String value = "[]".equals(params) ? command : command + " " + params.replace("\"", ""); String urlParameters = "{\"label\":\"" + getThingLabelByURL(io) + " - " + value + " - openHAB\",\"actions\":[{\"deviceURL\":\"" + io + "\",\"commands\":[{\"name\":\"" + command + "\",\"parameters\":" + params + "}]}]}"; @@ -799,11 +804,10 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { @Override public void handleConfigurationUpdate(Map configurationParameters) { super.handleConfigurationUpdate(configurationParameters); - if (configurationParameters.containsKey("email")) { - thingConfig.setEmail(configurationParameters.get("email").toString()); - } - if (configurationParameters.containsKey("password")) { - thingConfig.setPassword(configurationParameters.get("password").toString()); + if (configurationParameters.containsKey("email") || configurationParameters.containsKey("password") + || configurationParameters.containsKey("portalUrl")) { + reLoginNeeded = true; + tooManyRequests = false; } } @@ -841,11 +845,11 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { if (isAuthenticationChallenge(e)) { reLogin(); } else { - logger.debug("Cannot call url: {} with params: {}!", url, urlParameters, e); + logger.debug("Cannot call url: {} with params: {}!", getApiFullUrl(url), urlParameters, e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } } catch (TimeoutException e) { - logger.debug("Timeout when calling url: {} with params: {}!", url, urlParameters, e); + logger.debug("Timeout when calling url: {} with params: {}!", getApiFullUrl(url), urlParameters, e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } catch (InterruptedException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/binding/binding.xml index 44eba9c87..5c0a631b0 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/binding/binding.xml @@ -4,6 +4,7 @@ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd"> SomfyTahoma Binding - This is the binding for SomfyTahoma. + This is the binding for Somfy Tahoma and Somfy Connexoon home automation systems and for any other system + based on the OverKiz API. diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/config/config.xml index 850f0de26..5bffde7cc 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/config/config.xml @@ -19,26 +19,42 @@ + + + Cloud portal to connect to + + + + + + + + + + www.tahomalink.com + false + + - Email address for TahomaLink portal + Email address for the portal password - Password for TahomaLink portal + Password for the portal - Specifies the refresh time in seconds for polling events from Tahoma cloud + Specifies the refresh time in seconds for polling events from the cloud 30 - Specifies the timeout in seconds after which the status is got from Tahoma cloud + Specifies the timeout in seconds after which the status is got from the cloud 300 diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/bridge.xml index 68b248812..052c411b3 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.somfytahoma/src/main/resources/OH-INF/thing/bridge.xml @@ -6,8 +6,8 @@ - - Somfy Tahoma bridge enabling communication with Somfy devices + + Bridge enabling communication with devices through a cloud portal