2 * Copyright (c) 2010-2022 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.shelly.internal.api;
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
18 import java.io.IOException;
19 import java.nio.charset.StandardCharsets;
21 import java.util.TreeMap;
23 import javax.servlet.ServletException;
24 import javax.servlet.http.HttpServlet;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
31 import org.osgi.service.component.annotations.Activate;
32 import org.osgi.service.component.annotations.Component;
33 import org.osgi.service.component.annotations.ConfigurationPolicy;
34 import org.osgi.service.component.annotations.Deactivate;
35 import org.osgi.service.component.annotations.Reference;
36 import org.osgi.service.http.HttpService;
37 import org.osgi.service.http.NamespaceException;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * {@link ShellyEventServlet} implements a servlet. which is called by the Shelly device to signnal events (button,
43 * relay output, sensor data). The binding automatically sets those vent urls on startup (when not disabled in the thing
46 * @author Markus Michels - Initial contribution
49 @Component(service = HttpServlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
50 public class ShellyEventServlet extends HttpServlet {
51 private static final long serialVersionUID = 549582869577534569L;
52 private final Logger logger = LoggerFactory.getLogger(ShellyEventServlet.class);
54 private final HttpService httpService;
55 private final ShellyHandlerFactory handlerFactory;
58 public ShellyEventServlet(@Reference HttpService httpService, @Reference ShellyHandlerFactory handlerFactory,
59 Map<String, Object> config) {
60 this.httpService = httpService;
61 this.handlerFactory = handlerFactory;
63 httpService.registerServlet(SHELLY_CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
64 logger.debug("ShellyEventServlet started at '{}'", SHELLY_CALLBACK_URI);
65 } catch (NamespaceException | ServletException | IllegalArgumentException e) {
66 logger.warn("Could not start CallbackServlet", e);
71 protected void deactivate() {
72 httpService.unregister(SHELLY_CALLBACK_URI);
73 logger.debug("ShellyEventServlet stopped");
77 protected void service(@Nullable HttpServletRequest request, @Nullable HttpServletResponse resp)
78 throws ServletException, IOException, IllegalArgumentException {
80 String deviceName = "";
84 if ((request == null) || (resp == null)) {
85 logger.debug("request or resp must not be null!");
90 path = getString(request.getRequestURI()).toLowerCase();
91 String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
92 if (ipAddress == null) {
93 ipAddress = request.getRemoteAddr();
95 Map<String, String[]> parameters = request.getParameterMap();
96 logger.debug("CallbackServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
97 request.getRemotePort(), path, parameters.toString());
98 if (!path.toLowerCase().startsWith(SHELLY_CALLBACK_URI) || !path.contains("/event/shelly")) {
99 logger.warn("CallbackServlet received unknown request: path = {}", path);
104 // <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
105 // <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
106 // <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
107 deviceName = substringBetween(path, "/event/", "/").toLowerCase();
108 if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
109 || path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
110 index = substringAfterLast(path, "/").toLowerCase();
111 type = substringBetween(path, deviceName + "/", "/" + index);
114 type = substringAfterLast(path, "/").toLowerCase();
116 logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
117 Map<String, String> parms = new TreeMap<>();
119 for (Map.Entry<String, String[]> p : parameters.entrySet()) {
120 parms.put(p.getKey(), p.getValue()[0]);
123 handlerFactory.onEvent(ipAddress, deviceName, index, type, parms);
124 } catch (IllegalArgumentException e) {
125 logger.debug("{}: Exception processing callback: path={}; index={}, type={}, parameters={}", deviceName,
126 path, index, type, request.getParameterMap().toString());
128 resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
129 resp.getWriter().write("");