diff --git a/bundles/org.openhab.binding.verisure/README.md b/bundles/org.openhab.binding.verisure/README.md index 9f9e83fea571e..8f5a0d7ed7090 100644 --- a/bundles/org.openhab.binding.verisure/README.md +++ b/bundles/org.openhab.binding.verisure/README.md @@ -1,12 +1,9 @@ # 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 @@ -19,7 +16,7 @@ This binding supports the following thing types: - Water Detector (climate) - Siren (climate) - Night Control -- Yaleman SmartLock +- Yaleman Doorman SmartLock - SmartPlug - Door/Window Status - User Presence Status @@ -31,11 +28,14 @@ This binding supports the following thing types: ## 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 @@ -325,7 +325,8 @@ The following channels are supported: #### Configuration Options * `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 diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBindingConstants.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBindingConstants.java index c7722d722c89b..97d0da191345a 100644 --- a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBindingConstants.java +++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBindingConstants.java @@ -131,22 +131,23 @@ public class VerisureBindingConstants { // REST URI constants public static final String USERNAME = "username"; public static final String PASSWORD = "password"; - public static final String BASEURL = "https://mypages.verisure.com"; - public static final String LOGON_SUF = BASEURL + "/j_spring_security_check?locale=en_GB"; - public static final String ALARM_COMMAND = BASEURL + "/remotecontrol/armstatechange.cmd"; - public static final String SMARTLOCK_LOCK_COMMAND = BASEURL + "/remotecontrol/lockunlock.cmd"; - public static final String SMARTLOCK_SET_COMMAND = BASEURL + "/overview/setdoorlock.cmd"; - public static final String SMARTLOCK_AUTORELOCK_COMMAND = BASEURL + "/settings/setautorelock.cmd"; - public static final String SMARTLOCK_VOLUME_COMMAND = BASEURL + "/settings/setvolume.cmd"; + public static final String BASE_URL = "https://mypages.verisure.com"; + public static final String LOGON_SUF = BASE_URL + "/j_spring_security_check?locale=en_GB"; + public static final String ALARM_COMMAND = BASE_URL + "/remotecontrol/armstatechange.cmd"; + public static final String SMARTLOCK_LOCK_COMMAND = BASE_URL + "/remotecontrol/lockunlock.cmd"; + public static final String SMARTLOCK_SET_COMMAND = BASE_URL + "/overview/setdoorlock.cmd"; + public static final String SMARTLOCK_AUTORELOCK_COMMAND = BASE_URL + "/settings/setautorelock.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_SUF = BASEURL + START_REDIRECT; + public static final String START_SUF = BASE_URL + START_REDIRECT; // GraphQL constants - public static final String STATUS = BASEURL + "/uk/status"; - public static final String SETTINGS = BASEURL + "/uk/settings.html?giid="; - public static final String SET_INSTALLATION = BASEURL + "/setinstallation?giid="; + public static final String STATUS = BASE_URL + "/uk/status"; + public static final String EXTEND = BASE_URL + "/session/extend"; + 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 START_GRAPHQL = "/graphql"; public static final String AUTH_TOKEN = "/auth/token"; diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureSession.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureSession.java index bc87dcc232389..a91c139c0ea20 100644 --- a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureSession.java +++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureSession.java @@ -17,9 +17,12 @@ import java.math.BigDecimal; import java.net.CookieStore; import java.net.HttpCookie; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -86,7 +89,9 @@ public class VerisureSession { private int apiServerInUseIndex = 0; private int numberOfEvents = 15; 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 authstring = ""; private @Nullable String csrf; @@ -94,16 +99,21 @@ public class VerisureSession { private HttpClient httpClient; private @Nullable String userName = ""; private @Nullable String password = ""; + private @Nullable String vid = ""; + private @Nullable String vsAccess = ""; + private @Nullable String vsStepup = ""; public VerisureSession(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, @Nullable String userName, + @Nullable String passWord) { if (authstring != null) { this.authstring = authstring.substring(0); this.pinCode = pinCode; this.userName = userName; + this.password = passWord; // Try to login to Verisure if (logIn()) { return getInstallations(); @@ -119,12 +129,9 @@ public boolean refresh() { if (logIn()) { if (updateStatus()) { return true; - } else { - return false; } - } else { - return false; } + return false; } catch (HttpResponseException e) { logger.warn("Failed to do a refresh {}", e.getMessage()); return false; @@ -258,15 +265,21 @@ public void configureInstallationInstance(BigDecimal installationId) } } - private void setPasswordFromCookie() { + private void analyzeCookies() { CookieStore c = httpClient.getCookieStore(); List cookies = c.getCookies(); final List unmodifiableList = List.of(cookies.toArray(new HttpCookie[] {})); unmodifiableList.forEach(cookie -> { logger.trace("Response Cookie: {}", cookie); - if (cookie.getName().equals(PASSWORD_NAME)) { - password = cookie.getValue(); - logger.debug("Fetching vid {} from cookie", password); + if (VID.equals(cookie.getName())) { + vid = cookie.getValue(); + 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 +303,6 @@ private boolean areWeLoggedIn() throws ExecutionException, InterruptedException, switch (response.getStatus()) { case HttpStatus.OK_200: if (content.contains(" @Nullable T getJSONVerisureAPI(String url, Class jsonClass) 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(); logTraceWithPattern(response.getStatus(), content); @@ -325,6 +337,7 @@ private boolean areWeLoggedIn() throws ExecutionException, InterruptedException, private ContentResponse postVerisureAPI(String url, String data, boolean isJSON) throws ExecutionException, InterruptedException, TimeoutException { logger.debug("postVerisureAPI URL: {} Data:{}", url, data); + Request request = httpClient.newRequest(url).method(HttpMethod.POST); if (isJSON) { request.header("content-type", "application/json"); @@ -334,14 +347,29 @@ private ContentResponse postVerisureAPI(String url, String data, boolean isJSON) } } request.header("Accept", "application/json"); - if (!data.equals("empty")) { - request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)), - "application/x-www-form-urlencoded; charset=UTF-8"); + + 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 { - logger.debug("Setting cookie with username {} and vid {}", userName, password); + if (vid != null && !vid.isEmpty()) { + request.cookie(new HttpCookie(VID, vid)); + logger.debug("Setting cookie with vid {}", vid); + } + if (vsAccess != null && !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)); - request.cookie(new HttpCookie(PASSWORD_NAME, password)); } + + if (!"empty".equals(data)) { + request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)), + "application/x-www-form-urlencoded; charset=UTF-8"); + } + logger.debug("HTTP POST Request {}.", request.toString()); return request.send(); } @@ -400,6 +428,9 @@ private int postVerisureAPI(String urlString, String data) { logTraceWithPattern(httpStatus, content); return httpStatus; } + } else if (httpStatus == HttpStatus.BAD_REQUEST_400) { + setApiServerInUse(getNextApiServer()); + url = apiServerInUse + urlString; } else { logger.debug("Failed to send POST, Http status code: {}", response.getStatus()); } @@ -417,7 +448,11 @@ private int setSessionCookieAuthLogin() throws ExecutionException, InterruptedEx logTraceWithPattern(response.getStatus(), response.getContentAsString()); url = AUTH_LOGIN; - return postVerisureAPI(url, "empty"); + int httpStatusCode = postVerisureAPI(url, "empty"); + analyzeCookies(); + + // return response.getStatus(); + return httpStatusCode; } private boolean getInstallations() { @@ -488,10 +523,26 @@ private boolean getInstallations() { private synchronized boolean logIn() { try { if (!areWeLoggedIn()) { - logger.debug("Attempting to log in to mypages.verisure.com"); - String url = LOGON_SUF; + vid = ""; + 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 != null && !vsStepup.isEmpty()) { + logger.warn("MFA is activated on this user! Not supported by binding!"); + return false; + } + + url = LOGON_SUF; logger.debug("Login URL: {}", url); - int httpStatusCode = postVerisureAPI(url, authstring); + httpStatusCode = postVerisureAPI(url, authstring); if (httpStatusCode != HttpStatus.OK_200) { logger.debug("Failed to login, HTTP status code: {}", httpStatusCode); return false; @@ -500,7 +551,7 @@ private synchronized boolean logIn() { } else { return true; } - } catch (ExecutionException | InterruptedException | TimeoutException e) { + } catch (ExecutionException | InterruptedException | TimeoutException | URISyntaxException e) { logger.warn("Failed to login {}", e.getMessage()); } return false; @@ -617,16 +668,17 @@ private synchronized void updateSmartLockStatus(VerisureInstallation installatio // Set location slThing.setLocation(doorLock.getDevice().getArea()); slThing.setDeviceId(deviceId); + // Fetch more info from old endpoint try { VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(), VerisureSmartLockDTO.class); logger.debug("REST Response ({})", smartLockThing); slThing.setSmartLockJSON(smartLockThing); - notifyListenersIfChanged(slThing, installation, deviceId); } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) { logger.warn("Failed to query for smartlock status: {}", e.getMessage()); } + notifyListenersIfChanged(slThing, installation, deviceId); } }); @@ -740,7 +792,7 @@ private synchronized void updateClimateStatus(VerisureInstallation installation) cThing.setBatteryStatus(batteryStatus); } } 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 cThing.setLocation(climate.getDevice().getArea()); @@ -789,7 +841,7 @@ private synchronized void updateDoorWindowStatus(VerisureInstallation installati dThing.setBatteryStatus(batteryStatus); } } 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 dThing.setLocation(doorWindow.getDevice().getArea()); @@ -847,7 +899,7 @@ private synchronized void updateUserPresenceStatus(VerisureInstallation installa .getUserTrackings(); userTrackingList.forEach(userTracking -> { String localUserTrackingStatus = userTracking.getStatus(); - if (localUserTrackingStatus != null && localUserTrackingStatus.equals("ACTIVE")) { + if (localUserTrackingStatus != null && "ACTIVE".equals(localUserTrackingStatus)) { VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO(); VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation(); inst.setUserTrackings(Collections.singletonList(userTracking)); diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/discovery/VerisureThingDiscoveryService.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/discovery/VerisureThingDiscoveryService.java index 9651bfd0c55b4..0423f47364dc7 100644 --- a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/discovery/VerisureThingDiscoveryService.java +++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/discovery/VerisureThingDiscoveryService.java @@ -75,7 +75,8 @@ private void onThingAddedInternal(VerisureThingDTO thing) { String deviceId = thing.getDeviceId(); if (thingUID != 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) { label += ", Location: " + thing.getLocation(); } @@ -84,7 +85,7 @@ private void onThingAddedInternal(VerisureThingDTO thing) { } DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID) .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); thingDiscovered(discoveryResult); } diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureBridgeHandler.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureBridgeHandler.java index 5530a131cc78d..f691879f517bc 100644 --- a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureBridgeHandler.java +++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureBridgeHandler.java @@ -105,6 +105,7 @@ public void initialize() { logger.debug("Initializing Verisure Binding"); VerisureBridgeConfiguration config = getConfigAs(VerisureBridgeConfiguration.class); REFRESH_SEC = config.refresh; + this.pinCode = config.pin; if (config.username == null || config.password == null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, @@ -116,6 +117,7 @@ public void initialize() { authstring = "j_username=" + config.username + "&j_password=" + URLEncoder.encode(config.password, StandardCharsets.UTF_8.toString()) + "&spring-security-redirect=" + START_REDIRECT; + authstring = "j_username=" + config.username; scheduler.execute(() -> { if (session == null) { @@ -125,12 +127,9 @@ public void initialize() { VerisureSession session = this.session; updateStatus(ThingStatus.UNKNOWN); if (session != null) { - if (!session.initialize(authstring, pinCode, config.username)) { - logger.warn("Failed to initialize bridge, please check your credentials!"); + if (!session.initialize(authstring, pinCode, config.username, config.password)) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_REGISTERING_ERROR, - "Failed to login to Verisure, please check your credentials!"); - dispose(); - initialize(); + "Failed to login to Verisure, please check your account settings! Is MFA activated?"); return; } startAutomaticRefresh(); diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureClimateDeviceThingHandler.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureClimateDeviceThingHandler.java index 1ac6f65852875..61c668f0e7144 100644 --- a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureClimateDeviceThingHandler.java +++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureClimateDeviceThingHandler.java @@ -15,6 +15,7 @@ import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.measure.quantity.Dimensionless; @@ -23,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.verisure.internal.dto.VerisureBatteryStatusDTO; 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.QuantityType; import org.openhab.core.library.types.StringType; @@ -74,27 +76,35 @@ private void updateClimateDeviceState(VerisureClimatesDTO climateJSON) { State state = getValue(channelUID.getId(), climateJSON); updateState(channelUID, state); }); - String timeStamp = climateJSON.getData().getInstallation().getClimates().get(0).getTemperatureTimestamp(); - if (timeStamp != null) { - updateTimeStamp(timeStamp); + List climateList = climateJSON.getData().getInstallation().getClimates(); + if (climateList != null) { + String timeStamp = climateList.get(0).getTemperatureTimestamp(); + if (timeStamp != null) { + updateTimeStamp(timeStamp); + } } + updateInstallationChannels(climateJSON); } public State getValue(String channelId, VerisureClimatesDTO climateJSON) { + List climateList = climateJSON.getData().getInstallation().getClimates(); switch (channelId) { case CHANNEL_TEMPERATURE: - double temperature = climateJSON.getData().getInstallation().getClimates().get(0).getTemperatureValue(); - return new QuantityType(temperature, SIUnits.CELSIUS); + if (climateList != null) { + double temperature = climateList.get(0).getTemperatureValue(); + return new QuantityType(temperature, SIUnits.CELSIUS); + } case CHANNEL_HUMIDITY: - if (climateJSON.getData().getInstallation().getClimates().get(0).isHumidityEnabled()) { - double humidity = climateJSON.getData().getInstallation().getClimates().get(0).getHumidityValue(); + if (climateList != null && climateList.get(0).isHumidityEnabled()) { + double humidity = climateList.get(0).getHumidityValue(); return new QuantityType(humidity, Units.PERCENT); } case CHANNEL_HUMIDITY_ENABLED: - boolean humidityEnabled = climateJSON.getData().getInstallation().getClimates().get(0) - .isHumidityEnabled(); - return OnOffType.from(humidityEnabled); + if (climateList != null) { + boolean humidityEnabled = climateList.get(0).isHumidityEnabled(); + return OnOffType.from(humidityEnabled); + } case CHANNEL_LOCATION: String location = climateJSON.getLocation(); return location != null ? new StringType(location) : UnDefType.NULL; @@ -102,7 +112,7 @@ public State getValue(String channelId, VerisureClimatesDTO climateJSON) { VerisureBatteryStatusDTO batteryStatus = climateJSON.getBatteryStatus(); if (batteryStatus != null) { String status = batteryStatus.getStatus(); - if (status != null && status.equals("CRITICAL")) { + if (status != null && "CRITICAL".equals(status)) { return OnOffType.from(true); } } diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureDoorWindowThingHandler.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureDoorWindowThingHandler.java index d44d2ff70af59..ce05f95e70f44 100644 --- a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureDoorWindowThingHandler.java +++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/handler/VerisureDoorWindowThingHandler.java @@ -88,7 +88,7 @@ public State getValue(String channelId, DoorWindow doorWindow, VerisureDoorWindo VerisureBatteryStatusDTO batteryStatus = doorWindowJSON.getBatteryStatus(); if (batteryStatus != null) { String status = batteryStatus.getStatus(); - if (status != null && status.equals("CRITICAL")) { + if (status != null && "CRITICAL".equals(status)) { return OnOffType.from(true); } }