]> git.basschouten.com Git - openhab-addons.git/blob
1d841fa6cdb858702b3e98e1532fe02a6b46b09b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.verisure.internal.handler;
14
15 import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.TimeoutException;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.http.HttpStatus;
28 import org.openhab.binding.verisure.internal.VerisureSession;
29 import org.openhab.binding.verisure.internal.dto.VerisureSmartLockDTO;
30 import org.openhab.binding.verisure.internal.dto.VerisureSmartLockDTO.DoorLockVolumeSettings;
31 import org.openhab.binding.verisure.internal.dto.VerisureSmartLocksDTO;
32 import org.openhab.binding.verisure.internal.dto.VerisureSmartLocksDTO.Doorlock;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.Channel;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingTypeUID;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.UnDefType;
44
45 /**
46  * Handler for the Smart Lock Device thing type that Verisure provides.
47  *
48  * @author Jan Gustafsson - Initial contribution
49  *
50  */
51 @NonNullByDefault
52 public class VerisureSmartLockThingHandler extends VerisureThingHandler<VerisureSmartLocksDTO> {
53
54     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_SMARTLOCK);
55
56     private static final int REFRESH_DELAY_SECONDS = 10;
57
58     public VerisureSmartLockThingHandler(Thing thing) {
59         super(thing);
60     }
61
62     @Override
63     public void handleCommand(ChannelUID channelUID, Command command) {
64         logger.debug("handleCommand, channel: {}, command: {}", channelUID, command);
65         if (command instanceof RefreshType) {
66             super.handleCommand(channelUID, command);
67             return;
68         } else if (channelUID.getId().equals(CHANNEL_SMARTLOCK_STATUS)) {
69             handleSmartLockState(command);
70         } else if (channelUID.getId().equals(CHANNEL_AUTO_RELOCK)) {
71             handleAutoRelock(command);
72         } else if (channelUID.getId().equals(CHANNEL_SMARTLOCK_VOLUME)) {
73             handleSmartLockVolumeAndVoiceLevel(command, true);
74         } else if (channelUID.getId().equals(CHANNEL_SMARTLOCK_VOICE_LEVEL)) {
75             handleSmartLockVolumeAndVoiceLevel(command, false);
76         } else {
77             logger.warn("Unknown command! {}", command);
78             return;
79         }
80         scheduleImmediateRefresh(REFRESH_DELAY_SECONDS);
81     }
82
83     private void handleSmartLockState(Command command) {
84         String deviceId = config.getDeviceId();
85         VerisureSession session = getSession();
86         if (session != null) {
87             VerisureSmartLocksDTO smartLock = session.getVerisureThing(deviceId, getVerisureThingClass());
88             if (smartLock != null) {
89                 BigDecimal installationId = smartLock.getSiteId();
90                 String pinCode = session.getPinCode(installationId);
91                 if (pinCode != null) {
92                     String url = START_GRAPHQL;
93                     String operation;
94                     if (command == OnOffType.OFF) {
95                         operation = "DoorUnlock";
96                     } else if (command == OnOffType.ON) {
97                         operation = "DoorLock";
98                     } else {
99                         logger.debug("Unknown command! {}", command);
100                         return;
101                     }
102
103                     ArrayList<SmartLockDTO> list = new ArrayList<>();
104                     SmartLockDTO smartLockJSON = new SmartLockDTO();
105                     VariablesDTO variables = new VariablesDTO();
106                     InputDTO input = new InputDTO();
107
108                     variables.setDeviceLabel(deviceId);
109                     variables.setGiid(installationId.toString());
110                     input.setCode(pinCode);
111                     variables.setInput(input);
112                     smartLockJSON.setOperationName(operation);
113                     smartLockJSON.setVariables(variables);
114                     String query = "mutation " + operation
115                             + "($giid: String!, $deviceLabel: String!, $input: LockDoorInput!) {\n  " + operation
116                             + "(giid: $giid, deviceLabel: $deviceLabel, input: $input)\n}\n";
117                     smartLockJSON.setQuery(query);
118                     list.add(smartLockJSON);
119
120                     String queryQLSmartLockSetState = gson.toJson(list);
121                     logger.debug("Trying to set SmartLock state to {} with URL {} and data {}", operation, url,
122                             queryQLSmartLockSetState);
123
124                     int httpResultCode = session.sendCommand(url, queryQLSmartLockSetState, installationId);
125                     if (httpResultCode == HttpStatus.OK_200) {
126                         logger.debug("SmartLock status successfully changed!");
127                     } else {
128                         logger.warn("Failed to send command, HTTP result code {}", httpResultCode);
129                     }
130                 } else {
131                     logger.warn("PIN code is not configured! It is mandatory to control SmartLock!");
132                 }
133             }
134         }
135     }
136
137     private void handeAutoRelockResult(String url, String data, BigDecimal installationId, Command command) {
138         logger.debug("Trying to set Auto Relock {} with URL {} and data {}", command.toString(), url, data);
139         VerisureSession session = getSession();
140         if (session != null) {
141             int httpResultCode = session.sendCommand(url, data, installationId);
142             if (httpResultCode == HttpStatus.OK_200) {
143                 logger.debug("AutoRelock successfully changed to {}", command.toString());
144             } else {
145                 logger.warn("Failed to send command, HTTP result code {}", httpResultCode);
146             }
147         }
148     }
149
150     private void handleAutoRelock(Command command) {
151         String deviceId = config.getDeviceId();
152         VerisureSession session = getSession();
153         if (session != null) {
154             VerisureSmartLocksDTO smartLock = session.getVerisureThing(deviceId, getVerisureThingClass());
155             if (smartLock != null) {
156                 BigDecimal installationId = smartLock.getSiteId();
157                 try {
158                     String csrf = session.getCsrfToken(installationId);
159                     StringBuilder sb = new StringBuilder(deviceId);
160                     sb.insert(4, "+");
161                     String data;
162                     String url = SMARTLOCK_AUTORELOCK_COMMAND;
163                     if (command == OnOffType.ON) {
164                         data = "enabledDoorLocks=" + sb.toString()
165                                 + "&doorLockDevices%5B0%5D.autoRelockEnabled=true&_doorLockDevices%5B0%5D.autoRelockEnabled=on&_csrf="
166                                 + csrf;
167                         handeAutoRelockResult(url, data, installationId, command);
168                     } else if (command == OnOffType.OFF) {
169                         data = "enabledDoorLocks=&doorLockDevices%5B0%5D.autoRelockEnabled=true&_doorLockDevices%5B0%5D.autoRelockEnabled=on&_csrf="
170                                 + csrf;
171                         handeAutoRelockResult(url, data, installationId, command);
172                     } else {
173                         logger.warn("Unknown command! {}", command);
174                     }
175                 } catch (ExecutionException | InterruptedException | TimeoutException e) {
176                     logger.debug("Failed to handle auto-relock {}", e.getMessage());
177                 }
178             }
179         }
180     }
181
182     private boolean isSettingAllowed(List<String> allowedSettings, String setting) {
183         return allowedSettings.contains(setting);
184     }
185
186     private void handleSmartLockVolumeAndVoiceLevel(Command command, boolean setVolume) {
187         String deviceId = config.getDeviceId();
188         VerisureSession session = getSession();
189         if (session != null) {
190             VerisureSmartLocksDTO smartLocks = session.getVerisureThing(deviceId, getVerisureThingClass());
191             if (smartLocks != null) {
192                 VerisureSmartLockDTO smartLock = smartLocks.getSmartLockJSON();
193                 if (smartLock != null) {
194                     DoorLockVolumeSettings volumeSettings = smartLock.getDoorLockVolumeSettings();
195                     String volume;
196                     String voiceLevel;
197                     if (setVolume) {
198                         List<String> availableVolumes = volumeSettings.getAvailableVolumes();
199                         if (isSettingAllowed(availableVolumes, command.toString())) {
200                             volume = command.toString();
201                             voiceLevel = volumeSettings.getVoiceLevel();
202                         } else {
203                             logger.warn("Failed to change volume, setting not allowed {}", command.toString());
204                             return;
205                         }
206                     } else {
207                         List<String> availableVoiceLevels = volumeSettings.getAvailableVoiceLevels();
208                         if (isSettingAllowed(availableVoiceLevels, command.toString())) {
209                             volume = volumeSettings.getVolume();
210                             voiceLevel = command.toString();
211                         } else {
212                             logger.warn("Failed to change voice level, setting not allowed {}", command.toString());
213                             return;
214                         }
215                         BigDecimal installationId = smartLocks.getSiteId();
216                         try {
217                             String csrf = session.getCsrfToken(installationId);
218                             String url = SMARTLOCK_VOLUME_COMMAND;
219                             String data = "keypad.volume=MEDIUM&keypad.beepOnKeypress=true&_keypad.beepOnKeypress=on&siren.volume=MEDIUM&voiceDevice.volume=MEDIUM&doorLock.volume="
220                                     + volume + "&doorLock.voiceLevel=" + voiceLevel
221                                     + "&_devices%5B0%5D.on=on&devices%5B1%5D.on=true&_devices%5B1%5D.on=on&devices%5B2%5D.on=true&_devices%5B2%5D.on=on&_devices%5B3%5D.on=on&_keypad.keypadsPlayChime=on&_siren.sirensPlayChime=on&_csrf="
222                                     + csrf;
223                             logger.debug("Trying to set SmartLock volume with URL {} and data {}", url, data);
224                             int httpResultCode = session.sendCommand(url, data, installationId);
225                             if (httpResultCode == HttpStatus.OK_200) {
226                                 logger.debug("SmartLock volume successfully changed!");
227                             } else {
228                                 logger.warn("Failed to send command, HTTP result code {}", httpResultCode);
229                             }
230                         } catch (ExecutionException | InterruptedException | TimeoutException e) {
231                             logger.warn("Failed to get CSRF token {}", e.getMessage());
232                         }
233                     }
234                 }
235             }
236         }
237     }
238
239     @Override
240     public Class<VerisureSmartLocksDTO> getVerisureThingClass() {
241         return VerisureSmartLocksDTO.class;
242     }
243
244     @Override
245     public synchronized void update(VerisureSmartLocksDTO thing) {
246         updateSmartLockState(thing);
247         updateStatus(ThingStatus.ONLINE);
248     }
249
250     private void updateSmartLockState(VerisureSmartLocksDTO smartLocksJSON) {
251         List<Doorlock> doorLockList = smartLocksJSON.getData().getInstallation().getDoorlocks();
252         if (!doorLockList.isEmpty()) {
253             Doorlock doorlock = doorLockList.get(0);
254             String smartLockStatus = doorlock.getCurrentLockState();
255             VerisureSmartLockDTO smartLockJSON = smartLocksJSON.getSmartLockJSON();
256             if (smartLockStatus != null) {
257                 getThing().getChannels().stream().map(Channel::getUID)
258                         .filter(channelUID -> isLinked(channelUID) && !channelUID.getId().equals("timestamp"))
259                         .forEach(channelUID -> {
260                             State state = getValue(channelUID.getId(), doorlock, smartLockStatus, smartLockJSON);
261                             updateState(channelUID, state);
262                         });
263                 updateTimeStamp(doorlock.getEventTime());
264                 updateInstallationChannels(smartLocksJSON);
265             } else {
266                 logger.debug("Smart lock status {} or smartLockJSON {} is null!", smartLockStatus, smartLockJSON);
267             }
268         } else {
269             logger.debug("DoorLock list is empty!");
270         }
271     }
272
273     public State getValue(String channelId, Doorlock doorlock, String smartLockStatus,
274             @Nullable VerisureSmartLockDTO smartLockJSON) {
275         switch (channelId) {
276             case CHANNEL_SMARTLOCK_STATUS:
277                 if ("LOCKED".equals(smartLockStatus)) {
278                     return OnOffType.ON;
279                 } else if ("UNLOCKED".equals(smartLockStatus)) {
280                     return OnOffType.OFF;
281                 } else if ("PENDING".equals(smartLockStatus)) {
282                     // Schedule another refresh.
283                     logger.debug("Issuing another immediate refresh since status is still PENDING ...");
284                     this.scheduleImmediateRefresh(REFRESH_DELAY_SECONDS);
285                 }
286                 break;
287             case CHANNEL_CHANGED_BY_USER:
288                 String user = doorlock.getUserString();
289                 return user != null ? new StringType(user) : UnDefType.NULL;
290             case CHANNEL_CHANGED_VIA:
291                 String method = doorlock.getMethod();
292                 return method != null ? new StringType(method) : UnDefType.NULL;
293             case CHANNEL_MOTOR_JAM:
294                 return OnOffType.from(doorlock.isMotorJam());
295             case CHANNEL_LOCATION:
296                 String location = doorlock.getDevice().getArea();
297                 return location != null ? new StringType(location) : UnDefType.NULL;
298             case CHANNEL_AUTO_RELOCK:
299                 if (smartLockJSON != null) {
300                     return OnOffType.from(smartLockJSON.getAutoRelockEnabled());
301                 } else {
302                     return UnDefType.NULL;
303                 }
304             case CHANNEL_SMARTLOCK_VOLUME:
305                 if (smartLockJSON != null) {
306                     return new StringType(smartLockJSON.getDoorLockVolumeSettings().getVolume());
307                 } else {
308                     return UnDefType.NULL;
309                 }
310             case CHANNEL_SMARTLOCK_VOICE_LEVEL:
311                 if (smartLockJSON != null) {
312                     return new StringType(smartLockJSON.getDoorLockVolumeSettings().getVoiceLevel());
313                 } else {
314                     return UnDefType.NULL;
315                 }
316         }
317         return UnDefType.UNDEF;
318     }
319
320     private static class SmartLockDTO {
321
322         @SuppressWarnings("unused")
323         private @Nullable String operationName;
324         @SuppressWarnings("unused")
325         private VariablesDTO variables = new VariablesDTO();
326         @SuppressWarnings("unused")
327         private @Nullable String query;
328
329         public void setOperationName(String operationName) {
330             this.operationName = operationName;
331         }
332
333         public void setVariables(VariablesDTO variables) {
334             this.variables = variables;
335         }
336
337         public void setQuery(String query) {
338             this.query = query;
339         }
340     }
341
342     private static class VariablesDTO {
343
344         @SuppressWarnings("unused")
345         private @Nullable String giid;
346         @SuppressWarnings("unused")
347         private @Nullable String deviceLabel;
348         @SuppressWarnings("unused")
349         private InputDTO input = new InputDTO();
350
351         public void setGiid(String giid) {
352             this.giid = giid;
353         }
354
355         public void setDeviceLabel(String deviceLabel) {
356             this.deviceLabel = deviceLabel;
357         }
358
359         public void setInput(InputDTO input) {
360             this.input = input;
361         }
362     }
363
364     private static class InputDTO {
365
366         @SuppressWarnings("unused")
367         private @Nullable String code;
368
369         public void setCode(String code) {
370             this.code = code;
371         }
372     }
373
374     @Override
375     public void updateTriggerChannel(String event) {
376         logger.debug("SmartLockThingHandler trigger event {}", event);
377         triggerChannel(CHANNEL_SMARTLOCK_TRIGGER_CHANNEL, event);
378     }
379 }