]> git.basschouten.com Git - openhab-addons.git/blob
76f5eb507cfe2c0110a42ebb95959fbb51a74942
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.nuki.internal.dataexchange;
14
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;
21
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServlet;
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpServletResponse;
26
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;
43
44 import com.google.gson.Gson;
45
46 /**
47  * The {@link NukiApiServlet} class is responsible for handling the callbacks from the Nuki Bridge.
48  *
49  * @author Markus Katter - Initial contribution
50  * @contributer Christian Hoefler - Door sensor integration
51  */
52 public class NukiApiServlet extends HttpServlet {
53
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";
58
59     private HttpService httpService;
60     private List<NukiBridgeHandler> nukiBridgeHandlers = new ArrayList<>();
61     private String path;
62     private Gson gson;
63
64     public NukiApiServlet(HttpService httpService) {
65         logger.debug("Instantiating NukiApiServlet({})", httpService);
66         this.httpService = httpService;
67         gson = new Gson();
68     }
69
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());
77             return;
78         }
79         if (nukiBridgeHandlers.isEmpty()) {
80             this.activate();
81         }
82         nukiBridgeHandlers.add(nukiBridgeHandler);
83     }
84
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()) {
91             this.deactivate();
92         }
93     }
94
95     public int countNukiBridgeHandlers() {
96         return nukiBridgeHandlers.size();
97     }
98
99     private void activate() {
100         logger.debug("Activating NukiApiServlet.");
101         path = NukiBindingConstants.CALLBACK_ENDPOINT;
102         Dictionary<String, String> servletParams = new Hashtable<>();
103         try {
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);
108         }
109     }
110
111     private void deactivate() {
112         logger.trace("deactivate()");
113         httpService.unregister(path);
114     }
115
116     @Override
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!")));
127             return;
128         }
129         String nukiId = String.format("%08X", (bridgeApiLockStateRequestDto.getNukiId()));
130         String nukiIdThing;
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)
139                         : null;
140                 if (nukiIdThing != null && nukiIdThing.equals(nukiId)) {
141                     logger.debug("Processing ThingUID[{}] - nukiId[{}]", thing.getUID(), nukiId);
142                     NukiSmartLockHandler nsh = getSmartLockHandler(thing);
143                     if (nsh == null) {
144                         logger.debug("Could not update channels for ThingUID[{}] because Handler is null!",
145                                 thing.getUID());
146                         break;
147                     }
148                     Channel channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK);
149                     if (channel != null) {
150                         State state = bridgeApiLockStateRequestDto.getState() == NukiBindingConstants.LOCK_STATES_LOCKED
151                                 ? OnOffType.ON
152                                 : OnOffType.OFF;
153                         nsh.handleApiServletUpdate(channel.getUID(), state);
154                     }
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);
159                     }
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);
164                     }
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);
169                     }
170                     setHeaders(response);
171                     response.getWriter().println(gson.toJson(new NukiHttpServerStatusResponseDto("OK")));
172                     return;
173                 }
174             }
175         }
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!")));
180     }
181
182     private BridgeApiLockStateRequestDto getBridgeApiLockStateRequestDto(HttpServletRequest request) {
183         logger.trace("getBridgeApiLockStateRequestDto(...)");
184         String requestContent = null;
185         try {
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;
192             } else {
193                 logger.error("Invalid BCB-Request payload data!");
194                 logger.error("requestContent[{}]", requestContent);
195             }
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);
201         }
202         return null;
203     }
204
205     private NukiSmartLockHandler getSmartLockHandler(Thing thing) {
206         logger.trace("getSmartLockHandler(...) from thing[{}]", thing.getUID());
207         NukiSmartLockHandler nsh = (NukiSmartLockHandler) thing.getHandler();
208         if (nsh == null) {
209             logger.debug("Could not get NukiSmartLockHandler for ThingUID[{}]!", thing.getUID());
210             return null;
211         }
212         return nsh;
213     }
214
215     private void setHeaders(HttpServletResponse response) {
216         response.setCharacterEncoding(CHARSET);
217         response.setContentType(APPLICATION_JSON);
218     }
219 }