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.nuki.internal.dataexchange;
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.Dictionary;
18 import java.util.Hashtable;
19 import java.util.List;
20 import java.util.stream.Collectors;
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServlet;
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpServletResponse;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.eclipse.jetty.http.HttpStatus;
29 import org.openhab.binding.nuki.internal.NukiBindingConstants;
30 import org.openhab.binding.nuki.internal.dto.BridgeApiLockStateRequestDto;
31 import org.openhab.binding.nuki.internal.dto.NukiHttpServerStatusResponseDto;
32 import org.openhab.binding.nuki.internal.handler.NukiBridgeHandler;
33 import org.openhab.binding.nuki.internal.handler.NukiSmartLockHandler;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.thing.Channel;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.types.State;
39 import org.osgi.service.http.HttpService;
40 import org.osgi.service.http.NamespaceException;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.google.gson.Gson;
47 * The {@link NukiApiServlet} class is responsible for handling the callbacks from the Nuki Bridge.
49 * @author Markus Katter - Initial contribution
50 * @contributer Christian Hoefler - Door sensor integration
52 public class NukiApiServlet extends HttpServlet {
54 private final Logger logger = LoggerFactory.getLogger(NukiApiServlet.class);
55 private static final long serialVersionUID = -3601163473320027239L;
56 private static final String CHARSET = "utf-8";
57 private static final String APPLICATION_JSON = "application/json";
59 private HttpService httpService;
60 private List<NukiBridgeHandler> nukiBridgeHandlers = new ArrayList<>();
64 public NukiApiServlet(HttpService httpService) {
65 logger.debug("Instantiating NukiApiServlet({})", httpService);
66 this.httpService = httpService;
70 public void add(@NonNull NukiBridgeHandler nukiBridgeHandler) {
71 logger.trace("Adding NukiBridgeHandler[{}] for Bridge[{}] to NukiApiServlet.",
72 nukiBridgeHandler.getThing().getUID(),
73 nukiBridgeHandler.getThing().getConfiguration().get(NukiBindingConstants.CONFIG_IP));
74 if (!nukiBridgeHandler.isInitializable()) {
75 logger.debug("NukiBridgeHandler[{}] is not initializable, check required configuration!",
76 nukiBridgeHandler.getThing().getUID());
79 if (nukiBridgeHandlers.isEmpty()) {
82 nukiBridgeHandlers.add(nukiBridgeHandler);
85 public void remove(NukiBridgeHandler nukiBridgeHandler) {
86 logger.trace("Removing NukiBridgeHandler[{}] for Bridge[{}] from NukiApiServlet.",
87 nukiBridgeHandler.getThing().getUID(),
88 nukiBridgeHandler.getThing().getConfiguration().get(NukiBindingConstants.CONFIG_IP));
89 nukiBridgeHandlers.remove(nukiBridgeHandler);
90 if (nukiBridgeHandlers.isEmpty()) {
95 public int countNukiBridgeHandlers() {
96 return nukiBridgeHandlers.size();
99 private void activate() {
100 logger.debug("Activating NukiApiServlet.");
101 path = NukiBindingConstants.CALLBACK_ENDPOINT;
102 Dictionary<String, String> servletParams = new Hashtable<>();
104 httpService.registerServlet(path, this, servletParams, httpService.createDefaultHttpContext());
105 logger.debug("Started NukiApiServlet at path[{}]", path);
106 } catch (ServletException | NamespaceException e) {
107 logger.error("ERROR: {}", e.getMessage(), e);
111 private void deactivate() {
112 logger.trace("deactivate()");
113 httpService.unregister(path);
117 protected void service(HttpServletRequest request, HttpServletResponse response)
118 throws ServletException, IOException {
119 logger.debug("Servlet Request at URI[{}] request[{}]", request.getRequestURI(), request);
120 BridgeApiLockStateRequestDto bridgeApiLockStateRequestDto = getBridgeApiLockStateRequestDto(request);
121 if (bridgeApiLockStateRequestDto == null) {
122 logger.error("Could not handle Bridge CallBack Request - Discarding!");
123 logger.error("Please report a bug, if this request was done by the Nuki Bridge!");
124 setHeaders(response);
125 response.setStatus(HttpStatus.BAD_REQUEST_400);
126 response.getWriter().println(gson.toJson(new NukiHttpServerStatusResponseDto("Invalid BCB-Request!")));
129 String nukiId = String.format("%08X", (bridgeApiLockStateRequestDto.getNukiId()));
131 for (NukiBridgeHandler nukiBridgeHandler : nukiBridgeHandlers) {
132 logger.trace("Searching Bridge[{}] with NukiBridgeHandler[{}] for nukiId[{}].",
133 nukiBridgeHandler.getThing().getConfiguration().get(NukiBindingConstants.CONFIG_IP),
134 nukiBridgeHandler.getThing().getUID(), nukiId);
135 List<@NonNull Thing> allSmartLocks = nukiBridgeHandler.getThing().getThings();
136 for (Thing thing : allSmartLocks) {
137 nukiIdThing = thing.getConfiguration().containsKey(NukiBindingConstants.CONFIG_NUKI_ID)
138 ? (String) thing.getConfiguration().get(NukiBindingConstants.CONFIG_NUKI_ID)
140 if (nukiIdThing != null && nukiIdThing.equals(nukiId)) {
141 logger.debug("Processing ThingUID[{}] - nukiId[{}]", thing.getUID(), nukiId);
142 NukiSmartLockHandler nsh = getSmartLockHandler(thing);
144 logger.debug("Could not update channels for ThingUID[{}] because Handler is null!",
148 Channel channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK);
149 if (channel != null) {
150 State state = bridgeApiLockStateRequestDto.getState() == NukiBindingConstants.LOCK_STATES_LOCKED
153 nsh.handleApiServletUpdate(channel.getUID(), state);
155 channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_STATE);
156 if (channel != null) {
157 State state = new DecimalType(bridgeApiLockStateRequestDto.getState());
158 nsh.handleApiServletUpdate(channel.getUID(), state);
160 channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_LOW_BATTERY);
161 if (channel != null) {
162 State state = bridgeApiLockStateRequestDto.isBatteryCritical() ? OnOffType.ON : OnOffType.OFF;
163 nsh.handleApiServletUpdate(channel.getUID(), state);
165 channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_DOOR_STATE);
166 if (channel != null) {
167 State state = new DecimalType(bridgeApiLockStateRequestDto.getDoorsensorState());
168 nsh.handleApiServletUpdate(channel.getUID(), state);
170 setHeaders(response);
171 response.getWriter().println(gson.toJson(new NukiHttpServerStatusResponseDto("OK")));
176 logger.debug("Smart Lock with nukiId[{}] not found!", nukiId);
177 setHeaders(response);
178 response.setStatus(HttpStatus.NOT_FOUND_404);
179 response.getWriter().println(gson.toJson(new NukiHttpServerStatusResponseDto("Smart Lock not found!")));
182 private BridgeApiLockStateRequestDto getBridgeApiLockStateRequestDto(HttpServletRequest request) {
183 logger.trace("getBridgeApiLockStateRequestDto(...)");
184 String requestContent = null;
186 requestContent = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
187 BridgeApiLockStateRequestDto bridgeApiLockStateRequestDto = gson.fromJson(requestContent,
188 BridgeApiLockStateRequestDto.class);
189 if (bridgeApiLockStateRequestDto.getNukiId() != 0) {
190 logger.trace("requestContent[{}]", requestContent);
191 return bridgeApiLockStateRequestDto;
193 logger.error("Invalid BCB-Request payload data!");
194 logger.error("requestContent[{}]", requestContent);
196 } catch (IOException e) {
197 logger.error("Could not read payload from BCB-Request! Message[{}]", e.getMessage());
198 } catch (Exception e) {
199 logger.error("Could not create BridgeApiLockStateRequestDto from BCB-Request! Message[{}]", e.getMessage());
200 logger.error("requestContent[{}]", requestContent);
205 private NukiSmartLockHandler getSmartLockHandler(Thing thing) {
206 logger.trace("getSmartLockHandler(...) from thing[{}]", thing.getUID());
207 NukiSmartLockHandler nsh = (NukiSmartLockHandler) thing.getHandler();
209 logger.debug("Could not get NukiSmartLockHandler for ThingUID[{}]!", thing.getUID());
215 private void setHeaders(HttpServletResponse response) {
216 response.setCharacterEncoding(CHARSET);
217 response.setContentType(APPLICATION_JSON);