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.VerisureBroadbandConnectionsDTO;
46 import org.openhab.binding.verisure.internal.dto.VerisureClimatesDTO;
47 import org.openhab.binding.verisure.internal.dto.VerisureDoorWindowsDTO;
48 import org.openhab.binding.verisure.internal.dto.VerisureEventLogDTO;
49 import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO;
50 import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO.CommunicationState;
51 import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO;
52 import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO.Owainstallation;
53 import org.openhab.binding.verisure.internal.dto.VerisureMiceDetectionDTO;
54 import org.openhab.binding.verisure.internal.dto.VerisureSmartLockDTO;
55 import org.openhab.binding.verisure.internal.dto.VerisureSmartLocksDTO;
56 import org.openhab.binding.verisure.internal.dto.VerisureSmartPlugsDTO;
57 import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
58 import org.openhab.binding.verisure.internal.dto.VerisureUserPresencesDTO;
59 import org.openhab.binding.verisure.internal.handler.VerisureThingHandler;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
63 import com.google.gson.Gson;
64 import com.google.gson.JsonSyntaxException;
67 * This class performs the communication with Verisure My Pages.
69 * @author Jarle Hjortland - Initial contribution
70 * @author Jan Gustafsson - Re-design and support for several sites and update to new Verisure API
74 public class VerisureSession {
77 private final Map<String, VerisureThingDTO> verisureThings = new ConcurrentHashMap<>();
78 private final Map<String, VerisureThingHandler<?>> verisureHandlers = new ConcurrentHashMap<>();
79 private final Logger logger = LoggerFactory.getLogger(VerisureSession.class);
80 private final Gson gson = new Gson();
81 private final List<DeviceStatusListener<VerisureThingDTO>> deviceStatusListeners = new CopyOnWriteArrayList<>();
82 private final Map<BigDecimal, VerisureInstallation> verisureInstallations = new ConcurrentHashMap<>();
83 private static final List<String> APISERVERLIST = Arrays.asList("https://m-api01.verisure.com",
84 "https://m-api02.verisure.com");
85 private int apiServerInUseIndex = 0;
86 private int numberOfEvents = 15;
87 private static final String USER_NAME = "username";
88 private static final String PASSWORD_NAME = "vid";
89 private String apiServerInUse = APISERVERLIST.get(apiServerInUseIndex);
90 private String authstring = "";
91 private @Nullable String csrf;
92 private @Nullable String pinCode;
93 private HttpClient httpClient;
94 private @Nullable String userName = "";
95 private @Nullable String password = "";
97 public VerisureSession(HttpClient httpClient) {
98 this.httpClient = httpClient;
101 public boolean initialize(@Nullable String authstring, @Nullable String pinCode, @Nullable String userName) {
102 if (authstring != null) {
103 this.authstring = authstring.substring(0);
104 this.pinCode = pinCode;
105 this.userName = userName;
106 // Try to login to Verisure
108 return getInstallations();
116 public boolean refresh() {
124 } catch (HttpResponseException e) {
125 logger.warn("Failed to do a refresh {}", e.getMessage());
130 public int sendCommand(String url, String data, BigDecimal installationId) {
131 logger.debug("Sending command with URL {} and data {}", url, data);
133 configureInstallationInstance(installationId);
134 int httpResultCode = setSessionCookieAuthLogin();
135 if (httpResultCode == HttpStatus.OK_200) {
136 return postVerisureAPI(url, data);
138 return httpResultCode;
140 } catch (ExecutionException | InterruptedException | TimeoutException e) {
141 logger.debug("Failed to send command {}", e.getMessage());
143 return HttpStatus.BAD_REQUEST_400;
146 public boolean unregisterDeviceStatusListener(
147 DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
148 return deviceStatusListeners.remove(deviceStatusListener);
151 @SuppressWarnings("unchecked")
152 public boolean registerDeviceStatusListener(DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
153 return deviceStatusListeners.add((DeviceStatusListener<VerisureThingDTO>) deviceStatusListener);
156 @SuppressWarnings({ "unchecked" })
157 public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId, Class<T> thingType) {
158 VerisureThingDTO thing = verisureThings.get(deviceId);
159 if (thingType.isInstance(thing)) {
165 public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId) {
166 VerisureThingDTO thing = verisureThings.get(deviceId);
168 @SuppressWarnings("unchecked")
169 T thing2 = (T) thing;
175 public @Nullable VerisureThingHandler<?> getVerisureThinghandler(String deviceId) {
176 VerisureThingHandler<?> thingHandler = verisureHandlers.get(deviceId);
180 public void setVerisureThingHandler(VerisureThingHandler<?> vth, String deviceId) {
181 verisureHandlers.put(deviceId, vth);
184 public void removeVerisureThingHandler(String deviceId) {
185 verisureHandlers.remove(deviceId);
188 public Collection<VerisureThingDTO> getVerisureThings() {
189 return verisureThings.values();
192 public @Nullable String getCsrf() {
196 public @Nullable String getPinCode() {
200 public String getApiServerInUse() {
201 return apiServerInUse;
204 public void setApiServerInUse(String apiServerInUse) {
205 this.apiServerInUse = apiServerInUse;
208 public String getNextApiServer() {
209 apiServerInUseIndex++;
210 if (apiServerInUseIndex > (APISERVERLIST.size() - 1)) {
211 apiServerInUseIndex = 0;
213 return APISERVERLIST.get(apiServerInUseIndex);
216 public void setNumberOfEvents(int numberOfEvents) {
217 this.numberOfEvents = numberOfEvents;
220 public void configureInstallationInstance(BigDecimal installationId)
221 throws ExecutionException, InterruptedException, TimeoutException {
222 csrf = getCsrfToken(installationId);
223 logger.debug("Got CSRF: {}", csrf);
225 String url = SET_INSTALLATION + installationId;
229 public @Nullable String getCsrfToken(BigDecimal installationId)
230 throws ExecutionException, InterruptedException, TimeoutException {
232 String url = SETTINGS + installationId;
234 ContentResponse resp = httpClient.GET(url);
235 html = resp.getContentAsString();
236 logger.trace("url: {} html: {}", url, html);
238 Document htmlDocument = Jsoup.parse(html);
239 Element nameInput = htmlDocument.select("input[name=_csrf]").first();
240 if (nameInput != null) {
241 return nameInput.attr("value");
247 public @Nullable String getPinCode(BigDecimal installationId) {
248 return verisureInstallations.get(installationId).getPinCode();
251 private void setPasswordFromCookie() {
252 CookieStore c = httpClient.getCookieStore();
253 List<HttpCookie> cookies = c.getCookies();
254 cookies.forEach(cookie -> {
255 logger.trace("Response Cookie: {}", cookie);
256 if (cookie.getName().equals(PASSWORD_NAME)) {
257 password = cookie.getValue();
258 logger.debug("Fetching vid {} from cookie", password);
263 private void logTraceWithPattern(int responseStatus, String content) {
264 if (logger.isTraceEnabled()) {
265 String pattern = "(?m)^\\s*\\r?\\n|\\r?\\n\\s*(?!.*\\r?\\n)";
266 String replacement = "";
267 logger.trace("HTTP Response ({}) Body:{}", responseStatus, content.replaceAll(pattern, replacement));
271 private boolean areWeLoggedIn() throws ExecutionException, InterruptedException, TimeoutException {
272 logger.debug("Checking if we are logged in");
275 ContentResponse response = httpClient.newRequest(url).method(HttpMethod.GET).send();
276 String content = response.getContentAsString();
277 logTraceWithPattern(response.getStatus(), content);
279 switch (response.getStatus()) {
280 case HttpStatus.OK_200:
281 if (content.contains("<link href=\"/newapp")) {
282 setPasswordFromCookie();
285 logger.debug("We need to login again!");
288 case HttpStatus.MOVED_TEMPORARILY_302:
290 logger.debug("Status code 302. Redirected. Probably not logged in");
292 case HttpStatus.INTERNAL_SERVER_ERROR_500:
293 case HttpStatus.SERVICE_UNAVAILABLE_503:
294 throw new HttpResponseException(
295 "Status code " + response.getStatus() + ". Verisure service temporarily down", response);
297 logger.debug("Status code {} body {}", response.getStatus(), content);
303 private <T> @Nullable T getJSONVerisureAPI(String url, Class<T> jsonClass)
304 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
305 logger.debug("HTTP GET: {}", BASEURL + url);
307 ContentResponse response = httpClient.GET(BASEURL + url + "?_=" + System.currentTimeMillis());
308 String content = response.getContentAsString();
309 logTraceWithPattern(response.getStatus(), content);
311 return gson.fromJson(content, jsonClass);
314 private ContentResponse postVerisureAPI(String url, String data, boolean isJSON)
315 throws ExecutionException, InterruptedException, TimeoutException {
316 logger.debug("postVerisureAPI URL: {} Data:{}", url, data);
317 Request request = httpClient.newRequest(url).method(HttpMethod.POST);
319 request.header("content-type", "application/json");
322 request.header("X-CSRF-TOKEN", csrf);
325 request.header("Accept", "application/json");
326 if (!data.equals("empty")) {
327 request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)),
328 "application/x-www-form-urlencoded; charset=UTF-8");
330 logger.debug("Setting cookie with username {} and vid {}", userName, password);
331 request.cookie(new HttpCookie(USER_NAME, userName));
332 request.cookie(new HttpCookie(PASSWORD_NAME, password));
334 logger.debug("HTTP POST Request {}.", request.toString());
335 return request.send();
338 private <T> T postJSONVerisureAPI(String url, String data, Class<T> jsonClass)
339 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException, PostToAPIException {
340 for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
341 ContentResponse response = postVerisureAPI(apiServerInUse + url, data, Boolean.TRUE);
342 logger.debug("HTTP Response ({})", response.getStatus());
343 if (response.getStatus() == HttpStatus.OK_200) {
344 String content = response.getContentAsString();
345 if (content.contains("\"message\":\"Request Failed") && content.contains("503")) {
346 // Maybe Verisure has switched API server in use?
347 logger.debug("Changed API server! Response: {}", content);
348 setApiServerInUse(getNextApiServer());
350 String contentChomped = content.trim();
351 logger.trace("Response body: {}", content);
352 return gson.fromJson(contentChomped, jsonClass);
355 logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
358 throw new PostToAPIException("Failed to POST to API");
361 private int postVerisureAPI(String urlString, String data) {
363 if (urlString.contains("https://mypages")) {
366 url = apiServerInUse + urlString;
369 for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
371 ContentResponse response = postVerisureAPI(url, data, Boolean.FALSE);
372 logger.debug("HTTP Response ({})", response.getStatus());
373 int httpStatus = response.getStatus();
374 if (httpStatus == HttpStatus.OK_200) {
375 String content = response.getContentAsString();
376 if (content.contains("\"message\":\"Request Failed. Code 503 from")) {
377 if (url.contains("https://mypages")) {
379 return HttpStatus.SERVICE_UNAVAILABLE_503;
381 // Maybe Verisure has switched API server in use
382 setApiServerInUse(getNextApiServer());
383 url = apiServerInUse + urlString;
386 logTraceWithPattern(httpStatus, content);
390 logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
392 } catch (ExecutionException | InterruptedException | TimeoutException e) {
393 logger.warn("Failed to send a POST to the API {}", e.getMessage());
396 return HttpStatus.SERVICE_UNAVAILABLE_503;
399 private int setSessionCookieAuthLogin() throws ExecutionException, InterruptedException, TimeoutException {
400 // URL to set status which will give us 2 cookies with username and password used for the session
402 ContentResponse response = httpClient.GET(url);
403 logTraceWithPattern(response.getStatus(), response.getContentAsString());
406 return postVerisureAPI(url, "empty");
409 private boolean getInstallations() {
410 int httpResultCode = 0;
413 httpResultCode = setSessionCookieAuthLogin();
414 } catch (ExecutionException | InterruptedException | TimeoutException e) {
415 logger.warn("Failed to set session cookie {}", e.getMessage());
419 if (httpResultCode == HttpStatus.OK_200) {
420 String url = START_GRAPHQL;
422 String queryQLAccountInstallations = "[{\"operationName\":\"AccountInstallations\",\"variables\":{\"email\":\""
424 + "\"},\"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\"}]";
426 VerisureInstallationsDTO installations = postJSONVerisureAPI(url, queryQLAccountInstallations,
427 VerisureInstallationsDTO.class);
428 logger.debug("Installation: {}", installations.toString());
429 List<Owainstallation> owaInstList = installations.getData().getAccount().getOwainstallations();
430 boolean pinCodesMatchInstallations = true;
431 List<String> pinCodes = null;
432 String pinCode = this.pinCode;
433 if (pinCode != null) {
434 pinCodes = Arrays.asList(pinCode.split(","));
435 if (owaInstList.size() != pinCodes.size()) {
436 logger.debug("Number of installations {} does not match number of pin codes configured {}",
437 owaInstList.size(), pinCodes.size());
438 pinCodesMatchInstallations = false;
441 logger.debug("No pin-code defined for user {}", userName);
444 for (int i = 0; i < owaInstList.size(); i++) {
445 VerisureInstallation vInst = new VerisureInstallation();
446 Owainstallation owaInstallation = owaInstList.get(i);
447 String installationId = owaInstallation.getGiid();
448 if (owaInstallation.getAlias() != null && installationId != null) {
449 vInst.setInstallationId(new BigDecimal(installationId));
450 vInst.setInstallationName(owaInstallation.getAlias());
451 if (pinCode != null && pinCodes != null) {
452 int pinCodeIndex = pinCodesMatchInstallations ? i : 0;
453 vInst.setPinCode(pinCodes.get(pinCodeIndex));
454 logger.debug("Setting configured pincode index[{}] to installation ID {}", pinCodeIndex,
457 verisureInstallations.put(new BigDecimal(installationId), vInst);
459 logger.warn("Failed to get alias and/or giid");
463 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
464 | PostToAPIException e) {
465 logger.warn("Failed to send a POST to the API {}", e.getMessage());
468 logger.warn("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
474 private synchronized boolean logIn() {
476 if (!areWeLoggedIn()) {
477 logger.debug("Attempting to log in to mypages.verisure.com");
478 String url = LOGON_SUF;
479 logger.debug("Login URL: {}", url);
480 int httpStatusCode = postVerisureAPI(url, authstring);
481 if (httpStatusCode != HttpStatus.OK_200) {
482 logger.debug("Failed to login, HTTP status code: {}", httpStatusCode);
489 } catch (ExecutionException | InterruptedException | TimeoutException e) {
490 logger.warn("Failed to login {}", e.getMessage());
495 private <T extends VerisureThingDTO> void notifyListeners(T thing) {
496 deviceStatusListeners.forEach(listener -> {
497 if (listener.getVerisureThingClass().equals(thing.getClass())) {
498 listener.onDeviceStateChanged(thing);
503 private void notifyListenersIfChanged(VerisureThingDTO thing, VerisureInstallation installation, String deviceId) {
504 String normalizedDeviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
505 thing.setDeviceId(normalizedDeviceId);
506 thing.setSiteId(installation.getInstallationId());
507 thing.setSiteName(installation.getInstallationName());
508 VerisureThingDTO oldObj = verisureThings.get(normalizedDeviceId);
509 if (!thing.equals(oldObj)) {
510 verisureThings.put(thing.getDeviceId(), thing);
511 notifyListeners(thing);
513 logger.trace("No need to notify listeners for thing: {}", thing);
517 private void updateStatus() {
518 logger.debug("Update status");
519 verisureInstallations.forEach((installationId, installation) -> {
521 configureInstallationInstance(installation.getInstallationId());
522 int httpResultCode = setSessionCookieAuthLogin();
523 if (httpResultCode == HttpStatus.OK_200) {
524 updateAlarmStatus(installation);
525 updateSmartLockStatus(installation);
526 updateMiceDetectionStatus(installation);
527 updateClimateStatus(installation);
528 updateDoorWindowStatus(installation);
529 updateUserPresenceStatus(installation);
530 updateSmartPlugStatus(installation);
531 updateBroadbandConnectionStatus(installation);
532 updateEventLogStatus(installation);
533 updateGatewayStatus(installation);
535 logger.debug("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
537 } catch (ExecutionException | InterruptedException | TimeoutException e) {
538 logger.debug("Failed to update status {}", e.getMessage());
543 private String createOperationJSON(String operation, VariablesDTO variables, String query) {
544 OperationDTO operationJSON = new OperationDTO();
545 operationJSON.setOperationName(operation);
546 operationJSON.setVariables(variables);
547 operationJSON.setQuery(query);
548 return gson.toJson(Collections.singletonList(operationJSON));
551 private synchronized void updateAlarmStatus(VerisureInstallation installation) {
552 BigDecimal installationId = installation.getInstallationId();
553 String url = START_GRAPHQL;
554 String operation = "ArmState";
555 VariablesDTO variables = new VariablesDTO();
556 variables.setGiid(installationId.toString());
557 String query = "query " + operation
558 + "($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";
560 String queryQLAlarmStatus = createOperationJSON(operation, variables, query);
561 logger.debug("Quering API for alarm status!");
563 VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLAlarmStatus, VerisureAlarmsDTO.class);
564 logger.debug("REST Response ({})", thing);
565 // Set unique deviceID
566 String deviceId = "alarm" + installationId;
567 thing.setDeviceId(deviceId);
568 notifyListenersIfChanged(thing, installation, deviceId);
569 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
570 | PostToAPIException e) {
571 logger.warn("Failed to send a POST to the API {}", e.getMessage());
575 private synchronized void updateSmartLockStatus(VerisureInstallation installation) {
576 BigDecimal installationId = installation.getInstallationId();
577 String url = START_GRAPHQL;
578 String operation = "DoorLock";
579 String query = "query " + operation
580 + "($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";
581 VariablesDTO variables = new VariablesDTO();
582 variables.setGiid(installationId.toString());
583 String queryQLSmartLock = createOperationJSON(operation, variables, query);
584 logger.debug("Quering API for smart lock status");
587 VerisureSmartLocksDTO thing = postJSONVerisureAPI(url, queryQLSmartLock, VerisureSmartLocksDTO.class);
588 logger.debug("REST Response ({})", thing);
589 List<VerisureSmartLocksDTO.Doorlock> doorLockList = thing.getData().getInstallation().getDoorlocks();
590 doorLockList.forEach(doorLock -> {
591 VerisureSmartLocksDTO slThing = new VerisureSmartLocksDTO();
592 VerisureSmartLocksDTO.Installation inst = new VerisureSmartLocksDTO.Installation();
593 inst.setDoorlocks(Collections.singletonList(doorLock));
594 VerisureSmartLocksDTO.Data data = new VerisureSmartLocksDTO.Data();
595 data.setInstallation(inst);
596 slThing.setData(data);
597 // Set unique deviceID
598 String deviceId = doorLock.getDevice().getDeviceLabel();
599 if (deviceId != null) {
601 slThing.setLocation(doorLock.getDevice().getArea());
602 slThing.setDeviceId(deviceId);
603 // Fetch more info from old endpoint
605 VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(),
606 VerisureSmartLockDTO.class);
607 logger.debug("REST Response ({})", smartLockThing);
608 slThing.setSmartLockJSON(smartLockThing);
609 notifyListenersIfChanged(slThing, installation, deviceId);
610 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
611 logger.warn("Failed to query for smartlock status: {}", e.getMessage());
616 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
617 | PostToAPIException e) {
618 logger.warn("Failed to send a POST to the API {}", e.getMessage());
622 private synchronized void updateSmartPlugStatus(VerisureInstallation installation) {
623 BigDecimal installationId = installation.getInstallationId();
624 String url = START_GRAPHQL;
625 String operation = "SmartPlug";
626 VariablesDTO variables = new VariablesDTO();
627 variables.setGiid(installationId.toString());
628 String query = "query " + operation
629 + "($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";
630 String queryQLSmartPlug = createOperationJSON(operation, variables, query);
631 logger.debug("Quering API for smart plug status");
634 VerisureSmartPlugsDTO thing = postJSONVerisureAPI(url, queryQLSmartPlug, VerisureSmartPlugsDTO.class);
635 logger.debug("REST Response ({})", thing);
636 List<VerisureSmartPlugsDTO.Smartplug> smartPlugList = thing.getData().getInstallation().getSmartplugs();
637 smartPlugList.forEach(smartPlug -> {
638 VerisureSmartPlugsDTO spThing = new VerisureSmartPlugsDTO();
639 VerisureSmartPlugsDTO.Installation inst = new VerisureSmartPlugsDTO.Installation();
640 inst.setSmartplugs(Collections.singletonList(smartPlug));
641 VerisureSmartPlugsDTO.Data data = new VerisureSmartPlugsDTO.Data();
642 data.setInstallation(inst);
643 spThing.setData(data);
644 // Set unique deviceID
645 String deviceId = smartPlug.getDevice().getDeviceLabel();
646 if (deviceId != null) {
648 spThing.setLocation(smartPlug.getDevice().getArea());
649 notifyListenersIfChanged(spThing, installation, deviceId);
652 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
653 | PostToAPIException e) {
654 logger.warn("Failed to send a POST to the API {}", e.getMessage());
658 private synchronized void updateClimateStatus(VerisureInstallation installation) {
659 BigDecimal installationId = installation.getInstallationId();
660 String url = START_GRAPHQL;
661 VariablesDTO variables = new VariablesDTO();
662 variables.setGiid(installationId.toString());
663 String operation = "Climate";
664 String query = "query " + operation
665 + "($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";
667 String queryQLClimates = createOperationJSON(operation, variables, query);
668 logger.debug("Quering API for climate status");
671 VerisureClimatesDTO thing = postJSONVerisureAPI(url, queryQLClimates, VerisureClimatesDTO.class);
672 logger.debug("REST Response ({})", thing);
673 List<VerisureClimatesDTO.Climate> climateList = thing.getData().getInstallation().getClimates();
674 climateList.forEach(climate -> {
675 // If thing is Mouse detection device, then skip it, but fetch temperature from it
676 String type = climate.getDevice().getGui().getLabel();
677 if ("MOUSE".equals(type)) {
678 logger.debug("Mouse detection device!");
679 String deviceId = climate.getDevice().getDeviceLabel();
680 if (deviceId != null) {
681 deviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
682 VerisureThingDTO mouseThing = verisureThings.get(deviceId);
683 if (mouseThing != null && mouseThing instanceof VerisureMiceDetectionDTO) {
684 VerisureMiceDetectionDTO miceDetectorThing = (VerisureMiceDetectionDTO) mouseThing;
685 miceDetectorThing.setTemperatureValue(climate.getTemperatureValue());
686 miceDetectorThing.setTemperatureTime(climate.getTemperatureTimestamp());
687 notifyListeners(miceDetectorThing);
688 logger.debug("Found climate thing for a Verisure Mouse Detector");
693 VerisureClimatesDTO cThing = new VerisureClimatesDTO();
694 VerisureClimatesDTO.Installation inst = new VerisureClimatesDTO.Installation();
695 inst.setClimates(Collections.singletonList(climate));
696 VerisureClimatesDTO.Data data = new VerisureClimatesDTO.Data();
697 data.setInstallation(inst);
698 cThing.setData(data);
699 // Set unique deviceID
700 String deviceId = climate.getDevice().getDeviceLabel();
701 if (deviceId != null) {
703 cThing.setLocation(climate.getDevice().getArea());
704 notifyListenersIfChanged(cThing, installation, deviceId);
707 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
708 | PostToAPIException e) {
709 logger.warn("Failed to send a POST to the API {}", e.getMessage());
713 private synchronized void updateDoorWindowStatus(VerisureInstallation installation) {
714 BigDecimal installationId = installation.getInstallationId();
715 String url = START_GRAPHQL;
716 String operation = "DoorWindow";
717 VariablesDTO variables = new VariablesDTO();
718 variables.setGiid(installationId.toString());
719 String query = "query " + operation
720 + "($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";
722 String queryQLDoorWindow = createOperationJSON(operation, variables, query);
723 logger.debug("Quering API for door&window status");
726 VerisureDoorWindowsDTO thing = postJSONVerisureAPI(url, queryQLDoorWindow, VerisureDoorWindowsDTO.class);
727 logger.debug("REST Response ({})", thing);
728 List<VerisureDoorWindowsDTO.DoorWindow> doorWindowList = thing.getData().getInstallation().getDoorWindows();
729 doorWindowList.forEach(doorWindow -> {
730 VerisureDoorWindowsDTO dThing = new VerisureDoorWindowsDTO();
731 VerisureDoorWindowsDTO.Installation inst = new VerisureDoorWindowsDTO.Installation();
732 inst.setDoorWindows(Collections.singletonList(doorWindow));
733 VerisureDoorWindowsDTO.Data data = new VerisureDoorWindowsDTO.Data();
734 data.setInstallation(inst);
735 dThing.setData(data);
736 // Set unique deviceID
737 String deviceId = doorWindow.getDevice().getDeviceLabel();
738 if (deviceId != null) {
740 dThing.setLocation(doorWindow.getDevice().getArea());
741 notifyListenersIfChanged(dThing, installation, deviceId);
744 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
745 | PostToAPIException e) {
746 logger.warn("Failed to send a POST to the API {}", e.getMessage());
750 private synchronized void updateBroadbandConnectionStatus(VerisureInstallation inst) {
751 BigDecimal installationId = inst.getInstallationId();
752 String url = START_GRAPHQL;
753 String operation = "Broadband";
754 VariablesDTO variables = new VariablesDTO();
755 variables.setGiid(installationId.toString());
756 String query = "query " + operation
757 + "($giid: String!) {\n installation(giid: $giid) {\n broadband {\n testDate\n isBroadbandConnected\n __typename\n }\n __typename\n}\n}\n";
759 String queryQLBroadbandConnection = createOperationJSON(operation, variables, query);
760 logger.debug("Quering API for broadband connection status");
763 VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLBroadbandConnection,
764 VerisureBroadbandConnectionsDTO.class);
765 logger.debug("REST Response ({})", thing);
766 // Set unique deviceID
767 String deviceId = "bc" + installationId;
768 notifyListenersIfChanged(thing, inst, deviceId);
769 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
770 | PostToAPIException e) {
771 logger.warn("Failed to send a POST to the API {}", e.getMessage());
775 private synchronized void updateUserPresenceStatus(VerisureInstallation installation) {
776 BigDecimal installationId = installation.getInstallationId();
777 String url = START_GRAPHQL;
778 String operation = "userTrackings";
779 VariablesDTO variables = new VariablesDTO();
780 variables.setGiid(installationId.toString());
781 String query = "query " + operation
782 + "($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";
784 String queryQLUserPresence = createOperationJSON(operation, variables, query);
785 logger.debug("Quering API for user presence status");
788 VerisureUserPresencesDTO thing = postJSONVerisureAPI(url, queryQLUserPresence,
789 VerisureUserPresencesDTO.class);
790 logger.debug("REST Response ({})", thing);
791 List<VerisureUserPresencesDTO.UserTracking> userTrackingList = thing.getData().getInstallation()
793 userTrackingList.forEach(userTracking -> {
794 String localUserTrackingStatus = userTracking.getStatus();
795 if (localUserTrackingStatus != null && localUserTrackingStatus.equals("ACTIVE")) {
796 VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO();
797 VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation();
798 inst.setUserTrackings(Collections.singletonList(userTracking));
799 VerisureUserPresencesDTO.Data data = new VerisureUserPresencesDTO.Data();
800 data.setInstallation(inst);
801 upThing.setData(data);
802 // Set unique deviceID
803 String deviceId = "up" + userTracking.getWebAccount() + installationId;
804 notifyListenersIfChanged(upThing, installation, deviceId);
807 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
808 | PostToAPIException e) {
809 logger.warn("Failed to send a POST to the API {}", e.getMessage());
813 private synchronized void updateMiceDetectionStatus(VerisureInstallation installation) {
814 BigDecimal installationId = installation.getInstallationId();
815 String url = START_GRAPHQL;
816 String operation = "Mouse";
817 VariablesDTO variables = new VariablesDTO();
818 variables.setGiid(installationId.toString());
819 String query = "query " + operation
820 + "($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";
822 String queryQLMiceDetection = createOperationJSON(operation, variables, query);
823 logger.debug("Quering API for mice detection status");
826 VerisureMiceDetectionDTO thing = postJSONVerisureAPI(url, queryQLMiceDetection,
827 VerisureMiceDetectionDTO.class);
828 logger.debug("REST Response ({})", thing);
829 List<VerisureMiceDetectionDTO.Mouse> miceList = thing.getData().getInstallation().getMice();
830 miceList.forEach(mouse -> {
831 VerisureMiceDetectionDTO miceThing = new VerisureMiceDetectionDTO();
832 VerisureMiceDetectionDTO.Installation inst = new VerisureMiceDetectionDTO.Installation();
833 inst.setMice(Collections.singletonList(mouse));
834 VerisureMiceDetectionDTO.Data data = new VerisureMiceDetectionDTO.Data();
835 data.setInstallation(inst);
836 miceThing.setData(data);
837 // Set unique deviceID
838 String deviceId = mouse.getDevice().getDeviceLabel();
839 logger.debug("Mouse id: {} for thing: {}", deviceId, mouse);
840 if (deviceId != null) {
842 miceThing.setLocation(mouse.getDevice().getArea());
843 notifyListenersIfChanged(miceThing, 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 updateEventLogStatus(VerisureInstallation installation) {
853 BigDecimal installationId = installation.getInstallationId();
854 String url = START_GRAPHQL;
855 String operation = "EventLog";
857 int numberOfEvents = this.numberOfEvents;
858 List<String> eventCategories = new ArrayList<>(Arrays.asList("INTRUSION", "FIRE", "SOS", "WATER", "ANIMAL",
859 "TECHNICAL", "WARNING", "ARM", "DISARM", "LOCK", "UNLOCK", "PICTURE", "CLIMATE", "CAMERA_SETTINGS",
860 "DOORWINDOW_STATE_OPENED", "DOORWINDOW_STATE_CLOSED", "USERTRACKING"));
861 VariablesDTO variables = new VariablesDTO();
862 variables.setGiid(installationId.toString());
863 variables.setHideNotifications(true);
864 variables.setOffset(offset);
865 variables.setPagesize(numberOfEvents);
866 variables.setEventCategories(eventCategories);
867 String query = "query " + operation
868 + "($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";
870 String queryQLEventLog = createOperationJSON(operation, variables, query);
871 logger.debug("Quering API for event log status");
874 VerisureEventLogDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureEventLogDTO.class);
875 logger.debug("REST Response ({})", thing);
876 // Set unique deviceID
877 String deviceId = "el" + installationId;
878 notifyListenersIfChanged(thing, installation, deviceId);
879 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
880 | PostToAPIException e) {
881 logger.warn("Failed to send a POST to the API {}", e.getMessage());
885 private synchronized void updateGatewayStatus(VerisureInstallation installation) {
886 BigDecimal installationId = installation.getInstallationId();
887 String url = START_GRAPHQL;
888 String operation = "communicationState";
889 VariablesDTO variables = new VariablesDTO();
890 variables.setGiid(installationId.toString());
892 String query = "query " + operation
893 + "($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}";
895 String queryQLEventLog = createOperationJSON(operation, variables, query);
896 logger.debug("Quering API for gateway status");
899 VerisureGatewayDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureGatewayDTO.class);
900 logger.debug("REST Response ({})", thing);
901 // Set unique deviceID
902 List<CommunicationState> communicationStateList = thing.getData().getInstallation().getCommunicationState();
903 if (!communicationStateList.isEmpty()) {
904 String deviceId = communicationStateList.get(0).getDevice().getDeviceLabel();
905 if (deviceId != null) {
906 notifyListenersIfChanged(thing, installation, deviceId);
909 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
910 | PostToAPIException e) {
911 logger.warn("Failed to send a POST to the API {}", e.getMessage());
915 private final class VerisureInstallation {
916 private @Nullable String installationName;
917 private BigDecimal installationId = BigDecimal.ZERO;
918 private @Nullable String pinCode;
920 public @Nullable String getPinCode() {
924 public void setPinCode(@Nullable String pinCode) {
925 this.pinCode = pinCode;
928 public VerisureInstallation() {
931 public BigDecimal getInstallationId() {
932 return installationId;
935 public @Nullable String getInstallationName() {
936 return installationName;
939 public void setInstallationId(BigDecimal installationId) {
940 this.installationId = installationId;
943 public void setInstallationName(@Nullable String installationName) {
944 this.installationName = installationName;
948 private static class OperationDTO {
950 @SuppressWarnings("unused")
951 private @Nullable String operationName;
952 @SuppressWarnings("unused")
953 private VariablesDTO variables = new VariablesDTO();
954 @SuppressWarnings("unused")
955 private @Nullable String query;
957 public void setOperationName(String operationName) {
958 this.operationName = operationName;
961 public void setVariables(VariablesDTO variables) {
962 this.variables = variables;
965 public void setQuery(String query) {
970 public static class VariablesDTO {
972 @SuppressWarnings("unused")
973 private boolean hideNotifications;
974 @SuppressWarnings("unused")
976 @SuppressWarnings("unused")
977 private int pagesize;
978 @SuppressWarnings("unused")
979 private @Nullable List<String> eventCategories = null;
980 @SuppressWarnings("unused")
981 private @Nullable String giid;
983 public void setHideNotifications(boolean hideNotifications) {
984 this.hideNotifications = hideNotifications;
987 public void setOffset(int offset) {
988 this.offset = offset;
991 public void setPagesize(int pagesize) {
992 this.pagesize = pagesize;
995 public void setEventCategories(List<String> eventCategories) {
996 this.eventCategories = eventCategories;
999 public void setGiid(String giid) {
1004 private class PostToAPIException extends Exception {
1006 private static final long serialVersionUID = 1L;
1008 public PostToAPIException(String message) {