[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>
This commit is contained in:
lolodomo
2021-05-09 20:16:14 +02:00
committed by GitHub
parent 22eebc797a
commit 892221ccad
7 changed files with 80 additions and 44 deletions

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>