[verisure] Adapted to new authentication process and support for non MFA activated user. (#11228) (#11265)

* [verisure] Adapted to new authentication process and support for non MFA activated user. (#11228)

Signed-off-by: Jan Gustafsson <jannegpriv@gmail.com>

* Updated after code review.

Signed-off-by: Jan Gustafsson <jannegpriv@gmail.com>

* Updated after code review.

Signed-off-by: Jan Gustafsson <jannegpriv@gmail.com>

* Updated after code review.

Signed-off-by: Jan Gustafsson <jannegpriv@gmail.com>
This commit is contained in:
Jan Gustafsson 2021-10-16 11:28:08 +02:00 committed by GitHub
parent ef07105b8c
commit 18d26aa821
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 89 deletions

View File

@ -1,12 +1,9 @@
# Verisure Binding # Verisure Binding
This is an openHAB binding for Verisure Alarm System, by Securitas Direct. This is an openHAB binding for Verisure Smart Alarms by Verisure Securitas.
This binding uses the rest API behind the Verisure My Pages: This binding uses a rest API used by the [Verisure My Pages webpage](https://mypages.verisure.com/login.html)
https://mypages.verisure.com/login.html.
Be aware that Verisure don't approve if you update to often, I have gotten no complaints running with a 10 minutes update interval, but officially you should use 30 minutes.
## Supported Things ## Supported Things
@ -19,7 +16,7 @@ This binding supports the following thing types:
- Water Detector (climate) - Water Detector (climate)
- Siren (climate) - Siren (climate)
- Night Control - Night Control
- Yaleman SmartLock - Yaleman Doorman SmartLock
- SmartPlug - SmartPlug
- Door/Window Status - Door/Window Status
- User Presence Status - User Presence Status
@ -31,11 +28,14 @@ This binding supports the following thing types:
## Binding Configuration ## Binding Configuration
You will have to configure the bridge with username and password, these must be the same credentials as used when logging into https://mypages.verisure.com. You will have to configure the bridge with username and password of a pre-defined user on [Verisure page](https://mypages.verisure.com) that has not activated Multi Factor Authentication (MFA/2FA).
Verisure allows you to have more than one user so the suggestion is to use a specific user for automation that has MFA/2FA deactivated.
**NOTE:** To be able to have full control over all SmartLock/alarm functionality, the user also needs to have Administrator rights.
You must also configure pin-code(s) to be able to lock/unlock the SmartLock(s) and arm/unarm the Alarm(s).
You must also configure your pin-code(s) to be able to lock/unlock the SmartLock(s) and arm/unarm the Alarm(s).
**NOTE:** To be able to have full control over all SmartLock functionality, the user has to have Administrator rights.
## Discovery ## Discovery
@ -325,7 +325,8 @@ The following channels are supported:
#### Configuration Options #### Configuration Options
* `deviceId` - Device Id * `deviceId` - Device Id
* Since Event Log lacks a Verisure ID, the following naming convention is used for Event Log on site id 123456789: 'el123456789'. Installation ID can be found using DEBUG log settings. * Since Event Log lacks a Verisure ID, the following naming convention is used for Event Log on site id 123456789: 'el123456789'. Installation ID can be found using DEBUG log settings.
#### Channels #### Channels

View File

@ -131,22 +131,23 @@ public class VerisureBindingConstants {
// REST URI constants // REST URI constants
public static final String USERNAME = "username"; public static final String USERNAME = "username";
public static final String PASSWORD = "password"; public static final String PASSWORD = "password";
public static final String BASEURL = "https://mypages.verisure.com"; public static final String BASE_URL = "https://mypages.verisure.com";
public static final String LOGON_SUF = BASEURL + "/j_spring_security_check?locale=en_GB"; public static final String LOGON_SUF = BASE_URL + "/j_spring_security_check?locale=en_GB";
public static final String ALARM_COMMAND = BASEURL + "/remotecontrol/armstatechange.cmd"; public static final String ALARM_COMMAND = BASE_URL + "/remotecontrol/armstatechange.cmd";
public static final String SMARTLOCK_LOCK_COMMAND = BASEURL + "/remotecontrol/lockunlock.cmd"; public static final String SMARTLOCK_LOCK_COMMAND = BASE_URL + "/remotecontrol/lockunlock.cmd";
public static final String SMARTLOCK_SET_COMMAND = BASEURL + "/overview/setdoorlock.cmd"; public static final String SMARTLOCK_SET_COMMAND = BASE_URL + "/overview/setdoorlock.cmd";
public static final String SMARTLOCK_AUTORELOCK_COMMAND = BASEURL + "/settings/setautorelock.cmd"; public static final String SMARTLOCK_AUTORELOCK_COMMAND = BASE_URL + "/settings/setautorelock.cmd";
public static final String SMARTLOCK_VOLUME_COMMAND = BASEURL + "/settings/setvolume.cmd"; public static final String SMARTLOCK_VOLUME_COMMAND = BASE_URL + "/settings/setvolume.cmd";
public static final String SMARTPLUG_COMMAND = BASEURL + "/settings/smartplug/onoffplug.cmd"; public static final String SMARTPLUG_COMMAND = BASE_URL + "/settings/smartplug/onoffplug.cmd";
public static final String START_REDIRECT = "/uk/start.html"; public static final String START_REDIRECT = "/uk/start.html";
public static final String START_SUF = BASEURL + START_REDIRECT; public static final String START_SUF = BASE_URL + START_REDIRECT;
// GraphQL constants // GraphQL constants
public static final String STATUS = BASEURL + "/uk/status"; public static final String STATUS = BASE_URL + "/uk/status";
public static final String SETTINGS = BASEURL + "/uk/settings.html?giid="; public static final String EXTEND = BASE_URL + "/session/extend";
public static final String SET_INSTALLATION = BASEURL + "/setinstallation?giid="; public static final String SETTINGS = BASE_URL + "/uk/settings.html?giid=";
public static final String SET_INSTALLATION = BASE_URL + "/setinstallation?giid=";
public static final String BASEURL_API = "https://m-api02.verisure.com"; public static final String BASEURL_API = "https://m-api02.verisure.com";
public static final String START_GRAPHQL = "/graphql"; public static final String START_GRAPHQL = "/graphql";
public static final String AUTH_TOKEN = "/auth/token"; public static final String AUTH_TOKEN = "/auth/token";

View File

@ -23,8 +23,8 @@ import org.eclipse.jdt.annotation.Nullable;
*/ */
@NonNullByDefault @NonNullByDefault
public class VerisureBridgeConfiguration { public class VerisureBridgeConfiguration {
public @Nullable String username; public String username = "";
public @Nullable String password; public String password = "";
public int refresh; public int refresh = 600;
public @Nullable String pin; public @Nullable String pin;
} }

View File

@ -17,9 +17,12 @@ import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.CookieStore; import java.net.CookieStore;
import java.net.HttpCookie; import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -86,24 +89,30 @@ public class VerisureSession {
private int apiServerInUseIndex = 0; private int apiServerInUseIndex = 0;
private int numberOfEvents = 15; private int numberOfEvents = 15;
private static final String USER_NAME = "username"; private static final String USER_NAME = "username";
private static final String PASSWORD_NAME = "vid"; private static final String VID = "vid";
private static final String VS_STEPUP = "vs-stepup";
private static final String VS_ACCESS = "vs-access";
private String apiServerInUse = APISERVERLIST.get(apiServerInUseIndex); private String apiServerInUse = APISERVERLIST.get(apiServerInUseIndex);
private String authstring = ""; private String authstring = "";
private @Nullable String csrf; private @Nullable String csrf;
private @Nullable String pinCode; private @Nullable String pinCode;
private HttpClient httpClient; private HttpClient httpClient;
private @Nullable String userName = ""; private String userName = "";
private @Nullable String password = ""; private String password = "";
private String vid = "";
private String vsAccess = "";
private String vsStepup = "";
public VerisureSession(HttpClient httpClient) { public VerisureSession(HttpClient httpClient) {
this.httpClient = httpClient; this.httpClient = httpClient;
} }
public boolean initialize(@Nullable String authstring, @Nullable String pinCode, @Nullable String userName) { public boolean initialize(@Nullable String authstring, @Nullable String pinCode, String userName, String password) {
if (authstring != null) { if (authstring != null) {
this.authstring = authstring.substring(0); this.authstring = authstring.substring(0);
this.pinCode = pinCode; this.pinCode = pinCode;
this.userName = userName; this.userName = userName;
this.password = password;
// Try to login to Verisure // Try to login to Verisure
if (logIn()) { if (logIn()) {
return getInstallations(); return getInstallations();
@ -119,12 +128,9 @@ public class VerisureSession {
if (logIn()) { if (logIn()) {
if (updateStatus()) { if (updateStatus()) {
return true; return true;
} else {
return false;
} }
} else {
return false;
} }
return false;
} catch (HttpResponseException e) { } catch (HttpResponseException e) {
logger.warn("Failed to do a refresh {}", e.getMessage()); logger.warn("Failed to do a refresh {}", e.getMessage());
return false; return false;
@ -258,15 +264,21 @@ public class VerisureSession {
} }
} }
private void setPasswordFromCookie() { private void analyzeCookies() {
CookieStore c = httpClient.getCookieStore(); CookieStore c = httpClient.getCookieStore();
List<HttpCookie> cookies = c.getCookies(); List<HttpCookie> cookies = c.getCookies();
final List<HttpCookie> unmodifiableList = List.of(cookies.toArray(new HttpCookie[] {})); final List<HttpCookie> unmodifiableList = List.of(cookies.toArray(new HttpCookie[] {}));
unmodifiableList.forEach(cookie -> { unmodifiableList.forEach(cookie -> {
logger.trace("Response Cookie: {}", cookie); logger.trace("Response Cookie: {}", cookie);
if (cookie.getName().equals(PASSWORD_NAME)) { if (VID.equals(cookie.getName())) {
password = cookie.getValue(); vid = cookie.getValue();
logger.debug("Fetching vid {} from cookie", password); logger.debug("Fetching vid {} from cookie", vid);
} else if (VS_ACCESS.equals(cookie.getName())) {
vsAccess = cookie.getValue();
logger.debug("Fetching vs-access {} from cookie", vsAccess);
} else if (VS_STEPUP.equals(cookie.getName())) {
vsStepup = cookie.getValue();
logger.debug("Fetching vs-stepup {} from cookie", vsStepup);
} }
}); });
} }
@ -290,7 +302,6 @@ public class VerisureSession {
switch (response.getStatus()) { switch (response.getStatus()) {
case HttpStatus.OK_200: case HttpStatus.OK_200:
if (content.contains("<link href=\"/newapp")) { if (content.contains("<link href=\"/newapp")) {
setPasswordFromCookie();
return true; return true;
} else { } else {
logger.debug("We need to login again!"); logger.debug("We need to login again!");
@ -313,9 +324,9 @@ public class VerisureSession {
private <T> @Nullable T getJSONVerisureAPI(String url, Class<T> jsonClass) private <T> @Nullable T getJSONVerisureAPI(String url, Class<T> jsonClass)
throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException { throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
logger.debug("HTTP GET: {}", BASEURL + url); logger.debug("HTTP GET: {}", BASE_URL + url);
ContentResponse response = httpClient.GET(BASEURL + url + "?_=" + System.currentTimeMillis()); ContentResponse response = httpClient.GET(BASE_URL + url + "?_=" + System.currentTimeMillis());
String content = response.getContentAsString(); String content = response.getContentAsString();
logTraceWithPattern(response.getStatus(), content); logTraceWithPattern(response.getStatus(), content);
@ -325,6 +336,7 @@ public class VerisureSession {
private ContentResponse postVerisureAPI(String url, String data, boolean isJSON) private ContentResponse postVerisureAPI(String url, String data, boolean isJSON)
throws ExecutionException, InterruptedException, TimeoutException { throws ExecutionException, InterruptedException, TimeoutException {
logger.debug("postVerisureAPI URL: {} Data:{}", url, data); logger.debug("postVerisureAPI URL: {} Data:{}", url, data);
Request request = httpClient.newRequest(url).method(HttpMethod.POST); Request request = httpClient.newRequest(url).method(HttpMethod.POST);
if (isJSON) { if (isJSON) {
request.header("content-type", "application/json"); request.header("content-type", "application/json");
@ -334,14 +346,29 @@ public class VerisureSession {
} }
} }
request.header("Accept", "application/json"); request.header("Accept", "application/json");
if (!data.equals("empty")) {
if (url.contains(AUTH_LOGIN)) {
request.header("APPLICATION_ID", "OpenHAB Verisure");
String basicAuhentication = Base64.getEncoder().encodeToString((userName + ":" + password).getBytes());
request.header("authorization", "Basic " + basicAuhentication);
} else {
if (!vid.isEmpty()) {
request.cookie(new HttpCookie(VID, vid));
logger.debug("Setting cookie with vid {}", vid);
}
if (!vsAccess.isEmpty()) {
request.cookie(new HttpCookie(VS_ACCESS, vsAccess));
logger.debug("Setting cookie with vs-access {}", vsAccess);
}
logger.debug("Setting cookie with username {}", userName);
request.cookie(new HttpCookie(USER_NAME, userName));
}
if (!"empty".equals(data)) {
request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)), request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)),
"application/x-www-form-urlencoded; charset=UTF-8"); "application/x-www-form-urlencoded; charset=UTF-8");
} else {
logger.debug("Setting cookie with username {} and vid {}", userName, password);
request.cookie(new HttpCookie(USER_NAME, userName));
request.cookie(new HttpCookie(PASSWORD_NAME, password));
} }
logger.debug("HTTP POST Request {}.", request.toString()); logger.debug("HTTP POST Request {}.", request.toString());
return request.send(); return request.send();
} }
@ -400,6 +427,9 @@ public class VerisureSession {
logTraceWithPattern(httpStatus, content); logTraceWithPattern(httpStatus, content);
return httpStatus; return httpStatus;
} }
} else if (httpStatus == HttpStatus.BAD_REQUEST_400) {
setApiServerInUse(getNextApiServer());
url = apiServerInUse + urlString;
} else { } else {
logger.debug("Failed to send POST, Http status code: {}", response.getStatus()); logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
} }
@ -417,7 +447,11 @@ public class VerisureSession {
logTraceWithPattern(response.getStatus(), response.getContentAsString()); logTraceWithPattern(response.getStatus(), response.getContentAsString());
url = AUTH_LOGIN; url = AUTH_LOGIN;
return postVerisureAPI(url, "empty"); int httpStatusCode = postVerisureAPI(url, "empty");
analyzeCookies();
// return response.getStatus();
return httpStatusCode;
} }
private boolean getInstallations() { private boolean getInstallations() {
@ -488,10 +522,26 @@ public class VerisureSession {
private synchronized boolean logIn() { private synchronized boolean logIn() {
try { try {
if (!areWeLoggedIn()) { if (!areWeLoggedIn()) {
logger.debug("Attempting to log in to mypages.verisure.com"); vid = "";
String url = LOGON_SUF; vsAccess = "";
logger.debug("Attempting to log in to {}, remove all cookies to ensure a fresh session", BASE_URL);
URI authUri = new URI(BASE_URL);
CookieStore store = httpClient.getCookieStore();
store.get(authUri).forEach(cookie -> {
store.remove(authUri, cookie);
});
String url = AUTH_LOGIN;
int httpStatusCode = postVerisureAPI(url, "empty");
analyzeCookies();
if (!vsStepup.isEmpty()) {
logger.warn("MFA is activated on this user! Not supported by binding!");
return false;
}
url = LOGON_SUF;
logger.debug("Login URL: {}", url); logger.debug("Login URL: {}", url);
int httpStatusCode = postVerisureAPI(url, authstring); httpStatusCode = postVerisureAPI(url, authstring);
if (httpStatusCode != HttpStatus.OK_200) { if (httpStatusCode != HttpStatus.OK_200) {
logger.debug("Failed to login, HTTP status code: {}", httpStatusCode); logger.debug("Failed to login, HTTP status code: {}", httpStatusCode);
return false; return false;
@ -500,7 +550,7 @@ public class VerisureSession {
} else { } else {
return true; return true;
} }
} catch (ExecutionException | InterruptedException | TimeoutException e) { } catch (ExecutionException | InterruptedException | TimeoutException | URISyntaxException e) {
logger.warn("Failed to login {}", e.getMessage()); logger.warn("Failed to login {}", e.getMessage());
} }
return false; return false;
@ -617,16 +667,17 @@ public class VerisureSession {
// Set location // Set location
slThing.setLocation(doorLock.getDevice().getArea()); slThing.setLocation(doorLock.getDevice().getArea());
slThing.setDeviceId(deviceId); slThing.setDeviceId(deviceId);
// Fetch more info from old endpoint // Fetch more info from old endpoint
try { try {
VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(), VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(),
VerisureSmartLockDTO.class); VerisureSmartLockDTO.class);
logger.debug("REST Response ({})", smartLockThing); logger.debug("REST Response ({})", smartLockThing);
slThing.setSmartLockJSON(smartLockThing); slThing.setSmartLockJSON(smartLockThing);
notifyListenersIfChanged(slThing, installation, deviceId);
} catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) { } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
logger.warn("Failed to query for smartlock status: {}", e.getMessage()); logger.warn("Failed to query for smartlock status: {}", e.getMessage());
} }
notifyListenersIfChanged(slThing, installation, deviceId);
} }
}); });
@ -740,7 +791,7 @@ public class VerisureSession {
cThing.setBatteryStatus(batteryStatus); cThing.setBatteryStatus(batteryStatus);
} }
} catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) { } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
logger.warn("Failed to query for smartlock status: {}", e.getMessage()); logger.debug("Failed to query for battery status: {}", e.getMessage());
} }
// Set location // Set location
cThing.setLocation(climate.getDevice().getArea()); cThing.setLocation(climate.getDevice().getArea());
@ -789,7 +840,7 @@ public class VerisureSession {
dThing.setBatteryStatus(batteryStatus); dThing.setBatteryStatus(batteryStatus);
} }
} catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) { } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
logger.warn("Failed to query for smartlock status: {}", e.getMessage()); logger.warn("Failed to query for door&window status: {}", e.getMessage());
} }
// Set location // Set location
dThing.setLocation(doorWindow.getDevice().getArea()); dThing.setLocation(doorWindow.getDevice().getArea());
@ -847,7 +898,7 @@ public class VerisureSession {
.getUserTrackings(); .getUserTrackings();
userTrackingList.forEach(userTracking -> { userTrackingList.forEach(userTracking -> {
String localUserTrackingStatus = userTracking.getStatus(); String localUserTrackingStatus = userTracking.getStatus();
if (localUserTrackingStatus != null && localUserTrackingStatus.equals("ACTIVE")) { if ("ACTIVE".equals(localUserTrackingStatus)) {
VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO(); VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO();
VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation(); VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation();
inst.setUserTrackings(Collections.singletonList(userTracking)); inst.setUserTrackings(Collections.singletonList(userTracking));

View File

@ -75,7 +75,8 @@ public class VerisureThingDiscoveryService extends AbstractDiscoveryService
String deviceId = thing.getDeviceId(); String deviceId = thing.getDeviceId();
if (thingUID != null) { if (thingUID != null) {
if (verisureBridgeHandler != null) { if (verisureBridgeHandler != null) {
String label = "Device Id: " + deviceId; String className = thing.getClass().getSimpleName();
String label = "Type: " + className + " Device Id: " + deviceId;
if (thing.getLocation() != null) { if (thing.getLocation() != null) {
label += ", Location: " + thing.getLocation(); label += ", Location: " + thing.getLocation();
} }
@ -84,7 +85,7 @@ public class VerisureThingDiscoveryService extends AbstractDiscoveryService
} }
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID) DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel(label).withProperty(VerisureThingConfiguration.DEVICE_ID_LABEL, deviceId) .withLabel(label).withProperty(VerisureThingConfiguration.DEVICE_ID_LABEL, deviceId)
.withRepresentationProperty(deviceId).build(); .withRepresentationProperty(VerisureThingConfiguration.DEVICE_ID_LABEL).build();
logger.debug("thinguid: {}, bridge {}, label {}", thingUID, bridgeUID, deviceId); logger.debug("thinguid: {}, bridge {}, label {}", thingUID, bridgeUID, deviceId);
thingDiscovered(discoveryResult); thingDiscovered(discoveryResult);
} }

View File

@ -14,9 +14,6 @@ package org.openhab.binding.verisure.internal.handler;
import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*; import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
@ -65,7 +62,6 @@ public class VerisureBridgeHandler extends BaseBridgeHandler {
private String authstring = ""; private String authstring = "";
private @Nullable String pinCode; private @Nullable String pinCode;
private static int REFRESH_SEC = 600;
private @Nullable ScheduledFuture<?> refreshJob; private @Nullable ScheduledFuture<?> refreshJob;
private @Nullable ScheduledFuture<?> immediateRefreshJob; private @Nullable ScheduledFuture<?> immediateRefreshJob;
private @Nullable VerisureSession session; private @Nullable VerisureSession session;
@ -104,20 +100,19 @@ public class VerisureBridgeHandler extends BaseBridgeHandler {
public void initialize() { public void initialize() {
logger.debug("Initializing Verisure Binding"); logger.debug("Initializing Verisure Binding");
VerisureBridgeConfiguration config = getConfigAs(VerisureBridgeConfiguration.class); VerisureBridgeConfiguration config = getConfigAs(VerisureBridgeConfiguration.class);
REFRESH_SEC = config.refresh;
this.pinCode = config.pin; this.pinCode = config.pin;
if (config.username == null || config.password == null) { if (config.username.isBlank() || config.password.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Configuration of username and password is mandatory"); "Configuration of username and password is mandatory");
} else if (REFRESH_SEC < 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh time cannot negative!"); } else if (config.refresh < 10) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Refresh time is lower than min value of 10!");
} else { } else {
try { try {
authstring = "j_username=" + config.username + "&j_password=" authstring = "j_username=" + config.username;
+ URLEncoder.encode(config.password, StandardCharsets.UTF_8.toString())
+ "&spring-security-redirect=" + START_REDIRECT;
scheduler.execute(() -> { scheduler.execute(() -> {
if (session == null) { if (session == null) {
logger.debug("Session is null, let's create a new one"); logger.debug("Session is null, let's create a new one");
session = new VerisureSession(this.httpClient); session = new VerisureSession(this.httpClient);
@ -125,18 +120,15 @@ public class VerisureBridgeHandler extends BaseBridgeHandler {
VerisureSession session = this.session; VerisureSession session = this.session;
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
if (session != null) { if (session != null) {
if (!session.initialize(authstring, pinCode, config.username)) { if (!session.initialize(authstring, pinCode, config.username, config.password)) {
logger.warn("Failed to initialize bridge, please check your credentials!");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_REGISTERING_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_REGISTERING_ERROR,
"Failed to login to Verisure, please check your credentials!"); "Failed to login to Verisure, please check your account settings! Is MFA activated?");
dispose();
initialize();
return; return;
} }
startAutomaticRefresh(); startAutomaticRefresh(config.refresh);
} }
}); });
} catch (RuntimeException | UnsupportedEncodingException e) { } catch (RuntimeException e) {
logger.warn("Failed to initialize: {}", e.getMessage()); logger.warn("Failed to initialize: {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} }
@ -227,12 +219,12 @@ public class VerisureBridgeHandler extends BaseBridgeHandler {
} }
} }
private void startAutomaticRefresh() { private void startAutomaticRefresh(int refresh) {
ScheduledFuture<?> refreshJob = this.refreshJob; ScheduledFuture<?> refreshJob = this.refreshJob;
logger.debug("Start automatic refresh {}", refreshJob); logger.debug("Start automatic refresh {}", refreshJob);
if (refreshJob == null || refreshJob.isCancelled()) { if (refreshJob == null || refreshJob.isCancelled()) {
try { try {
this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, REFRESH_SEC, this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refresh,
TimeUnit.SECONDS); TimeUnit.SECONDS);
logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob); logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
} catch (RejectedExecutionException e) { } catch (RejectedExecutionException e) {

View File

@ -15,6 +15,7 @@ package org.openhab.binding.verisure.internal.handler;
import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*; import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.measure.quantity.Dimensionless; import javax.measure.quantity.Dimensionless;
@ -23,6 +24,7 @@ import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.verisure.internal.dto.VerisureBatteryStatusDTO; import org.openhab.binding.verisure.internal.dto.VerisureBatteryStatusDTO;
import org.openhab.binding.verisure.internal.dto.VerisureClimatesDTO; import org.openhab.binding.verisure.internal.dto.VerisureClimatesDTO;
import org.openhab.binding.verisure.internal.dto.VerisureClimatesDTO.Climate;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
@ -74,27 +76,35 @@ public class VerisureClimateDeviceThingHandler extends VerisureThingHandler<Veri
State state = getValue(channelUID.getId(), climateJSON); State state = getValue(channelUID.getId(), climateJSON);
updateState(channelUID, state); updateState(channelUID, state);
}); });
String timeStamp = climateJSON.getData().getInstallation().getClimates().get(0).getTemperatureTimestamp(); List<Climate> climateList = climateJSON.getData().getInstallation().getClimates();
if (timeStamp != null) { if (climateList != null && !climateList.isEmpty()) {
updateTimeStamp(timeStamp); String timeStamp = climateList.get(0).getTemperatureTimestamp();
if (timeStamp != null) {
updateTimeStamp(timeStamp);
}
} }
updateInstallationChannels(climateJSON); updateInstallationChannels(climateJSON);
} }
public State getValue(String channelId, VerisureClimatesDTO climateJSON) { public State getValue(String channelId, VerisureClimatesDTO climateJSON) {
List<Climate> climateList = climateJSON.getData().getInstallation().getClimates();
switch (channelId) { switch (channelId) {
case CHANNEL_TEMPERATURE: case CHANNEL_TEMPERATURE:
double temperature = climateJSON.getData().getInstallation().getClimates().get(0).getTemperatureValue(); if (climateList != null && !climateList.isEmpty()) {
return new QuantityType<Temperature>(temperature, SIUnits.CELSIUS); double temperature = climateList.get(0).getTemperatureValue();
return new QuantityType<Temperature>(temperature, SIUnits.CELSIUS);
}
case CHANNEL_HUMIDITY: case CHANNEL_HUMIDITY:
if (climateJSON.getData().getInstallation().getClimates().get(0).isHumidityEnabled()) { if (climateList != null && !climateList.isEmpty() && climateList.get(0).isHumidityEnabled()) {
double humidity = climateJSON.getData().getInstallation().getClimates().get(0).getHumidityValue(); double humidity = climateList.get(0).getHumidityValue();
return new QuantityType<Dimensionless>(humidity, Units.PERCENT); return new QuantityType<Dimensionless>(humidity, Units.PERCENT);
} }
case CHANNEL_HUMIDITY_ENABLED: case CHANNEL_HUMIDITY_ENABLED:
boolean humidityEnabled = climateJSON.getData().getInstallation().getClimates().get(0) if (climateList != null && !climateList.isEmpty()) {
.isHumidityEnabled(); boolean humidityEnabled = climateList.get(0).isHumidityEnabled();
return OnOffType.from(humidityEnabled); return OnOffType.from(humidityEnabled);
}
case CHANNEL_LOCATION: case CHANNEL_LOCATION:
String location = climateJSON.getLocation(); String location = climateJSON.getLocation();
return location != null ? new StringType(location) : UnDefType.NULL; return location != null ? new StringType(location) : UnDefType.NULL;
@ -102,7 +112,7 @@ public class VerisureClimateDeviceThingHandler extends VerisureThingHandler<Veri
VerisureBatteryStatusDTO batteryStatus = climateJSON.getBatteryStatus(); VerisureBatteryStatusDTO batteryStatus = climateJSON.getBatteryStatus();
if (batteryStatus != null) { if (batteryStatus != null) {
String status = batteryStatus.getStatus(); String status = batteryStatus.getStatus();
if (status != null && status.equals("CRITICAL")) { if ("CRITICAL".equals(status)) {
return OnOffType.from(true); return OnOffType.from(true);
} }
} }

View File

@ -88,7 +88,7 @@ public class VerisureDoorWindowThingHandler extends VerisureThingHandler<Verisur
VerisureBatteryStatusDTO batteryStatus = doorWindowJSON.getBatteryStatus(); VerisureBatteryStatusDTO batteryStatus = doorWindowJSON.getBatteryStatus();
if (batteryStatus != null) { if (batteryStatus != null) {
String status = batteryStatus.getStatus(); String status = batteryStatus.getStatus();
if (status != null && status.equals("CRITICAL")) { if ("CRITICAL".equals(status)) {
return OnOffType.from(true); return OnOffType.from(true);
} }
} }