[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:
parent
ef07105b8c
commit
18d26aa821
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue