2 * Copyright (c) 2010-2024 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.annotation.WebServlet;
25 import javax.servlet.http.HttpServlet;
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletResponse;
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;
48 * {@link ShellyEventServlet} implements the WebSocket callback for Gen2 devices
50 * @author Markus Michels - Initial contribution
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);
59 private final ShellyHandlerFactory handlerFactory;
60 private final ShellyThingTable thingTable;
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);
70 protected void deactivate() {
71 logger.debug("ShellyEventServlet: Stopping");
75 * Servlet handler. Shelly1: http request, Shelly2: WebSocket call
78 protected void service(HttpServletRequest request, HttpServletResponse resp)
79 throws ServletException, IOException, IllegalArgumentException {
80 String path = getString(request.getRequestURI()).toLowerCase();
82 if (path.equals(SHELLY2_CALLBACK_URI)) { // Shelly2 WebSocket
83 super.service(request, resp);
87 // Shelly1: http events, URL looks like
88 // <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
89 // <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
90 // <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
91 String deviceName = "";
95 String ipAddress = request.getRemoteAddr();
96 Map<String, String[]> parameters = request.getParameterMap();
97 logger.debug("ShellyEventServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
98 request.getRemotePort(), path, parameters.toString());
99 if (!path.toLowerCase().startsWith(SHELLY1_CALLBACK_URI) || !path.contains("/event/shelly")) {
100 logger.warn("ShellyEventServlet received unknown request: path = {}", path);
104 deviceName = substringBetween(path, "/event/", "/").toLowerCase();
105 if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
106 || path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
107 index = substringAfterLast(path, "/").toLowerCase();
108 type = substringBetween(path, deviceName + "/", "/" + index);
111 type = substringAfterLast(path, "/").toLowerCase();
113 logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
115 Map<String, String> parms = new TreeMap<>();
116 for (Map.Entry<String, String[]> p : parameters.entrySet()) {
117 parms.put(p.getKey(), p.getValue()[0]);
120 handlerFactory.onEvent(ipAddress, deviceName, index, type, parms);
121 } catch (IllegalArgumentException e) {
122 logger.debug("{}: Exception processing callback: path={}; index={}, type={}, parameters={}", deviceName,
123 path, index, type, request.getParameterMap().toString());
125 resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
126 resp.getWriter().write("");
132 * public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
134 * response.getWriter().println("HTTP GET method not implemented.");
138 * WebSocket: register Shelly2RpcSocket class
141 public void configure(@Nullable WebSocketServletFactory factory) {
142 if (factory != null) {
143 factory.getPolicy().setIdleTimeout(15000);
144 factory.setCreator(new Shelly2WebSocketCreator(thingTable));
145 factory.register(Shelly2RpcSocket.class);
149 public static class Shelly2WebSocketCreator implements WebSocketCreator {
150 private final Logger logger = LoggerFactory.getLogger(Shelly2WebSocketCreator.class);
152 private final ShellyThingTable thingTable;
154 public Shelly2WebSocketCreator(ShellyThingTable thingTable) {
155 this.thingTable = thingTable;
159 public Object createWebSocket(@Nullable ServletUpgradeRequest req, @Nullable ServletUpgradeResponse resp) {
160 logger.debug("WebSocket: Create socket from servlet");
161 return new Shelly2RpcSocket(thingTable, true);