[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
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

View File

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

View File

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

View File

@ -17,9 +17,12 @@ import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
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,24 +89,30 @@ 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;
private @Nullable String pinCode;
private HttpClient httpClient;
private @Nullable String userName = "";
private @Nullable String password = "";
private String userName = "";
private String password = "";
private String vid = "";
private String vsAccess = "";
private 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, String userName, 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 +128,9 @@ public class VerisureSession {
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 +264,21 @@ public class VerisureSession {
}
}
private void setPasswordFromCookie() {
private void analyzeCookies() {
CookieStore c = httpClient.getCookieStore();
List<HttpCookie> cookies = c.getCookies();
final List<HttpCookie> 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 +302,6 @@ public class VerisureSession {
switch (response.getStatus()) {
case HttpStatus.OK_200:
if (content.contains("<link href=\"/newapp")) {
setPasswordFromCookie();
return true;
} else {
logger.debug("We need to login again!");
@ -313,9 +324,9 @@ public class VerisureSession {
private <T> @Nullable T getJSONVerisureAPI(String url, Class<T> 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 +336,7 @@ public class VerisureSession {
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 +346,29 @@ public class VerisureSession {
}
}
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)),
"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());
return request.send();
}
@ -400,6 +427,9 @@ public class VerisureSession {
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 +447,11 @@ public class VerisureSession {
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 +522,26 @@ public class VerisureSession {
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.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 +550,7 @@ public class VerisureSession {
} 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 +667,17 @@ public class VerisureSession {
// 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 +791,7 @@ public class VerisureSession {
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 +840,7 @@ public class VerisureSession {
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 +898,7 @@ public class VerisureSession {
.getUserTrackings();
userTrackingList.forEach(userTracking -> {
String localUserTrackingStatus = userTracking.getStatus();
if (localUserTrackingStatus != null && localUserTrackingStatus.equals("ACTIVE")) {
if ("ACTIVE".equals(localUserTrackingStatus)) {
VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO();
VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation();
inst.setUserTrackings(Collections.singletonList(userTracking));

View File

@ -75,7 +75,8 @@ public class VerisureThingDiscoveryService extends AbstractDiscoveryService
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 @@ public class VerisureThingDiscoveryService extends AbstractDiscoveryService
}
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);
}

View File

@ -14,9 +14,6 @@ package org.openhab.binding.verisure.internal.handler;
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.Collections;
import java.util.Set;
@ -65,7 +62,6 @@ public class VerisureBridgeHandler extends BaseBridgeHandler {
private String authstring = "";
private @Nullable String pinCode;
private static int REFRESH_SEC = 600;
private @Nullable ScheduledFuture<?> refreshJob;
private @Nullable ScheduledFuture<?> immediateRefreshJob;
private @Nullable VerisureSession session;
@ -104,20 +100,19 @@ public class VerisureBridgeHandler extends BaseBridgeHandler {
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) {
if (config.username.isBlank() || config.password.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"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 {
try {
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) {
logger.debug("Session is null, let's create a new one");
session = new VerisureSession(this.httpClient);
@ -125,18 +120,15 @@ public class VerisureBridgeHandler extends BaseBridgeHandler {
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();
startAutomaticRefresh(config.refresh);
}
});
} catch (RuntimeException | UnsupportedEncodingException e) {
} catch (RuntimeException e) {
logger.warn("Failed to initialize: {}", 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;
logger.debug("Start automatic refresh {}", refreshJob);
if (refreshJob == null || refreshJob.isCancelled()) {
try {
this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, REFRESH_SEC,
this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refresh,
TimeUnit.SECONDS);
logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
} 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 java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.measure.quantity.Dimensionless;
@ -23,6 +24,7 @@ import javax.measure.quantity.Temperature;
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 @@ public class VerisureClimateDeviceThingHandler extends VerisureThingHandler<Veri
State state = getValue(channelUID.getId(), climateJSON);
updateState(channelUID, state);
});
String timeStamp = climateJSON.getData().getInstallation().getClimates().get(0).getTemperatureTimestamp();
if (timeStamp != null) {
updateTimeStamp(timeStamp);
List<Climate> climateList = climateJSON.getData().getInstallation().getClimates();
if (climateList != null && !climateList.isEmpty()) {
String timeStamp = climateList.get(0).getTemperatureTimestamp();
if (timeStamp != null) {
updateTimeStamp(timeStamp);
}
}
updateInstallationChannels(climateJSON);
}
public State getValue(String channelId, VerisureClimatesDTO climateJSON) {
List<Climate> climateList = climateJSON.getData().getInstallation().getClimates();
switch (channelId) {
case CHANNEL_TEMPERATURE:
double temperature = climateJSON.getData().getInstallation().getClimates().get(0).getTemperatureValue();
return new QuantityType<Temperature>(temperature, SIUnits.CELSIUS);
if (climateList != null && !climateList.isEmpty()) {
double temperature = climateList.get(0).getTemperatureValue();
return new QuantityType<Temperature>(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.isEmpty() && climateList.get(0).isHumidityEnabled()) {
double humidity = climateList.get(0).getHumidityValue();
return new QuantityType<Dimensionless>(humidity, Units.PERCENT);
}
case CHANNEL_HUMIDITY_ENABLED:
boolean humidityEnabled = climateJSON.getData().getInstallation().getClimates().get(0)
.isHumidityEnabled();
return OnOffType.from(humidityEnabled);
if (climateList != null && !climateList.isEmpty()) {
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 class VerisureClimateDeviceThingHandler extends VerisureThingHandler<Veri
VerisureBatteryStatusDTO batteryStatus = climateJSON.getBatteryStatus();
if (batteryStatus != null) {
String status = batteryStatus.getStatus();
if (status != null && status.equals("CRITICAL")) {
if ("CRITICAL".equals(status)) {
return OnOffType.from(true);
}
}

View File

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