[somfytahoma] Open to other portals (#10611)

* [somfytahoma] Open to other portals

Signed-off-by: Laurent Garnier <lg.hc@free.fr>

* Review comment: suppress the advanced setting for cookie handling

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
pull/10665/head
lolodomo 2021-05-09 20:16:14 +02:00 committed by GitHub
parent 22eebc797a
commit 892221ccad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 44 deletions

View File

@ -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) |

View File

@ -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");

View File

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

View File

@ -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<SomfyTahomaEvent> 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<SomfyTahomaActionGroup> 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<String, Object> 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);

View File

@ -4,6 +4,7 @@
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>SomfyTahoma Binding</name>
<description>This is the binding for SomfyTahoma.</description>
<description>This is the binding for Somfy Tahoma and Somfy Connexoon home automation systems and for any other system
based on the OverKiz API.</description>
</binding:binding>

View File

@ -19,26 +19,42 @@
</config-description>
<config-description uri="bridge-type:somfytahoma:bridge">
<parameter name="cloudPortal" type="text" required="false">
<label>Cloud Portal</label>
<description>Cloud portal to connect to</description>
<options>
<option value="www.tahomalink.com">Somfy TaHoma / Somfy Connexoon IO / Somfy (Europe)</option>
<option value="ha201-1.overkiz.com">Somfy Connexoon RTS / Somfy (Australia)</option>
<option value="ha401-1.overkiz.com">Somfy (North America)</option>
<option value="ha110-1.overkiz.com">Cozytouch</option>
<option value="ha101-1.overkiz.com">eedomus</option>
<option value="ha117-1.overkiz.com">Hi Kumo</option>
<option value="ha112-1.overkiz.com">Rexel Energeasy Connect</option>
</options>
<default>www.tahomalink.com</default>
<limitToOptions>false</limitToOptions>
</parameter>
<parameter name="email" type="text" required="true">
<label>Email Address</label>
<description>Email address for TahomaLink portal</description>
<description>Email address for the portal</description>
</parameter>
<parameter name="password" type="text" required="true">
<context>password</context>
<label>Password</label>
<description>Password for TahomaLink portal</description>
<description>Password for the portal</description>
</parameter>
<parameter name="refresh" type="integer" required="false" min="10">
<label>Refresh</label>
<description>Specifies the refresh time in seconds for polling events from Tahoma cloud</description>
<description>Specifies the refresh time in seconds for polling events from the cloud</description>
<default>30</default>
</parameter>
<parameter name="statusTimeout" type="integer" required="false" min="60">
<label>Status Timeout</label>
<description>Specifies the timeout in seconds after which the status is got from Tahoma cloud</description>
<description>Specifies the timeout in seconds after which the status is got from the cloud</description>
<default>300</default>
</parameter>

View File

@ -6,8 +6,8 @@
<!-- Bridge -->
<bridge-type id="bridge">
<label>Somfy Tahoma Bridge</label>
<description>Somfy Tahoma bridge enabling communication with Somfy devices</description>
<label>Bridge</label>
<description>Bridge enabling communication with devices through a cloud portal</description>
<config-description-ref uri="bridge-type:somfytahoma:bridge"/>
</bridge-type>