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() {
120 if (updateStatus()) {
128 } catch (HttpResponseException e) {
129 logger.warn("Failed to do a refresh {}", e.getMessage());
134 public int sendCommand(String url, String data, BigDecimal installationId) {
135 logger.debug("Sending command with URL {} and data {}", url, data);
137 configureInstallationInstance(installationId);
138 int httpResultCode = setSessionCookieAuthLogin();
139 if (httpResultCode == HttpStatus.OK_200) {
140 return postVerisureAPI(url, data);
142 return httpResultCode;
144 } catch (ExecutionException | InterruptedException | TimeoutException e) {
145 logger.debug("Failed to send command {}", e.getMessage());
147 return HttpStatus.BAD_REQUEST_400;
150 public boolean unregisterDeviceStatusListener(
151 DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
152 return deviceStatusListeners.remove(deviceStatusListener);
155 @SuppressWarnings("unchecked")
156 public boolean registerDeviceStatusListener(DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
157 return deviceStatusListeners.add((DeviceStatusListener<VerisureThingDTO>) deviceStatusListener);
160 @SuppressWarnings({ "unchecked" })
161 public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId, Class<T> thingType) {
162 VerisureThingDTO thing = verisureThings.get(deviceId);
163 if (thingType.isInstance(thing)) {
169 public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId) {
170 VerisureThingDTO thing = verisureThings.get(deviceId);
172 @SuppressWarnings("unchecked")
173 T thing2 = (T) thing;
179 public @Nullable VerisureThingHandler<?> getVerisureThinghandler(String deviceId) {
180 VerisureThingHandler<?> thingHandler = verisureHandlers.get(deviceId);
184 public void setVerisureThingHandler(VerisureThingHandler<?> vth, String deviceId) {
185 verisureHandlers.put(deviceId, vth);
188 public void removeVerisureThingHandler(String deviceId) {
189 verisureHandlers.remove(deviceId);
192 public Collection<VerisureThingDTO> getVerisureThings() {
193 return verisureThings.values();
196 public @Nullable String getCsrf() {
200 public @Nullable String getPinCode() {
204 public String getApiServerInUse() {
205 return apiServerInUse;
208 public void setApiServerInUse(String apiServerInUse) {
209 this.apiServerInUse = apiServerInUse;
212 public String getNextApiServer() {
213 apiServerInUseIndex++;
214 if (apiServerInUseIndex > (APISERVERLIST.size() - 1)) {
215 apiServerInUseIndex = 0;
217 return APISERVERLIST.get(apiServerInUseIndex);
220 public void setNumberOfEvents(int numberOfEvents) {
221 this.numberOfEvents = numberOfEvents;
224 public void configureInstallationInstance(BigDecimal installationId)
225 throws ExecutionException, InterruptedException, TimeoutException {
226 csrf = getCsrfToken(installationId);
227 logger.debug("Got CSRF: {}", csrf);
229 String url = SET_INSTALLATION + installationId;
233 public @Nullable String getCsrfToken(BigDecimal installationId)
234 throws ExecutionException, InterruptedException, TimeoutException {
236 String url = SETTINGS + installationId;
238 ContentResponse resp = httpClient.GET(url);
239 html = resp.getContentAsString();
240 logger.trace("url: {} html: {}", url, html);
242 Document htmlDocument = Jsoup.parse(html);
243 Element nameInput = htmlDocument.select("input[name=_csrf]").first();
244 if (nameInput != null) {
245 return nameInput.attr("value");
251 public @Nullable String getPinCode(BigDecimal installationId) {
252 VerisureInstallation inst = verisureInstallations.get(installationId);
254 return inst.getPinCode();
256 logger.debug("Installation is null!");
261 private void setPasswordFromCookie() {
262 CookieStore c = httpClient.getCookieStore();
263 List<HttpCookie> cookies = c.getCookies();
264 final List<HttpCookie> unmodifiableList = List.of(cookies.toArray(new HttpCookie[] {}));
265 unmodifiableList.forEach(cookie -> {
266 logger.trace("Response Cookie: {}", cookie);
267 if (cookie.getName().equals(PASSWORD_NAME)) {
268 password = cookie.getValue();
269 logger.debug("Fetching vid {} from cookie", password);
274 private void logTraceWithPattern(int responseStatus, String content) {
275 if (logger.isTraceEnabled()) {
276 String pattern = "(?m)^\\s*\\r?\\n|\\r?\\n\\s*(?!.*\\r?\\n)";
277 String replacement = "";
278 logger.trace("HTTP Response ({}) Body:{}", responseStatus, content.replaceAll(pattern, replacement));
282 private boolean areWeLoggedIn() throws ExecutionException, InterruptedException, TimeoutException {
283 logger.debug("Checking if we are logged in");
286 ContentResponse response = httpClient.newRequest(url).method(HttpMethod.GET).send();
287 String content = response.getContentAsString();
288 logTraceWithPattern(response.getStatus(), content);
290 switch (response.getStatus()) {
291 case HttpStatus.OK_200:
292 if (content.contains("<link href=\"/newapp")) {
293 setPasswordFromCookie();
296 logger.debug("We need to login again!");
299 case HttpStatus.MOVED_TEMPORARILY_302:
301 logger.debug("Status code 302. Redirected. Probably not logged in");
303 case HttpStatus.INTERNAL_SERVER_ERROR_500:
304 case HttpStatus.SERVICE_UNAVAILABLE_503:
305 throw new HttpResponseException(
306 "Status code " + response.getStatus() + ". Verisure service temporarily down", response);
308 logger.debug("Status code {} body {}", response.getStatus(), content);
314 private <T> @Nullable T getJSONVerisureAPI(String url, Class<T> jsonClass)
315 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
316 logger.debug("HTTP GET: {}", BASEURL + url);
318 ContentResponse response = httpClient.GET(BASEURL + url + "?_=" + System.currentTimeMillis());
319 String content = response.getContentAsString();
320 logTraceWithPattern(response.getStatus(), content);
322 return gson.fromJson(content, jsonClass);
325 private ContentResponse postVerisureAPI(String url, String data, boolean isJSON)
326 throws ExecutionException, InterruptedException, TimeoutException {
327 logger.debug("postVerisureAPI URL: {} Data:{}", url, data);
328 Request request = httpClient.newRequest(url).method(HttpMethod.POST);
330 request.header("content-type", "application/json");
333 request.header("X-CSRF-TOKEN", csrf);
336 request.header("Accept", "application/json");
337 if (!data.equals("empty")) {
338 request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)),
339 "application/x-www-form-urlencoded; charset=UTF-8");
341 logger.debug("Setting cookie with username {} and vid {}", userName, password);
342 request.cookie(new HttpCookie(USER_NAME, userName));
343 request.cookie(new HttpCookie(PASSWORD_NAME, password));
345 logger.debug("HTTP POST Request {}.", request.toString());
346 return request.send();
349 private <T> T postJSONVerisureAPI(String url, String data, Class<T> jsonClass)
350 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException, PostToAPIException {
351 for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
352 ContentResponse response = postVerisureAPI(apiServerInUse + url, data, Boolean.TRUE);
353 logger.debug("HTTP Response ({})", response.getStatus());
354 if (response.getStatus() == HttpStatus.OK_200) {
355 String content = response.getContentAsString();
356 if (content.contains("\"message\":\"Request Failed") && content.contains("503")) {
357 // Maybe Verisure has switched API server in use?
358 logger.debug("Changed API server! Response: {}", content);
359 setApiServerInUse(getNextApiServer());
360 } else if (content.contains("\"message\":\"Request Failed")
361 && content.contains("Invalid session cookie")) {
362 throw new PostToAPIException("Invalid session cookie");
364 String contentChomped = content.trim();
365 logger.trace("Response body: {}", content);
366 return gson.fromJson(contentChomped, jsonClass);
369 logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
372 throw new PostToAPIException("Failed to POST to API");
375 private int postVerisureAPI(String urlString, String data) {
377 if (urlString.contains("https://mypages")) {
380 url = apiServerInUse + urlString;
383 for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
385 ContentResponse response = postVerisureAPI(url, data, Boolean.FALSE);
386 logger.debug("HTTP Response ({})", response.getStatus());
387 int httpStatus = response.getStatus();
388 if (httpStatus == HttpStatus.OK_200) {
389 String content = response.getContentAsString();
390 if (content.contains("\"message\":\"Request Failed. Code 503 from")) {
391 if (url.contains("https://mypages")) {
393 return HttpStatus.SERVICE_UNAVAILABLE_503;
395 // Maybe Verisure has switched API server in use
396 setApiServerInUse(getNextApiServer());
397 url = apiServerInUse + urlString;
400 logTraceWithPattern(httpStatus, content);
404 logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
406 } catch (ExecutionException | InterruptedException | TimeoutException e) {
407 logger.warn("Failed to send a POST to the API {}", e.getMessage());
410 return HttpStatus.SERVICE_UNAVAILABLE_503;
413 private int setSessionCookieAuthLogin() throws ExecutionException, InterruptedException, TimeoutException {
414 // URL to set status which will give us 2 cookies with username and password used for the session
416 ContentResponse response = httpClient.GET(url);
417 logTraceWithPattern(response.getStatus(), response.getContentAsString());
420 return postVerisureAPI(url, "empty");
423 private boolean getInstallations() {
424 int httpResultCode = 0;
427 httpResultCode = setSessionCookieAuthLogin();
428 } catch (ExecutionException | InterruptedException | TimeoutException e) {
429 logger.warn("Failed to set session cookie {}", e.getMessage());
433 if (httpResultCode == HttpStatus.OK_200) {
434 String url = START_GRAPHQL;
436 String queryQLAccountInstallations = "[{\"operationName\":\"AccountInstallations\",\"variables\":{\"email\":\""
438 + "\"},\"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\"}]";
440 VerisureInstallationsDTO installations = postJSONVerisureAPI(url, queryQLAccountInstallations,
441 VerisureInstallationsDTO.class);
442 logger.debug("Installation: {}", installations.toString());
443 List<Owainstallation> owaInstList = installations.getData().getAccount().getOwainstallations();
444 boolean pinCodesMatchInstallations = true;
445 List<String> pinCodes = null;
446 String pinCode = this.pinCode;
447 if (pinCode != null) {
448 pinCodes = Arrays.asList(pinCode.split(","));
449 if (owaInstList.size() != pinCodes.size()) {
450 logger.debug("Number of installations {} does not match number of pin codes configured {}",
451 owaInstList.size(), pinCodes.size());
452 pinCodesMatchInstallations = false;
455 logger.debug("No pin-code defined for user {}", userName);
458 for (int i = 0; i < owaInstList.size(); i++) {
459 VerisureInstallation vInst = new VerisureInstallation();
460 Owainstallation owaInstallation = owaInstList.get(i);
461 String installationId = owaInstallation.getGiid();
462 if (owaInstallation.getAlias() != null && installationId != null) {
463 vInst.setInstallationId(new BigDecimal(installationId));
464 vInst.setInstallationName(owaInstallation.getAlias());
465 if (pinCode != null && pinCodes != null) {
466 int pinCodeIndex = pinCodesMatchInstallations ? i : 0;
467 vInst.setPinCode(pinCodes.get(pinCodeIndex));
468 logger.debug("Setting configured pincode index[{}] to installation ID {}", pinCodeIndex,
471 verisureInstallations.put(new BigDecimal(installationId), vInst);
473 logger.warn("Failed to get alias and/or giid");
477 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
478 | PostToAPIException e) {
479 logger.warn("Failed to send a POST to the API {}", e.getMessage());
482 logger.warn("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
488 private synchronized boolean logIn() {
490 if (!areWeLoggedIn()) {
491 logger.debug("Attempting to log in to mypages.verisure.com");
492 String url = LOGON_SUF;
493 logger.debug("Login URL: {}", url);
494 int httpStatusCode = postVerisureAPI(url, authstring);
495 if (httpStatusCode != HttpStatus.OK_200) {
496 logger.debug("Failed to login, HTTP status code: {}", httpStatusCode);
503 } catch (ExecutionException | InterruptedException | TimeoutException e) {
504 logger.warn("Failed to login {}", e.getMessage());
509 private <T extends VerisureThingDTO> void notifyListeners(T thing) {
510 deviceStatusListeners.forEach(listener -> {
511 if (listener.getVerisureThingClass().equals(thing.getClass())) {
512 listener.onDeviceStateChanged(thing);
517 private void notifyListenersIfChanged(VerisureThingDTO thing, VerisureInstallation installation, String deviceId) {
518 String normalizedDeviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
519 thing.setDeviceId(normalizedDeviceId);
520 thing.setSiteId(installation.getInstallationId());
521 thing.setSiteName(installation.getInstallationName());
522 VerisureThingDTO oldObj = verisureThings.get(normalizedDeviceId);
523 if (!thing.equals(oldObj)) {
524 verisureThings.put(thing.getDeviceId(), thing);
525 notifyListeners(thing);
527 logger.trace("No need to notify listeners for thing: {}", thing);
531 private boolean updateStatus() {
532 logger.debug("Update status");
533 for (Map.Entry<BigDecimal, VerisureInstallation> verisureInstallations : verisureInstallations.entrySet()) {
534 VerisureInstallation installation = verisureInstallations.getValue();
536 configureInstallationInstance(installation.getInstallationId());
537 int httpResultCode = setSessionCookieAuthLogin();
538 if (httpResultCode == HttpStatus.OK_200) {
539 updateAlarmStatus(installation);
540 updateSmartLockStatus(installation);
541 updateMiceDetectionStatus(installation);
542 updateClimateStatus(installation);
543 updateDoorWindowStatus(installation);
544 updateUserPresenceStatus(installation);
545 updateSmartPlugStatus(installation);
546 updateBroadbandConnectionStatus(installation);
547 updateEventLogStatus(installation);
548 updateGatewayStatus(installation);
550 logger.debug("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
553 } catch (ExecutionException | InterruptedException | TimeoutException | PostToAPIException e) {
554 logger.debug("Failed to update status {}", e.getMessage());
561 private String createOperationJSON(String operation, VariablesDTO variables, String query) {
562 OperationDTO operationJSON = new OperationDTO();
563 operationJSON.setOperationName(operation);
564 operationJSON.setVariables(variables);
565 operationJSON.setQuery(query);
566 return gson.toJson(Collections.singletonList(operationJSON));
569 private synchronized void updateAlarmStatus(VerisureInstallation installation) throws PostToAPIException {
570 BigDecimal installationId = installation.getInstallationId();
571 String url = START_GRAPHQL;
572 String operation = "ArmState";
573 VariablesDTO variables = new VariablesDTO();
574 variables.setGiid(installationId.toString());
575 String query = "query " + operation
576 + "($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";
578 String queryQLAlarmStatus = createOperationJSON(operation, variables, query);
579 logger.debug("Quering API for alarm status!");
581 VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLAlarmStatus, VerisureAlarmsDTO.class);
582 logger.debug("REST Response ({})", thing);
583 // Set unique deviceID
584 String deviceId = "alarm" + installationId;
585 thing.setDeviceId(deviceId);
586 notifyListenersIfChanged(thing, installation, deviceId);
587 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
588 logger.warn("Failed to send a POST to the API {}", e.getMessage());
592 private synchronized void updateSmartLockStatus(VerisureInstallation installation) {
593 BigDecimal installationId = installation.getInstallationId();
594 String url = START_GRAPHQL;
595 String operation = "DoorLock";
596 String query = "query " + operation
597 + "($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";
598 VariablesDTO variables = new VariablesDTO();
599 variables.setGiid(installationId.toString());
600 String queryQLSmartLock = createOperationJSON(operation, variables, query);
601 logger.debug("Quering API for smart lock status");
604 VerisureSmartLocksDTO thing = postJSONVerisureAPI(url, queryQLSmartLock, VerisureSmartLocksDTO.class);
605 logger.debug("REST Response ({})", thing);
606 List<VerisureSmartLocksDTO.Doorlock> doorLockList = thing.getData().getInstallation().getDoorlocks();
607 doorLockList.forEach(doorLock -> {
608 VerisureSmartLocksDTO slThing = new VerisureSmartLocksDTO();
609 VerisureSmartLocksDTO.Installation inst = new VerisureSmartLocksDTO.Installation();
610 inst.setDoorlocks(Collections.singletonList(doorLock));
611 VerisureSmartLocksDTO.Data data = new VerisureSmartLocksDTO.Data();
612 data.setInstallation(inst);
613 slThing.setData(data);
614 // Set unique deviceID
615 String deviceId = doorLock.getDevice().getDeviceLabel();
616 if (deviceId != null) {
618 slThing.setLocation(doorLock.getDevice().getArea());
619 slThing.setDeviceId(deviceId);
620 // Fetch more info from old endpoint
622 VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(),
623 VerisureSmartLockDTO.class);
624 logger.debug("REST Response ({})", smartLockThing);
625 slThing.setSmartLockJSON(smartLockThing);
626 notifyListenersIfChanged(slThing, installation, deviceId);
627 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
628 logger.warn("Failed to query for smartlock status: {}", e.getMessage());
633 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
634 | PostToAPIException e) {
635 logger.warn("Failed to send a POST to the API {}", e.getMessage());
639 private synchronized void updateSmartPlugStatus(VerisureInstallation installation) {
640 BigDecimal installationId = installation.getInstallationId();
641 String url = START_GRAPHQL;
642 String operation = "SmartPlug";
643 VariablesDTO variables = new VariablesDTO();
644 variables.setGiid(installationId.toString());
645 String query = "query " + operation
646 + "($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";
647 String queryQLSmartPlug = createOperationJSON(operation, variables, query);
648 logger.debug("Quering API for smart plug status");
651 VerisureSmartPlugsDTO thing = postJSONVerisureAPI(url, queryQLSmartPlug, VerisureSmartPlugsDTO.class);
652 logger.debug("REST Response ({})", thing);
653 List<VerisureSmartPlugsDTO.Smartplug> smartPlugList = thing.getData().getInstallation().getSmartplugs();
654 smartPlugList.forEach(smartPlug -> {
655 VerisureSmartPlugsDTO spThing = new VerisureSmartPlugsDTO();
656 VerisureSmartPlugsDTO.Installation inst = new VerisureSmartPlugsDTO.Installation();
657 inst.setSmartplugs(Collections.singletonList(smartPlug));
658 VerisureSmartPlugsDTO.Data data = new VerisureSmartPlugsDTO.Data();
659 data.setInstallation(inst);
660 spThing.setData(data);
661 // Set unique deviceID
662 String deviceId = smartPlug.getDevice().getDeviceLabel();
663 if (deviceId != null) {
665 spThing.setLocation(smartPlug.getDevice().getArea());
666 notifyListenersIfChanged(spThing, installation, deviceId);
669 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
670 | PostToAPIException e) {
671 logger.warn("Failed to send a POST to the API {}", e.getMessage());
675 private @Nullable VerisureBatteryStatusDTO getBatteryStatus(String deviceId,
676 VerisureBatteryStatusDTO @Nullable [] batteryStatus) {
677 if (batteryStatus != null) {
678 for (VerisureBatteryStatusDTO verisureBatteryStatusDTO : batteryStatus) {
679 String id = verisureBatteryStatusDTO.getId();
680 if (id != null && id.equals(deviceId)) {
681 return verisureBatteryStatusDTO;
688 private synchronized void updateClimateStatus(VerisureInstallation installation) {
689 BigDecimal installationId = installation.getInstallationId();
690 String url = START_GRAPHQL;
691 VariablesDTO variables = new VariablesDTO();
692 variables.setGiid(installationId.toString());
693 String operation = "Climate";
694 String query = "query " + operation
695 + "($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";
697 String queryQLClimates = createOperationJSON(operation, variables, query);
698 logger.debug("Quering API for climate status");
701 VerisureClimatesDTO thing = postJSONVerisureAPI(url, queryQLClimates, VerisureClimatesDTO.class);
702 logger.debug("REST Response ({})", thing);
703 List<VerisureClimatesDTO.Climate> climateList = thing.getData().getInstallation().getClimates();
704 if (climateList != null) {
705 climateList.forEach(climate -> {
706 // If thing is Mouse detection device, then skip it, but fetch temperature from it
707 String type = climate.getDevice().getGui().getLabel();
708 if ("MOUSE".equals(type)) {
709 logger.debug("Mouse detection device!");
710 String deviceId = climate.getDevice().getDeviceLabel();
711 if (deviceId != null) {
712 deviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
713 VerisureThingDTO mouseThing = verisureThings.get(deviceId);
714 if (mouseThing != null && mouseThing instanceof VerisureMiceDetectionDTO) {
715 VerisureMiceDetectionDTO miceDetectorThing = (VerisureMiceDetectionDTO) mouseThing;
716 miceDetectorThing.setTemperatureValue(climate.getTemperatureValue());
717 miceDetectorThing.setTemperatureTime(climate.getTemperatureTimestamp());
718 notifyListeners(miceDetectorThing);
719 logger.debug("Found climate thing for a Verisure Mouse Detector");
724 VerisureClimatesDTO cThing = new VerisureClimatesDTO();
725 VerisureClimatesDTO.Installation inst = new VerisureClimatesDTO.Installation();
726 inst.setClimates(Collections.singletonList(climate));
727 VerisureClimatesDTO.Data data = new VerisureClimatesDTO.Data();
728 data.setInstallation(inst);
729 cThing.setData(data);
730 // Set unique deviceID
731 String deviceId = climate.getDevice().getDeviceLabel();
732 if (deviceId != null) {
734 VerisureBatteryStatusDTO[] batteryStatusThingArray = getJSONVerisureAPI(BATTERY_STATUS,
735 VerisureBatteryStatusDTO[].class);
736 VerisureBatteryStatusDTO batteryStatus = getBatteryStatus(deviceId,
737 batteryStatusThingArray);
738 if (batteryStatus != null) {
739 logger.debug("REST Response ({})", batteryStatus);
740 cThing.setBatteryStatus(batteryStatus);
742 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
743 logger.warn("Failed to query for smartlock status: {}", e.getMessage());
746 cThing.setLocation(climate.getDevice().getArea());
747 notifyListenersIfChanged(cThing, installation, deviceId);
751 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
752 | PostToAPIException e) {
753 logger.warn("Failed to send a POST to the API {}", e.getMessage());
757 private synchronized void updateDoorWindowStatus(VerisureInstallation installation) {
758 BigDecimal installationId = installation.getInstallationId();
759 String url = START_GRAPHQL;
760 String operation = "DoorWindow";
761 VariablesDTO variables = new VariablesDTO();
762 variables.setGiid(installationId.toString());
763 String query = "query " + operation
764 + "($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";
766 String queryQLDoorWindow = createOperationJSON(operation, variables, query);
767 logger.debug("Quering API for door&window status");
770 VerisureDoorWindowsDTO thing = postJSONVerisureAPI(url, queryQLDoorWindow, VerisureDoorWindowsDTO.class);
771 logger.debug("REST Response ({})", thing);
772 List<VerisureDoorWindowsDTO.DoorWindow> doorWindowList = thing.getData().getInstallation().getDoorWindows();
773 doorWindowList.forEach(doorWindow -> {
774 VerisureDoorWindowsDTO dThing = new VerisureDoorWindowsDTO();
775 VerisureDoorWindowsDTO.Installation inst = new VerisureDoorWindowsDTO.Installation();
776 inst.setDoorWindows(Collections.singletonList(doorWindow));
777 VerisureDoorWindowsDTO.Data data = new VerisureDoorWindowsDTO.Data();
778 data.setInstallation(inst);
779 dThing.setData(data);
780 // Set unique deviceID
781 String deviceId = doorWindow.getDevice().getDeviceLabel();
782 if (deviceId != null) {
784 VerisureBatteryStatusDTO[] batteryStatusThingArray = getJSONVerisureAPI(BATTERY_STATUS,
785 VerisureBatteryStatusDTO[].class);
786 VerisureBatteryStatusDTO batteryStatus = getBatteryStatus(deviceId, batteryStatusThingArray);
787 if (batteryStatus != null) {
788 logger.debug("REST Response ({})", batteryStatus);
789 dThing.setBatteryStatus(batteryStatus);
791 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
792 logger.warn("Failed to query for smartlock status: {}", e.getMessage());
795 dThing.setLocation(doorWindow.getDevice().getArea());
796 notifyListenersIfChanged(dThing, installation, deviceId);
799 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
800 | PostToAPIException e) {
801 logger.warn("Failed to send a POST to the API {}", e.getMessage());
805 private synchronized void updateBroadbandConnectionStatus(VerisureInstallation inst) {
806 BigDecimal installationId = inst.getInstallationId();
807 String url = START_GRAPHQL;
808 String operation = "Broadband";
809 VariablesDTO variables = new VariablesDTO();
810 variables.setGiid(installationId.toString());
811 String query = "query " + operation
812 + "($giid: String!) {\n installation(giid: $giid) {\n broadband {\n testDate\n isBroadbandConnected\n __typename\n }\n __typename\n}\n}\n";
814 String queryQLBroadbandConnection = createOperationJSON(operation, variables, query);
815 logger.debug("Quering API for broadband connection status");
818 VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLBroadbandConnection,
819 VerisureBroadbandConnectionsDTO.class);
820 logger.debug("REST Response ({})", thing);
821 // Set unique deviceID
822 String deviceId = "bc" + installationId;
823 notifyListenersIfChanged(thing, inst, deviceId);
824 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
825 | PostToAPIException e) {
826 logger.warn("Failed to send a POST to the API {}", e.getMessage());
830 private synchronized void updateUserPresenceStatus(VerisureInstallation installation) {
831 BigDecimal installationId = installation.getInstallationId();
832 String url = START_GRAPHQL;
833 String operation = "userTrackings";
834 VariablesDTO variables = new VariablesDTO();
835 variables.setGiid(installationId.toString());
836 String query = "query " + operation
837 + "($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";
839 String queryQLUserPresence = createOperationJSON(operation, variables, query);
840 logger.debug("Quering API for user presence status");
843 VerisureUserPresencesDTO thing = postJSONVerisureAPI(url, queryQLUserPresence,
844 VerisureUserPresencesDTO.class);
845 logger.debug("REST Response ({})", thing);
846 List<VerisureUserPresencesDTO.UserTracking> userTrackingList = thing.getData().getInstallation()
848 userTrackingList.forEach(userTracking -> {
849 String localUserTrackingStatus = userTracking.getStatus();
850 if (localUserTrackingStatus != null && localUserTrackingStatus.equals("ACTIVE")) {
851 VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO();
852 VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation();
853 inst.setUserTrackings(Collections.singletonList(userTracking));
854 VerisureUserPresencesDTO.Data data = new VerisureUserPresencesDTO.Data();
855 data.setInstallation(inst);
856 upThing.setData(data);
857 // Set unique deviceID
858 String deviceId = "up" + userTracking.getWebAccount() + installationId;
859 notifyListenersIfChanged(upThing, installation, deviceId);
862 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
863 | PostToAPIException e) {
864 logger.warn("Failed to send a POST to the API {}", e.getMessage());
868 private synchronized void updateMiceDetectionStatus(VerisureInstallation installation) {
869 BigDecimal installationId = installation.getInstallationId();
870 String url = START_GRAPHQL;
871 String operation = "Mouse";
872 VariablesDTO variables = new VariablesDTO();
873 variables.setGiid(installationId.toString());
874 String query = "query " + operation
875 + "($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";
877 String queryQLMiceDetection = createOperationJSON(operation, variables, query);
878 logger.debug("Quering API for mice detection status");
881 VerisureMiceDetectionDTO thing = postJSONVerisureAPI(url, queryQLMiceDetection,
882 VerisureMiceDetectionDTO.class);
883 logger.debug("REST Response ({})", thing);
884 List<VerisureMiceDetectionDTO.Mouse> miceList = thing.getData().getInstallation().getMice();
885 miceList.forEach(mouse -> {
886 VerisureMiceDetectionDTO miceThing = new VerisureMiceDetectionDTO();
887 VerisureMiceDetectionDTO.Installation inst = new VerisureMiceDetectionDTO.Installation();
888 inst.setMice(Collections.singletonList(mouse));
889 VerisureMiceDetectionDTO.Data data = new VerisureMiceDetectionDTO.Data();
890 data.setInstallation(inst);
891 miceThing.setData(data);
892 // Set unique deviceID
893 String deviceId = mouse.getDevice().getDeviceLabel();
894 logger.debug("Mouse id: {} for thing: {}", deviceId, mouse);
895 if (deviceId != null) {
897 miceThing.setLocation(mouse.getDevice().getArea());
898 notifyListenersIfChanged(miceThing, installation, deviceId);
901 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
902 | PostToAPIException e) {
903 logger.warn("Failed to send a POST to the API {}", e.getMessage());
907 private synchronized void updateEventLogStatus(VerisureInstallation installation) {
908 BigDecimal installationId = installation.getInstallationId();
909 String url = START_GRAPHQL;
910 String operation = "EventLog";
912 int numberOfEvents = this.numberOfEvents;
913 List<String> eventCategories = new ArrayList<>(Arrays.asList("INTRUSION", "FIRE", "SOS", "WATER", "ANIMAL",
914 "TECHNICAL", "WARNING", "ARM", "DISARM", "LOCK", "UNLOCK", "PICTURE", "CLIMATE", "CAMERA_SETTINGS",
915 "DOORWINDOW_STATE_OPENED", "DOORWINDOW_STATE_CLOSED", "USERTRACKING"));
916 VariablesDTO variables = new VariablesDTO();
917 variables.setGiid(installationId.toString());
918 variables.setHideNotifications(true);
919 variables.setOffset(offset);
920 variables.setPagesize(numberOfEvents);
921 variables.setEventCategories(eventCategories);
922 String query = "query " + operation
923 + "($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";
925 String queryQLEventLog = createOperationJSON(operation, variables, query);
926 logger.debug("Quering API for event log status");
929 VerisureEventLogDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureEventLogDTO.class);
930 logger.debug("REST Response ({})", thing);
931 // Set unique deviceID
932 String deviceId = "el" + installationId;
933 notifyListenersIfChanged(thing, installation, deviceId);
934 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
935 | PostToAPIException e) {
936 logger.warn("Failed to send a POST to the API {}", e.getMessage());
940 private synchronized void updateGatewayStatus(VerisureInstallation installation) {
941 BigDecimal installationId = installation.getInstallationId();
942 String url = START_GRAPHQL;
943 String operation = "communicationState";
944 VariablesDTO variables = new VariablesDTO();
945 variables.setGiid(installationId.toString());
947 String query = "query " + operation
948 + "($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}";
950 String queryQLEventLog = createOperationJSON(operation, variables, query);
951 logger.debug("Quering API for gateway status");
954 VerisureGatewayDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureGatewayDTO.class);
955 logger.debug("REST Response ({})", thing);
956 // Set unique deviceID
957 List<CommunicationState> communicationStateList = thing.getData().getInstallation().getCommunicationState();
958 if (!communicationStateList.isEmpty()) {
959 String deviceId = communicationStateList.get(0).getDevice().getDeviceLabel();
960 if (deviceId != null) {
961 notifyListenersIfChanged(thing, installation, deviceId);
964 } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
965 | PostToAPIException e) {
966 logger.warn("Failed to send a POST to the API {}", e.getMessage());
970 private final class VerisureInstallation {
971 private @Nullable String installationName;
972 private BigDecimal installationId = BigDecimal.ZERO;
973 private @Nullable String pinCode;
975 public @Nullable String getPinCode() {
979 public void setPinCode(@Nullable String pinCode) {
980 this.pinCode = pinCode;
983 public VerisureInstallation() {
986 public BigDecimal getInstallationId() {
987 return installationId;
990 public @Nullable String getInstallationName() {
991 return installationName;
994 public void setInstallationId(BigDecimal installationId) {
995 this.installationId = installationId;
998 public void setInstallationName(@Nullable String installationName) {
999 this.installationName = installationName;
1003 private static class OperationDTO {
1005 @SuppressWarnings("unused")
1006 private @Nullable String operationName;
1007 @SuppressWarnings("unused")
1008 private VariablesDTO variables = new VariablesDTO();
1009 @SuppressWarnings("unused")
1010 private @Nullable String query;
1012 public void setOperationName(String operationName) {
1013 this.operationName = operationName;
1016 public void setVariables(VariablesDTO variables) {
1017 this.variables = variables;
1020 public void setQuery(String query) {
1025 public static class VariablesDTO {
1027 @SuppressWarnings("unused")
1028 private boolean hideNotifications;
1029 @SuppressWarnings("unused")
1031 @SuppressWarnings("unused")
1032 private int pagesize;
1033 @SuppressWarnings("unused")
1034 private @Nullable List<String> eventCategories = null;
1035 @SuppressWarnings("unused")
1036 private @Nullable String giid;
1038 public void setHideNotifications(boolean hideNotifications) {
1039 this.hideNotifications = hideNotifications;
1042 public void setOffset(int offset) {
1043 this.offset = offset;
1046 public void setPagesize(int pagesize) {
1047 this.pagesize = pagesize;
1050 public void setEventCategories(List<String> eventCategories) {
1051 this.eventCategories = eventCategories;
1054 public void setGiid(String giid) {
1059 private class PostToAPIException extends Exception {
1061 private static final long serialVersionUID = 1L;
1063 public PostToAPIException(String message) {