]> git.basschouten.com Git - openhab-addons.git/blob
9e3609e2d488facbcbccd5448f27ac6c51b21067
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.shelly.internal.api;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
17
18 import java.io.IOException;
19 import java.nio.charset.StandardCharsets;
20 import java.util.Map;
21 import java.util.TreeMap;
22
23 import javax.servlet.ServletException;
24 import javax.servlet.annotation.WebServlet;
25 import javax.servlet.http.HttpServlet;
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletResponse;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
32 import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
33 import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
34 import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
35 import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
36 import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
37 import org.openhab.binding.shelly.internal.api2.Shelly2RpcSocket;
38 import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
39 import org.osgi.service.component.annotations.Activate;
40 import org.osgi.service.component.annotations.Component;
41 import org.osgi.service.component.annotations.ConfigurationPolicy;
42 import org.osgi.service.component.annotations.Deactivate;
43 import org.osgi.service.component.annotations.Reference;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * {@link ShellyEventServlet} implements the WebSocket callback for Gen2 devices
49  *
50  * @author Markus Michels - Initial contribution
51  */
52 @NonNullByDefault
53 @WebServlet(name = "ShellyEventServlet", urlPatterns = { SHELLY1_CALLBACK_URI, SHELLY2_CALLBACK_URI })
54 @Component(service = HttpServlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
55 public class ShellyEventServlet extends WebSocketServlet {
56     private static final long serialVersionUID = -1210354558091063207L;
57     private final Logger logger = LoggerFactory.getLogger(ShellyEventServlet.class);
58
59     private final ShellyHandlerFactory handlerFactory;
60     private final ShellyThingTable thingTable;
61
62     @Activate
63     public ShellyEventServlet(@Reference ShellyHandlerFactory handlerFactory, @Reference ShellyThingTable thingTable) {
64         this.handlerFactory = handlerFactory;
65         this.thingTable = thingTable;
66         logger.debug("Shelly EventServlet started at {} and {}", SHELLY1_CALLBACK_URI, SHELLY2_CALLBACK_URI);
67     }
68
69     @Deactivate
70     protected void deactivate() {
71         logger.debug("ShellyEventServlet: Stopping");
72     }
73
74     /**
75      * Servlet handler. Shelly1: http request, Shelly2: WebSocket call
76      */
77     @Override
78     protected void service(@Nullable HttpServletRequest request, @Nullable HttpServletResponse resp)
79             throws ServletException, IOException, IllegalArgumentException {
80         String path = getString(request.getRequestURI()).toLowerCase();
81         if (path.equals(SHELLY2_CALLBACK_URI)) { // Shelly2 WebSocket
82             if (request != null && resp != null) {
83                 super.service(request, resp);
84             }
85             return;
86         }
87
88         // Shelly1: http events, URL looks like
89         // <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
90         // <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
91         // <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
92         String deviceName = "";
93         String index = "";
94         String type = "";
95         try {
96             String ipAddress = request.getRemoteAddr();
97             Map<String, String[]> parameters = request.getParameterMap();
98             logger.debug("ShellyEventServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
99                     request.getRemotePort(), path, parameters.toString());
100             if (!path.toLowerCase().startsWith(SHELLY1_CALLBACK_URI) || !path.contains("/event/shelly")) {
101                 logger.warn("ShellyEventServlet received unknown request: path = {}", path);
102                 return;
103             }
104
105             deviceName = substringBetween(path, "/event/", "/").toLowerCase();
106             if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
107                     || path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
108                 index = substringAfterLast(path, "/").toLowerCase();
109                 type = substringBetween(path, deviceName + "/", "/" + index);
110             } else {
111                 index = "";
112                 type = substringAfterLast(path, "/").toLowerCase();
113             }
114             logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
115
116             Map<String, String> parms = new TreeMap<>();
117             for (Map.Entry<String, String[]> p : parameters.entrySet()) {
118                 parms.put(p.getKey(), p.getValue()[0]);
119
120             }
121             handlerFactory.onEvent(ipAddress, deviceName, index, type, parms);
122         } catch (IllegalArgumentException e) {
123             logger.debug("{}: Exception processing callback: path={}; index={}, type={}, parameters={}", deviceName,
124                     path, index, type, request.getParameterMap().toString());
125         } finally {
126             resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
127             resp.getWriter().write("");
128         }
129     }
130
131     /*
132      * @Override
133      * public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
134      * {
135      * response.getWriter().println("HTTP GET method not implemented.");
136      * }
137      */
138     /**
139      * WebSocket: register Shelly2RpcSocket class
140      */
141     @Override
142     public void configure(@Nullable WebSocketServletFactory factory) {
143         if (factory != null) {
144             factory.getPolicy().setIdleTimeout(15000);
145             factory.setCreator(new Shelly2WebSocketCreator(thingTable));
146             factory.register(Shelly2RpcSocket.class);
147         }
148     }
149
150     public static class Shelly2WebSocketCreator implements WebSocketCreator {
151         private final Logger logger = LoggerFactory.getLogger(Shelly2WebSocketCreator.class);
152
153         private final ShellyThingTable thingTable;
154
155         public Shelly2WebSocketCreator(ShellyThingTable thingTable) {
156             this.thingTable = thingTable;
157         }
158
159         @Override
160         public Object createWebSocket(@Nullable ServletUpgradeRequest req, @Nullable ServletUpgradeResponse resp) {
161             logger.debug("WebSocket: Create socket from servlet");
162             return new Shelly2RpcSocket(thingTable, true);
163         }
164     }
165 }