2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.verisure.internal;
15 import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.net.CookieStore;
19 import java.net.HttpCookie;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.CopyOnWriteArrayList;
29 import java.util.concurrent.ExecutionException;
30 import java.util.concurrent.TimeoutException;
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.eclipse.jetty.client.HttpClient;
35 import org.eclipse.jetty.client.HttpResponseException;
36 import org.eclipse.jetty.client.api.ContentResponse;
37 import org.eclipse.jetty.client.api.Request;
38 import org.eclipse.jetty.client.util.BytesContentProvider;
39 import org.eclipse.jetty.http.HttpMethod;
40 import org.eclipse.jetty.http.HttpStatus;
41 import org.jsoup.Jsoup;
42 import org.jsoup.nodes.Document;
43 import org.jsoup.nodes.Element;
44 import org.openhab.binding.verisure.internal.dto.VerisureAlarmsDTO;
45 import org.openhab.binding.verisure.internal.dto.VerisureBatteryStatusDTO;
46 import org.openhab.binding.verisure.internal.dto.VerisureBroadbandConnectionsDTO;
47 import org.openhab.binding.verisure.internal.dto.VerisureClimatesDTO;
48 import org.openhab.binding.verisure.internal.dto.VerisureDoorWindowsDTO;
49 import org.openhab.binding.verisure.internal.dto.VerisureEventLogDTO;
50 import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO;
51 import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO.CommunicationState;
52 import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO;
53 import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO.Owainstallation;
54 import org.openhab.binding.verisure.internal.dto.VerisureMiceDetectionDTO;
55 import org.openhab.binding.verisure.internal.dto.VerisureSmartLockDTO;
56 import org.openhab.binding.verisure.internal.dto.VerisureSmartLocksDTO;
57 import org.openhab.binding.verisure.internal.dto.VerisureSmartPlugsDTO;
58 import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
59 import org.openhab.binding.verisure.internal.dto.VerisureUserPresencesDTO;
60 import org.openhab.binding.verisure.internal.handler.VerisureThingHandler;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import com.google.gson.Gson;
65 import com.google.gson.JsonSyntaxException;
68 * This class performs the communication with Verisure My Pages.
70 * @author Jarle Hjortland - Initial contribution
71 * @author Jan Gustafsson - Re-design and support for several sites and update to new Verisure API
75 public class VerisureSession {
78 private final Map<String, VerisureThingDTO> verisureThings = new ConcurrentHashMap<>();
79 private final Map<String, VerisureThingHandler<?>> verisureHandlers = new ConcurrentHashMap<>();
80 private final Logger logger = LoggerFactory.getLogger(VerisureSession.class);
81 private final Gson gson = new Gson();
82 private final List<DeviceStatusListener<VerisureThingDTO>> deviceStatusListeners = new CopyOnWriteArrayList<>();
83 private final Map<BigDecimal, VerisureInstallation> verisureInstallations = new ConcurrentHashMap<>();
84 private static final List<String> APISERVERLIST = Arrays.asList("https://m-api01.verisure.com",
85 "https://m-api02.verisure.com");
86 private int apiServerInUseIndex = 0;
87 private int numberOfEvents = 15;
88 private static final String USER_NAME = "username";
89 private static final String PASSWORD_NAME = "vid";
90 private String apiServerInUse = APISERVERLIST.get(apiServerInUseIndex);
91 private String authstring = "";
92 private @Nullable String csrf;
93 private @Nullable String pinCode;
94 private HttpClient httpClient;
95 private @Nullable String userName = "";
96 private @Nullable String password = "";
98 public VerisureSession(HttpClient httpClient) {
99 this.httpClient = httpClient;
102 public boolean initialize(@Nullable String authstring, @Nullable String pinCode, @Nullable String userName) {
103 if (authstring != null) {
104 this.authstring = authstring.substring(0);
105 this.pinCode = pinCode;
106 this.userName = userName;
107 // Try to login to Verisure
109 return getInstallations();
117 public boolean refresh() {
125 } catch (HttpResponseException e) {
126 logger.warn("Failed to do a refresh {}", e.getMessage());
131 public int sendCommand(String url, String data, BigDecimal installationId) {
132 logger.debug("Sending command with URL {} and data {}", url, data);
134 configureInstallationInstance(installationId);
135 int httpResultCode = setSessionCookieAuthLogin();
136 if (httpResultCode == HttpStatus.OK_200) {
137 return postVerisureAPI(url, data);
139 return httpResultCode;
141 } catch (ExecutionException | InterruptedException | TimeoutException e) {
142 logger.debug("Failed to send command {}", e.getMessage());
144 return HttpStatus.BAD_REQUEST_400;
147 public boolean unregisterDeviceStatusListener(
148 DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
149 return deviceStatusListeners.remove(deviceStatusListener);
152 @SuppressWarnings("unchecked")
153 public boolean registerDeviceStatusListener(DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
154 return deviceStatusListeners.add((DeviceStatusListener<VerisureThingDTO>) deviceStatusListener);
157 @SuppressWarnings({ "unchecked" })
158 public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId, Class<T> thingType) {
159 VerisureThingDTO thing = verisureThings.get(deviceId);
160 if (thingType.isInstance(thing)) {
166 public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId) {
167 VerisureThingDTO thing = verisureThings.get(deviceId);
169 @SuppressWarnings("unchecked")
170 T thing2 = (T) thing;
176 public @Nullable VerisureThingHandler<?> getVerisureThinghandler(String deviceId) {
177 VerisureThingHandler<?> thingHandler = verisureHandlers.get(deviceId);
181 public void setVerisureThingHandler(VerisureThingHandler<?> vth, String deviceId) {
182 verisureHandlers.put(deviceId, vth);
185 public void removeVerisureThingHandler(String deviceId) {
186 verisureHandlers.remove(deviceId);
189 public Collection<VerisureThingDTO> getVerisureThings() {
190 return verisureThings.values();
193 public @Nullable String getCsrf() {
197 public @Nullable String getPinCode() {
201 public String getApiServerInUse() {
202 return apiServerInUse;
205 public void setApiServerInUse(String apiServerInUse) {
206 this.apiServerInUse = apiServerInUse;
209 public String getNextApiServer() {
210 apiServerInUseIndex++;
211 if (apiServerInUseIndex > (APISERVERLIST.size() - 1)) {
212 apiServerInUseIndex = 0;
214 return APISERVERLIST.get(apiServerInUseIndex);
217 public void setNumberOfEvents(int numberOfEvents) {
218 this.numberOfEvents = numberOfEvents;
221 public void configureInstallationInstance(BigDecimal installationId)
222 throws ExecutionException, InterruptedException, TimeoutException {
223 csrf = getCsrfToken(installationId);
224 logger.debug("Got CSRF: {}", csrf);
226 String url = SET_INSTALLATION + installationId;
230 public @Nullable String getCsrfToken(BigDecimal installationId)
231 throws ExecutionException, InterruptedException, TimeoutException {
233 String url = SETTINGS + installationId;
235 ContentResponse resp = httpClient.GET(url);
236 html = resp.getContentAsString();
237 logger.trace("url: {} html: {}", url, html);
239 Document htmlDocument = Jsoup.parse(html);
240 Element nameInput = htmlDocument.select("input[name=_csrf]").first();
241 if (nameInput != null) {
242 return nameInput.attr("value");
248 public @Nullable String getPinCode(BigDecimal installationId) {
249 return verisureInstallations.get(installationId).getPinCode();
252 private void setPasswordFromCookie() {
253 CookieStore c = httpClient.getCookieStore();
254 List<HttpCookie> cookies = c.getCookies();
255 cookies.forEach(cookie -> {
256 logger.trace("Response Cookie: {}", cookie);
257 if (cookie.getName().equals(PASSWORD_NAME)) {
258 password = cookie.getValue();
259 logger.debug("Fetching vid {} from cookie", password);
264 private void logTraceWithPattern(int responseStatus, String content) {
265 if (logger.isTraceEnabled()) {
266 String pattern = "(?m)^\\s*\\r?\\n|\\r?\\n\\s*(?!.*\\r?\\n)";
267 String replacement = "";
268 logger.trace("HTTP Response ({}) Body:{}", responseStatus, content.replaceAll(pattern, replacement));
272 private boolean areWeLoggedIn() throws ExecutionException, InterruptedException, TimeoutException {
273 logger.debug("Checking if we are logged in");
276 ContentResponse response = httpClient.newRequest(url).method(HttpMethod.GET).send();
277 String content = response.getContentAsString();
278 logTraceWithPattern(response.getStatus(), content);
280 switch (response.getStatus()) {
281 case HttpStatus.OK_200:
282 if (content.contains("<link href=\"/newapp")) {
283 setPasswordFromCookie();
286 logger.debug("We need to login again!");
289 case HttpStatus.MOVED_TEMPORARILY_302:
291 logger.debug("Status code 302. Redirected. Probably not logged in");
293 case HttpStatus.INTERNAL_SERVER_ERROR_500:
294 case HttpStatus.SERVICE_UNAVAILABLE_503:
295 throw new HttpResponseException(
296 "Status code " + response.getStatus() + ". Verisure service temporarily down", response);
298 logger.debug("Status code {} body {}", response.getStatus(), content);
304 private <T> @Nullable T getJSONVerisureAPI(String url, Class<T> jsonClass)
305 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
306 logger.debug("HTTP GET: {}", BASEURL + url);
308 ContentResponse response = httpClient.GET(BASEURL + url + "?_=" + System.currentTimeMillis());
309 String content = response.getContentAsString();
310 logTraceWithPattern(response.getStatus(), content);
312 return gson.fromJson(content, jsonClass);
315 private ContentResponse postVerisureAPI(String url, String data, boolean isJSON)
316 throws ExecutionException, InterruptedException, TimeoutException {
317 logger.debug("postVerisureAPI URL: {} Data:{}", url, data);
318 Request request = httpClient.newRequest(url).method(HttpMethod.POST);
320 request.header("content-type", "application/json");
323 request.header("X-CSRF-TOKEN", csrf);
326 request.header("Accept", "application/json");
327 if (!data.equals("empty")) {
328 request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)),
329 "application/x-www-form-urlencoded; charset=UTF-8");
331 logger.debug("Setting cookie with username {} and vid {}", userName, password);
332 request.cookie(new HttpCookie(USER_NAME, userName));
333 request.cookie(new HttpCookie(PASSWORD_NAME, password));
335 logger.debug("HTTP POST Request {}.", request.toString());
336 return request.send();
339 private <T> T postJSONVerisureAPI(String url, String data, Class<T> jsonClass)
340 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException, PostToAPIException {
341 for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
342 ContentResponse response = postVerisureAPI(apiServerInUse + url, data, Boolean.TRUE);
343 logger.debug("HTTP Response ({})", response.getStatus());
344 if (response.getStatus() == HttpStatus.OK_200) {
345 String content = response.getContentAsString();
346 if (content.contains("\"message\":\"Request Failed") && content.contains("503")) {
347 // Maybe Verisure has switched API server in use?
348 logger.debug("Changed API server! Response: {}", content);
349 setApiServerInUse(getNextApiServer());
351 String contentChomped = content.trim();
352 logger.trace("Response body: {}", content);
353 return gson.fromJson(contentChomped, jsonClass);
356 logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
359 throw new PostToAPIException("Failed to POST to API");
362 private int postVerisureAPI(String urlString, String data) {
364 if (urlString.contains("https://mypages")) {
367 url = apiServerInUse + urlString;
370 for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
372 ContentResponse response = postVerisureAPI(url, data, Boolean.FALSE);
373 logger.debug("HTTP Response ({})", response.getStatus());
374 int httpStatus = response.getStatus();
375 if (httpStatus == HttpStatus.OK_200) {
376 String content = response.getContentAsString();
377 if (content.contains("\"message\":\"Request Failed. Code 503 from")) {
378 if (url.contains("https://mypages")) {
380 return HttpStatus.SERVICE_UNAVAILABLE_503;
382 // Maybe Verisure has switched API server in use
383 setApiServerInUse(getNextApiServer());
384 url = apiServerInUse + urlString;
387 logTraceWithPattern(httpStatus, content);
391 logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
393 } catch (ExecutionException | InterruptedException | TimeoutException e) {
394 logger.warn("Failed to send a POST to the API {}", e.getMessage());
397 return HttpStatus.SERVICE_UNAVAILABLE_503;
400 private int setSessionCookieAuthLogin() throws ExecutionException, InterruptedException, TimeoutException {
401 // URL to set status which will give us 2 cookies with username and password used for the session
403 ContentResponse response = httpClient.GET(url);
404 logTraceWithPattern(response.getStatus(), response.getContentAsString());
407 return postVerisureAPI(url, "empty");
410 private boolean getInstallations() {
411 int httpResultCode = 0;
414 httpResultCode = setSessionCookieAuthLogin();
415 } catch (ExecutionException | InterruptedException | TimeoutException e) {
416 logger.warn("Failed to set session cookie {}", e.getMessage());
420 if (httpResultCode == HttpStatus.OK_200) {
421 String url = START_GRAPHQL;
423 String queryQLAccountInstallations = "[{\"operationName\":\"AccountInstallations\",\"variables\":{\"email\":\""
425 + "\"},\"query\":\"query AccountInstallations($email: String!) {\\n account(email: $email) {\\n owainstallations {\\n giid\\n alias\\n type\\n subsidiary\\n dealerId\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]";
427 VerisureInstallationsDTO installations = postJSONVerisureAPI(url, queryQLAccountInstallations,
428 VerisureInstallationsDTO.class);
429 logger.debug("Installation: {}", installations.toString());
430 List<Owainstallation> owaInstList = installations.getData().getAccount().getOwainstallations();
431 boolean pinCodesMatchInstallations = true;
432 List<String> pinCodes = null;
433 String pinCode = this.pinCode;
434 if (pinCode != null) {
435 pinCodes = Arrays.asList(pinCode.split(","));
436 if (owaInstList.size() != pinCodes.size()) {
437 logger.debug("Number of installations {} does not match number of pin codes configured {}",
438 owaInstList.size(), pinCodes.size());
439 pinCodesMatchInstallations = false;
442 logger.debug("No pin-code defined for user {}", userName);
445 for (int i = 0; i < owaInstList.size(); i++) {
446 VerisureInstallation vInst = new VerisureInstallation();
447 Owainstallation owaInstallation = owaInstList.get(i);
448 String installationId = owaInstallation.getGiid();
449 if (owaInstallation.getAlias() != null && installationId != null) {
450 vInst.setInstallationId(new BigDecimal(installationId));
451 vInst.setInstallationName(owaInstallation.getAlias());
452 if (pinCode != null && pinCodes != null) {
453 int pinCodeIndex = pinCodesMatchInstallations ? i : 0;
454 vInst.setPinCode(pinCodes.get(pinCodeIndex));
455 logger.debug("Setting configured pincode index[{}] to installation ID {}", pinCodeIndex,
458 verisureInstallations.put(new BigDecimal(installationId), vInst);
460 logger.warn("Failed to get alias and/or giid");
464 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
465 | PostToAPIException e) {
466 logger.warn("Failed to send a POST to the API {}", e.getMessage());
469 logger.warn("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
475 private synchronized boolean logIn() {
477 if (!areWeLoggedIn()) {
478 logger.debug("Attempting to log in to mypages.verisure.com");
479 String url = LOGON_SUF;
480 logger.debug("Login URL: {}", url);
481 int httpStatusCode = postVerisureAPI(url, authstring);
482 if (httpStatusCode != HttpStatus.OK_200) {
483 logger.debug("Failed to login, HTTP status code: {}", httpStatusCode);
490 } catch (ExecutionException | InterruptedException | TimeoutException e) {
491 logger.warn("Failed to login {}", e.getMessage());
496 private <T extends VerisureThingDTO> void notifyListeners(T thing) {
497 deviceStatusListeners.forEach(listener -> {
498 if (listener.getVerisureThingClass().equals(thing.getClass())) {
499 listener.onDeviceStateChanged(thing);
504 private void notifyListenersIfChanged(VerisureThingDTO thing, VerisureInstallation installation, String deviceId) {
505 String normalizedDeviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
506 thing.setDeviceId(normalizedDeviceId);
507 thing.setSiteId(installation.getInstallationId());
508 thing.setSiteName(installation.getInstallationName());
509 VerisureThingDTO oldObj = verisureThings.get(normalizedDeviceId);
510 if (!thing.equals(oldObj)) {
511 verisureThings.put(thing.getDeviceId(), thing);
512 notifyListeners(thing);
514 logger.trace("No need to notify listeners for thing: {}", thing);
518 private void updateStatus() {
519 logger.debug("Update status");
520 verisureInstallations.forEach((installationId, installation) -> {
522 configureInstallationInstance(installation.getInstallationId());
523 int httpResultCode = setSessionCookieAuthLogin();
524 if (httpResultCode == HttpStatus.OK_200) {
525 updateAlarmStatus(installation);
526 updateSmartLockStatus(installation);
527 updateMiceDetectionStatus(installation);
528 updateClimateStatus(installation);
529 updateDoorWindowStatus(installation);
530 updateUserPresenceStatus(installation);
531 updateSmartPlugStatus(installation);
532 updateBroadbandConnectionStatus(installation);
533 updateEventLogStatus(installation);
534 updateGatewayStatus(installation);
536 logger.debug("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
538 } catch (ExecutionException | InterruptedException | TimeoutException e) {
539 logger.debug("Failed to update status {}", e.getMessage());
544 private String createOperationJSON(String operation, VariablesDTO variables, String query) {
545 OperationDTO operationJSON = new OperationDTO();
546 operationJSON.setOperationName(operation);
547 operationJSON.setVariables(variables);
548 operationJSON.setQuery(query);
549 return gson.toJson(Collections.singletonList(operationJSON));
552 private synchronized void updateAlarmStatus(VerisureInstallation installation) {
553 BigDecimal installationId = installation.getInstallationId();
554 String url = START_GRAPHQL;
555 String operation = "ArmState";
556 VariablesDTO variables = new VariablesDTO();
557 variables.setGiid(installationId.toString());
558 String query = "query " + operation
559 + "($giid: String!) {\n installation(giid: $giid) {\n armState {\n type\n statusType\n date\n name\n changedVia\n allowedForFirstLine\n allowed\n errorCodes {\n value\n message\n __typename\n}\n __typename\n}\n __typename\n}\n}\n";
561 String queryQLAlarmStatus = createOperationJSON(operation, variables, query);
562 logger.debug("Quering API for alarm status!");
564 VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLAlarmStatus, VerisureAlarmsDTO.class);
565 logger.debug("REST Response ({})", thing);
566 // Set unique deviceID
567 String deviceId = "alarm" + installationId;
568 thing.setDeviceId(deviceId);
569 notifyListenersIfChanged(thing, installation, deviceId);
570 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
571 | PostToAPIException e) {
572 logger.warn("Failed to send a POST to the API {}", e.getMessage());
576 private synchronized void updateSmartLockStatus(VerisureInstallation installation) {
577 BigDecimal installationId = installation.getInstallationId();
578 String url = START_GRAPHQL;
579 String operation = "DoorLock";
580 String query = "query " + operation
581 + "($giid: String!) {\n installation(giid: $giid) {\n doorlocks {\n device {\n deviceLabel\n area\n __typename\n}\n currentLockState\n eventTime\n secureModeActive\n motorJam\n userString\n method\n __typename\n}\n __typename\n}\n}\n";
582 VariablesDTO variables = new VariablesDTO();
583 variables.setGiid(installationId.toString());
584 String queryQLSmartLock = createOperationJSON(operation, variables, query);
585 logger.debug("Quering API for smart lock status");
588 VerisureSmartLocksDTO thing = postJSONVerisureAPI(url, queryQLSmartLock, VerisureSmartLocksDTO.class);
589 logger.debug("REST Response ({})", thing);
590 List<VerisureSmartLocksDTO.Doorlock> doorLockList = thing.getData().getInstallation().getDoorlocks();
591 doorLockList.forEach(doorLock -> {
592 VerisureSmartLocksDTO slThing = new VerisureSmartLocksDTO();
593 VerisureSmartLocksDTO.Installation inst = new VerisureSmartLocksDTO.Installation();
594 inst.setDoorlocks(Collections.singletonList(doorLock));
595 VerisureSmartLocksDTO.Data data = new VerisureSmartLocksDTO.Data();
596 data.setInstallation(inst);
597 slThing.setData(data);
598 // Set unique deviceID
599 String deviceId = doorLock.getDevice().getDeviceLabel();
600 if (deviceId != null) {
602 slThing.setLocation(doorLock.getDevice().getArea());
603 slThing.setDeviceId(deviceId);
604 // Fetch more info from old endpoint
606 VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(),
607 VerisureSmartLockDTO.class);
608 logger.debug("REST Response ({})", smartLockThing);
609 slThing.setSmartLockJSON(smartLockThing);
610 notifyListenersIfChanged(slThing, installation, deviceId);
611 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
612 logger.warn("Failed to query for smartlock status: {}", e.getMessage());
617 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
618 | PostToAPIException e) {
619 logger.warn("Failed to send a POST to the API {}", e.getMessage());
623 private synchronized void updateSmartPlugStatus(VerisureInstallation installation) {
624 BigDecimal installationId = installation.getInstallationId();
625 String url = START_GRAPHQL;
626 String operation = "SmartPlug";
627 VariablesDTO variables = new VariablesDTO();
628 variables.setGiid(installationId.toString());
629 String query = "query " + operation
630 + "($giid: String!) {\n installation(giid: $giid) {\n smartplugs {\n device {\n deviceLabel\n area\n gui {\n support\n label\n __typename\n}\n __typename\n}\n currentState\n icon\n isHazardous\n __typename\n}\n __typename\n}\n}\n";
631 String queryQLSmartPlug = createOperationJSON(operation, variables, query);
632 logger.debug("Quering API for smart plug status");
635 VerisureSmartPlugsDTO thing = postJSONVerisureAPI(url, queryQLSmartPlug, VerisureSmartPlugsDTO.class);
636 logger.debug("REST Response ({})", thing);
637 List<VerisureSmartPlugsDTO.Smartplug> smartPlugList = thing.getData().getInstallation().getSmartplugs();
638 smartPlugList.forEach(smartPlug -> {
639 VerisureSmartPlugsDTO spThing = new VerisureSmartPlugsDTO();
640 VerisureSmartPlugsDTO.Installation inst = new VerisureSmartPlugsDTO.Installation();
641 inst.setSmartplugs(Collections.singletonList(smartPlug));
642 VerisureSmartPlugsDTO.Data data = new VerisureSmartPlugsDTO.Data();
643 data.setInstallation(inst);
644 spThing.setData(data);
645 // Set unique deviceID
646 String deviceId = smartPlug.getDevice().getDeviceLabel();
647 if (deviceId != null) {
649 spThing.setLocation(smartPlug.getDevice().getArea());
650 notifyListenersIfChanged(spThing, installation, deviceId);
653 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
654 | PostToAPIException e) {
655 logger.warn("Failed to send a POST to the API {}", e.getMessage());
659 private @Nullable VerisureBatteryStatusDTO getBatteryStatus(String deviceId,
660 VerisureBatteryStatusDTO @Nullable [] batteryStatus) {
661 if (batteryStatus != null) {
662 for (VerisureBatteryStatusDTO verisureBatteryStatusDTO : batteryStatus) {
663 String id = verisureBatteryStatusDTO.getId();
664 if (id != null && id.equals(deviceId)) {
665 return verisureBatteryStatusDTO;
672 private synchronized void updateClimateStatus(VerisureInstallation installation) {
673 BigDecimal installationId = installation.getInstallationId();
674 String url = START_GRAPHQL;
675 VariablesDTO variables = new VariablesDTO();
676 variables.setGiid(installationId.toString());
677 String operation = "Climate";
678 String query = "query " + operation
679 + "($giid: String!) {\n installation(giid: $giid) {\n climates {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n humidityEnabled\n humidityTimestamp\n humidityValue\n temperatureTimestamp\n temperatureValue\n __typename\n }\n __typename\n}\n}\n";
681 String queryQLClimates = createOperationJSON(operation, variables, query);
682 logger.debug("Quering API for climate status");
685 VerisureClimatesDTO thing = postJSONVerisureAPI(url, queryQLClimates, VerisureClimatesDTO.class);
686 logger.debug("REST Response ({})", thing);
687 List<VerisureClimatesDTO.Climate> climateList = thing.getData().getInstallation().getClimates();
688 if (climateList != null) {
689 climateList.forEach(climate -> {
690 // If thing is Mouse detection device, then skip it, but fetch temperature from it
691 String type = climate.getDevice().getGui().getLabel();
692 if ("MOUSE".equals(type)) {
693 logger.debug("Mouse detection device!");
694 String deviceId = climate.getDevice().getDeviceLabel();
695 if (deviceId != null) {
696 deviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
697 VerisureThingDTO mouseThing = verisureThings.get(deviceId);
698 if (mouseThing != null && mouseThing instanceof VerisureMiceDetectionDTO) {
699 VerisureMiceDetectionDTO miceDetectorThing = (VerisureMiceDetectionDTO) mouseThing;
700 miceDetectorThing.setTemperatureValue(climate.getTemperatureValue());
701 miceDetectorThing.setTemperatureTime(climate.getTemperatureTimestamp());
702 notifyListeners(miceDetectorThing);
703 logger.debug("Found climate thing for a Verisure Mouse Detector");
708 VerisureClimatesDTO cThing = new VerisureClimatesDTO();
709 VerisureClimatesDTO.Installation inst = new VerisureClimatesDTO.Installation();
710 inst.setClimates(Collections.singletonList(climate));
711 VerisureClimatesDTO.Data data = new VerisureClimatesDTO.Data();
712 data.setInstallation(inst);
713 cThing.setData(data);
714 // Set unique deviceID
715 String deviceId = climate.getDevice().getDeviceLabel();
716 if (deviceId != null) {
718 VerisureBatteryStatusDTO[] batteryStatusThingArray = getJSONVerisureAPI(BATTERY_STATUS,
719 VerisureBatteryStatusDTO[].class);
720 VerisureBatteryStatusDTO batteryStatus = getBatteryStatus(deviceId,
721 batteryStatusThingArray);
722 if (batteryStatus != null) {
723 logger.debug("REST Response ({})", batteryStatus);
724 cThing.setBatteryStatus(batteryStatus);
726 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
727 logger.warn("Failed to query for smartlock status: {}", e.getMessage());
730 cThing.setLocation(climate.getDevice().getArea());
731 notifyListenersIfChanged(cThing, installation, deviceId);
735 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
736 | PostToAPIException e) {
737 logger.warn("Failed to send a POST to the API {}", e.getMessage());
741 private synchronized void updateDoorWindowStatus(VerisureInstallation installation) {
742 BigDecimal installationId = installation.getInstallationId();
743 String url = START_GRAPHQL;
744 String operation = "DoorWindow";
745 VariablesDTO variables = new VariablesDTO();
746 variables.setGiid(installationId.toString());
747 String query = "query " + operation
748 + "($giid: String!) {\n installation(giid: $giid) {\n doorWindows {\n device {\n deviceLabel\n area\n __typename\n }\n type\n state\n wired\n reportTime\n __typename\n }\n __typename\n}\n}\n";
750 String queryQLDoorWindow = createOperationJSON(operation, variables, query);
751 logger.debug("Quering API for door&window status");
754 VerisureDoorWindowsDTO thing = postJSONVerisureAPI(url, queryQLDoorWindow, VerisureDoorWindowsDTO.class);
755 logger.debug("REST Response ({})", thing);
756 List<VerisureDoorWindowsDTO.DoorWindow> doorWindowList = thing.getData().getInstallation().getDoorWindows();
757 doorWindowList.forEach(doorWindow -> {
758 VerisureDoorWindowsDTO dThing = new VerisureDoorWindowsDTO();
759 VerisureDoorWindowsDTO.Installation inst = new VerisureDoorWindowsDTO.Installation();
760 inst.setDoorWindows(Collections.singletonList(doorWindow));
761 VerisureDoorWindowsDTO.Data data = new VerisureDoorWindowsDTO.Data();
762 data.setInstallation(inst);
763 dThing.setData(data);
764 // Set unique deviceID
765 String deviceId = doorWindow.getDevice().getDeviceLabel();
766 if (deviceId != null) {
768 VerisureBatteryStatusDTO[] batteryStatusThingArray = getJSONVerisureAPI(BATTERY_STATUS,
769 VerisureBatteryStatusDTO[].class);
770 VerisureBatteryStatusDTO batteryStatus = getBatteryStatus(deviceId, batteryStatusThingArray);
771 if (batteryStatus != null) {
772 logger.debug("REST Response ({})", batteryStatus);
773 dThing.setBatteryStatus(batteryStatus);
775 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
776 logger.warn("Failed to query for smartlock status: {}", e.getMessage());
779 dThing.setLocation(doorWindow.getDevice().getArea());
780 notifyListenersIfChanged(dThing, installation, deviceId);
783 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
784 | PostToAPIException e) {
785 logger.warn("Failed to send a POST to the API {}", e.getMessage());
789 private synchronized void updateBroadbandConnectionStatus(VerisureInstallation inst) {
790 BigDecimal installationId = inst.getInstallationId();
791 String url = START_GRAPHQL;
792 String operation = "Broadband";
793 VariablesDTO variables = new VariablesDTO();
794 variables.setGiid(installationId.toString());
795 String query = "query " + operation
796 + "($giid: String!) {\n installation(giid: $giid) {\n broadband {\n testDate\n isBroadbandConnected\n __typename\n }\n __typename\n}\n}\n";
798 String queryQLBroadbandConnection = createOperationJSON(operation, variables, query);
799 logger.debug("Quering API for broadband connection status");
802 VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLBroadbandConnection,
803 VerisureBroadbandConnectionsDTO.class);
804 logger.debug("REST Response ({})", thing);
805 // Set unique deviceID
806 String deviceId = "bc" + installationId;
807 notifyListenersIfChanged(thing, inst, deviceId);
808 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
809 | PostToAPIException e) {
810 logger.warn("Failed to send a POST to the API {}", e.getMessage());
814 private synchronized void updateUserPresenceStatus(VerisureInstallation installation) {
815 BigDecimal installationId = installation.getInstallationId();
816 String url = START_GRAPHQL;
817 String operation = "userTrackings";
818 VariablesDTO variables = new VariablesDTO();
819 variables.setGiid(installationId.toString());
820 String query = "query " + operation
821 + "($giid: String!) {\ninstallation(giid: $giid) {\n userTrackings {\n isCallingUser\n webAccount\n status\n xbnContactId\n currentLocationName\n deviceId\n name\n currentLocationTimestamp\n deviceName\n currentLocationId\n __typename\n}\n __typename\n}\n}\n";
823 String queryQLUserPresence = createOperationJSON(operation, variables, query);
824 logger.debug("Quering API for user presence status");
827 VerisureUserPresencesDTO thing = postJSONVerisureAPI(url, queryQLUserPresence,
828 VerisureUserPresencesDTO.class);
829 logger.debug("REST Response ({})", thing);
830 List<VerisureUserPresencesDTO.UserTracking> userTrackingList = thing.getData().getInstallation()
832 userTrackingList.forEach(userTracking -> {
833 String localUserTrackingStatus = userTracking.getStatus();
834 if (localUserTrackingStatus != null && localUserTrackingStatus.equals("ACTIVE")) {
835 VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO();
836 VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation();
837 inst.setUserTrackings(Collections.singletonList(userTracking));
838 VerisureUserPresencesDTO.Data data = new VerisureUserPresencesDTO.Data();
839 data.setInstallation(inst);
840 upThing.setData(data);
841 // Set unique deviceID
842 String deviceId = "up" + userTracking.getWebAccount() + installationId;
843 notifyListenersIfChanged(upThing, installation, deviceId);
846 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
847 | PostToAPIException e) {
848 logger.warn("Failed to send a POST to the API {}", e.getMessage());
852 private synchronized void updateMiceDetectionStatus(VerisureInstallation installation) {
853 BigDecimal installationId = installation.getInstallationId();
854 String url = START_GRAPHQL;
855 String operation = "Mouse";
856 VariablesDTO variables = new VariablesDTO();
857 variables.setGiid(installationId.toString());
858 String query = "query " + operation
859 + "($giid: String!) {\n installation(giid: $giid) {\n mice {\n device {\n deviceLabel\n area\n gui {\n support\n __typename\n}\n __typename\n}\n type\n detections {\n count\n gatewayTime\n nodeTime\n duration\n __typename\n}\n __typename\n}\n __typename\n}\n}\n";
861 String queryQLMiceDetection = createOperationJSON(operation, variables, query);
862 logger.debug("Quering API for mice detection status");
865 VerisureMiceDetectionDTO thing = postJSONVerisureAPI(url, queryQLMiceDetection,
866 VerisureMiceDetectionDTO.class);
867 logger.debug("REST Response ({})", thing);
868 List<VerisureMiceDetectionDTO.Mouse> miceList = thing.getData().getInstallation().getMice();
869 miceList.forEach(mouse -> {
870 VerisureMiceDetectionDTO miceThing = new VerisureMiceDetectionDTO();
871 VerisureMiceDetectionDTO.Installation inst = new VerisureMiceDetectionDTO.Installation();
872 inst.setMice(Collections.singletonList(mouse));
873 VerisureMiceDetectionDTO.Data data = new VerisureMiceDetectionDTO.Data();
874 data.setInstallation(inst);
875 miceThing.setData(data);
876 // Set unique deviceID
877 String deviceId = mouse.getDevice().getDeviceLabel();
878 logger.debug("Mouse id: {} for thing: {}", deviceId, mouse);
879 if (deviceId != null) {
881 miceThing.setLocation(mouse.getDevice().getArea());
882 notifyListenersIfChanged(miceThing, installation, deviceId);
885 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
886 | PostToAPIException e) {
887 logger.warn("Failed to send a POST to the API {}", e.getMessage());
891 private synchronized void updateEventLogStatus(VerisureInstallation installation) {
892 BigDecimal installationId = installation.getInstallationId();
893 String url = START_GRAPHQL;
894 String operation = "EventLog";
896 int numberOfEvents = this.numberOfEvents;
897 List<String> eventCategories = new ArrayList<>(Arrays.asList("INTRUSION", "FIRE", "SOS", "WATER", "ANIMAL",
898 "TECHNICAL", "WARNING", "ARM", "DISARM", "LOCK", "UNLOCK", "PICTURE", "CLIMATE", "CAMERA_SETTINGS",
899 "DOORWINDOW_STATE_OPENED", "DOORWINDOW_STATE_CLOSED", "USERTRACKING"));
900 VariablesDTO variables = new VariablesDTO();
901 variables.setGiid(installationId.toString());
902 variables.setHideNotifications(true);
903 variables.setOffset(offset);
904 variables.setPagesize(numberOfEvents);
905 variables.setEventCategories(eventCategories);
906 String query = "query " + operation
907 + "($giid: String!, $offset: Int!, $pagesize: Int!, $eventCategories: [String], $fromDate: String, $toDate: String, $eventContactIds: [String]) {\n installation(giid: $giid) {\n eventLog(offset: $offset, pagesize: $pagesize, eventCategories: $eventCategories, eventContactIds: $eventContactIds, fromDate: $fromDate, toDate: $toDate) {\n moreDataAvailable\n pagedList {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n gatewayArea\n eventType\n eventCategory\n eventSource\n eventId\n eventTime\n userName\n armState\n userType\n climateValue\n sensorType\n eventCount\n __typename\n }\n __typename\n }\n __typename\n }\n}\n";
909 String queryQLEventLog = createOperationJSON(operation, variables, query);
910 logger.debug("Quering API for event log status");
913 VerisureEventLogDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureEventLogDTO.class);
914 logger.debug("REST Response ({})", thing);
915 // Set unique deviceID
916 String deviceId = "el" + installationId;
917 notifyListenersIfChanged(thing, installation, deviceId);
918 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
919 | PostToAPIException e) {
920 logger.warn("Failed to send a POST to the API {}", e.getMessage());
924 private synchronized void updateGatewayStatus(VerisureInstallation installation) {
925 BigDecimal installationId = installation.getInstallationId();
926 String url = START_GRAPHQL;
927 String operation = "communicationState";
928 VariablesDTO variables = new VariablesDTO();
929 variables.setGiid(installationId.toString());
931 String query = "query " + operation
932 + "($giid: String!) {\n installation(giid: $giid) {\n communicationState {\n hardwareCarrierType\n result\n mediaType\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n testDate\n __typename\n }\n __typename\n }\n}";
934 String queryQLEventLog = createOperationJSON(operation, variables, query);
935 logger.debug("Quering API for gateway status");
938 VerisureGatewayDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureGatewayDTO.class);
939 logger.debug("REST Response ({})", thing);
940 // Set unique deviceID
941 List<CommunicationState> communicationStateList = thing.getData().getInstallation().getCommunicationState();
942 if (!communicationStateList.isEmpty()) {
943 String deviceId = communicationStateList.get(0).getDevice().getDeviceLabel();
944 if (deviceId != null) {
945 notifyListenersIfChanged(thing, installation, deviceId);
948 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
949 | PostToAPIException e) {
950 logger.warn("Failed to send a POST to the API {}", e.getMessage());
954 private final class VerisureInstallation {
955 private @Nullable String installationName;
956 private BigDecimal installationId = BigDecimal.ZERO;
957 private @Nullable String pinCode;
959 public @Nullable String getPinCode() {
963 public void setPinCode(@Nullable String pinCode) {
964 this.pinCode = pinCode;
967 public VerisureInstallation() {
970 public BigDecimal getInstallationId() {
971 return installationId;
974 public @Nullable String getInstallationName() {
975 return installationName;
978 public void setInstallationId(BigDecimal installationId) {
979 this.installationId = installationId;
982 public void setInstallationName(@Nullable String installationName) {
983 this.installationName = installationName;
987 private static class OperationDTO {
989 @SuppressWarnings("unused")
990 private @Nullable String operationName;
991 @SuppressWarnings("unused")
992 private VariablesDTO variables = new VariablesDTO();
993 @SuppressWarnings("unused")
994 private @Nullable String query;
996 public void setOperationName(String operationName) {
997 this.operationName = operationName;
1000 public void setVariables(VariablesDTO variables) {
1001 this.variables = variables;
1004 public void setQuery(String query) {
1009 public static class VariablesDTO {
1011 @SuppressWarnings("unused")
1012 private boolean hideNotifications;
1013 @SuppressWarnings("unused")
1015 @SuppressWarnings("unused")
1016 private int pagesize;
1017 @SuppressWarnings("unused")
1018 private @Nullable List<String> eventCategories = null;
1019 @SuppressWarnings("unused")
1020 private @Nullable String giid;
1022 public void setHideNotifications(boolean hideNotifications) {
1023 this.hideNotifications = hideNotifications;
1026 public void setOffset(int offset) {
1027 this.offset = offset;
1030 public void setPagesize(int pagesize) {
1031 this.pagesize = pagesize;
1034 public void setEventCategories(List<String> eventCategories) {
1035 this.eventCategories = eventCategories;
1038 public void setGiid(String giid) {
1043 private class PostToAPIException extends Exception {
1045 private static final long serialVersionUID = 1L;
1047 public PostToAPIException(String message) {