2 * Copyright (c) 2010-2020 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.nuki.internal.handler;
15 import java.util.concurrent.ScheduledFuture;
16 import java.util.concurrent.TimeUnit;
18 import org.openhab.binding.nuki.internal.NukiBindingConstants;
19 import org.openhab.binding.nuki.internal.converter.LockActionConverter;
20 import org.openhab.binding.nuki.internal.dataexchange.BridgeLockActionResponse;
21 import org.openhab.binding.nuki.internal.dataexchange.BridgeLockStateResponse;
22 import org.openhab.binding.nuki.internal.dataexchange.NukiBaseResponse;
23 import org.openhab.binding.nuki.internal.dataexchange.NukiHttpClient;
24 import org.openhab.core.config.core.Configuration;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.thing.Bridge;
28 import org.openhab.core.thing.Channel;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.ThingStatusInfo;
34 import org.openhab.core.thing.binding.BaseThingHandler;
35 import org.openhab.core.thing.binding.ThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.types.State;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * The {@link NukiSmartLockHandler} is responsible for handling commands, which are
44 * sent to one of the channels.
46 * @author Markus Katter - Initial contribution
47 * @contributer Christian Hoefler - Door sensor integration
49 public class NukiSmartLockHandler extends BaseThingHandler {
51 private final Logger logger = LoggerFactory.getLogger(NukiSmartLockHandler.class);
52 private static final int JOB_INTERVAL = 60;
54 private NukiHttpClient nukiHttpClient;
55 private ScheduledFuture<?> reInitJob;
56 private String nukiId;
57 private boolean unlatch;
59 public NukiSmartLockHandler(Thing thing) {
61 logger.debug("Instantiating NukiSmartLockHandler({})", thing);
65 public void initialize() {
66 logger.debug("initialize() for Smart Lock[{}].", getThing().getUID());
67 Configuration config = getConfig();
68 nukiId = (String) config.get(NukiBindingConstants.CONFIG_NUKI_ID);
69 unlatch = (Boolean) config.get(NukiBindingConstants.CONFIG_UNLATCH);
71 logger.debug("NukiSmartLockHandler[{}] is not initializable, nukiId setting is unset in the configuration!",
73 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "nukiId setting is unset");
75 scheduler.execute(this::initializeHandler);
80 public void dispose() {
81 logger.debug("dispose() for Smart Lock[{}].", getThing().getUID());
85 private void initializeHandler() {
86 logger.debug("initializeHandler() for Smart Lock[{}]", nukiId);
87 Bridge bridge = getBridge();
89 initializeHandler(null, null);
91 initializeHandler(bridge.getHandler(), bridge.getStatus());
95 private void initializeHandler(ThingHandler bridgeHandler, ThingStatus bridgeStatus) {
96 if (bridgeHandler != null && bridgeStatus != null) {
97 if (bridgeStatus == ThingStatus.ONLINE) {
98 nukiHttpClient = ((NukiBridgeHandler) bridgeHandler).getNukiHttpClient();
99 BridgeLockStateResponse bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
100 if (handleResponse(bridgeLockStateResponse, null, null)) {
101 updateStatus(ThingStatus.ONLINE);
102 for (Channel channel : thing.getChannels()) {
103 handleCommand(channel.getUID(), RefreshType.REFRESH);
108 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
112 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
118 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
119 logger.debug("bridgeStatusChanged({}) for Smart Lock[{}].", bridgeStatusInfo, nukiId);
120 scheduler.execute(() -> {
121 Bridge bridge = getBridge();
122 if (bridge == null) {
123 initializeHandler(null, bridgeStatusInfo.getStatus());
125 initializeHandler(bridge.getHandler(), bridgeStatusInfo.getStatus());
131 public void handleCommand(ChannelUID channelUID, Command command) {
132 logger.debug("handleCommand({}, {})", channelUID, command);
134 if (getThing().getStatus() != ThingStatus.ONLINE) {
135 logger.debug("Thing is not ONLINE; command[{}] for channelUID[{}] is ignored", command, channelUID);
139 if (command instanceof RefreshType) {
140 handleCommandRefreshType(channelUID, command);
144 boolean validCmd = true;
145 switch (channelUID.getId()) {
146 case NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK:
147 if (command instanceof OnOffType) {
150 lockAction = (command == OnOffType.OFF ? NukiBindingConstants.LOCK_ACTIONS_UNLATCH
151 : NukiBindingConstants.LOCK_ACTIONS_LOCK);
153 lockAction = (command == OnOffType.OFF ? NukiBindingConstants.LOCK_ACTIONS_UNLOCK
154 : NukiBindingConstants.LOCK_ACTIONS_LOCK);
156 Channel channelLockState = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_STATE);
157 if (channelLockState != null) {
158 updateState(channelLockState.getUID(),
159 new DecimalType(LockActionConverter.getLockStateFor(lockAction)));
161 BridgeLockActionResponse bridgeLockActionResponse = nukiHttpClient.getBridgeLockAction(nukiId,
163 handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString());
168 case NukiBindingConstants.CHANNEL_SMARTLOCK_STATE:
169 if (command instanceof DecimalType) {
171 lockAction = ((DecimalType) command).intValue();
172 lockAction = LockActionConverter.getLockActionFor(lockAction);
173 updateState(channelUID, new DecimalType(LockActionConverter.getLockStateFor(lockAction)));
174 BridgeLockActionResponse bridgeLockActionResponse = nukiHttpClient.getBridgeLockAction(nukiId,
176 handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString());
186 logger.debug("Unexpected command[{}] for channelUID[{}]!", command, channelUID);
190 private void handleCommandRefreshType(ChannelUID channelUID, Command command) {
191 logger.debug("handleCommandRefreshType({}, {})", channelUID, command);
192 BridgeLockStateResponse bridgeLockStateResponse;
193 switch (channelUID.getId()) {
194 case NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK:
195 bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
196 if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
197 int lockState = bridgeLockStateResponse.getState();
199 if (lockState == NukiBindingConstants.LOCK_STATES_LOCKED) {
200 state = OnOffType.ON;
201 } else if (lockState == NukiBindingConstants.LOCK_STATES_UNLOCKED) {
202 state = OnOffType.OFF;
205 "Smart Lock returned lockState[{}]. Intentionally setting possibly wrong value 'OFF' for channel 'smartlockLock'!",
207 state = OnOffType.OFF;
209 updateState(channelUID, state);
212 case NukiBindingConstants.CHANNEL_SMARTLOCK_STATE:
213 bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
214 if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
215 updateState(channelUID, new DecimalType(bridgeLockStateResponse.getState()));
218 case NukiBindingConstants.CHANNEL_SMARTLOCK_LOW_BATTERY:
219 bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
220 if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
221 updateState(channelUID, bridgeLockStateResponse.isBatteryCritical() ? OnOffType.ON : OnOffType.OFF);
224 case NukiBindingConstants.CHANNEL_SMARTLOCK_DOOR_STATE:
225 bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
226 if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
227 updateState(channelUID, new DecimalType(bridgeLockStateResponse.getDoorsensorState()));
231 logger.debug("Command[{}] for channelUID[{}] not implemented!", command, channelUID);
236 private boolean handleResponse(NukiBaseResponse nukiBaseResponse, String channelUID, String command) {
237 if (nukiBaseResponse.getStatus() == 200 && nukiBaseResponse.isSuccess()) {
238 logger.debug("Command[{}] succeeded for channelUID[{}] on nukiId[{}]!", command, channelUID, nukiId);
240 } else if (nukiBaseResponse.getStatus() != 200) {
241 logger.debug("Request to Bridge failed! status[{}] - message[{}]", nukiBaseResponse.getStatus(),
242 nukiBaseResponse.getMessage());
243 } else if (!nukiBaseResponse.isSuccess()) {
245 "Request from Bridge to Smart Lock failed! status[{}] - message[{}] - isSuccess[{}]. Check if Nuki Smart Lock is powered on!",
246 nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage(), nukiBaseResponse.isSuccess());
248 logger.debug("Could not handle command[{}] for channelUID[{}] on nukiId[{}]!", command, channelUID, nukiId);
249 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, nukiBaseResponse.getMessage());
250 Channel channelLock = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK);
251 if (channelLock != null) {
252 updateState(channelLock.getUID(), OnOffType.OFF);
254 Channel channelLockState = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_STATE);
255 if (channelLockState != null) {
256 updateState(channelLockState.getUID(), new DecimalType(NukiBindingConstants.LOCK_STATES_UNDEFINED));
258 Channel channelDoorState = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_DOOR_STATE);
259 if (channelDoorState != null) {
260 updateState(channelDoorState.getUID(), new DecimalType(NukiBindingConstants.DOORSENSOR_STATES_UNKNOWN));
266 private void startReInitJob() {
267 logger.trace("Starting reInitJob with interval of {}secs for Smart Lock[{}].", JOB_INTERVAL, nukiId);
268 if (reInitJob != null) {
269 logger.trace("Already started reInitJob for Smart Lock[{}].", nukiId);
272 reInitJob = scheduler.scheduleWithFixedDelay(this::initializeHandler, JOB_INTERVAL, JOB_INTERVAL,
276 private void stopReInitJob() {
277 logger.trace("Stopping reInitJob for Smart Lock[{}].", nukiId);
278 if (reInitJob != null && !reInitJob.isCancelled()) {
279 logger.trace("Stopped reInitJob for Smart Lock[{}].", nukiId);
280 reInitJob.cancel(true);
285 public void handleApiServletUpdate(ChannelUID channelUID, State newState) {
286 logger.trace("handleApiServletUpdate({}, {})", channelUID, newState);
287 updateState(channelUID, newState);