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