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(@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);
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 = "";
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);
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);
112 type = substringAfterLast(path, "/").toLowerCase();
114 logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
116 Map<String, String> parms = new TreeMap<>();
117 for (Map.Entry<String, String[]> p : parameters.entrySet()) {
118 parms.put(p.getKey(), p.getValue()[0]);
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());
126 resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
127 resp.getWriter().write("");
133 * public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
135 * response.getWriter().println("HTTP GET method not implemented.");
139 * WebSocket: register Shelly2RpcSocket class
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);
150 public static class Shelly2WebSocketCreator implements WebSocketCreator {
151 private final Logger logger = LoggerFactory.getLogger(Shelly2WebSocketCreator.class);
153 private final ShellyThingTable thingTable;
155 public Shelly2WebSocketCreator(ShellyThingTable thingTable) {
156 this.thingTable = thingTable;
160 public Object createWebSocket(@Nullable ServletUpgradeRequest req, @Nullable ServletUpgradeResponse resp) {
161 logger.debug("WebSocket: Create socket from servlet");
162 return new Shelly2RpcSocket(thingTable, true);