]> git.basschouten.com Git - openhab-addons.git/blob
4450a1dd632f204cbb562a839f4bb60528a1618d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.io.hueemulation.internal.rest;
14
15 import java.net.URI;
16 import java.util.stream.Collectors;
17
18 import javax.ws.rs.GET;
19 import javax.ws.rs.Path;
20 import javax.ws.rs.Produces;
21 import javax.ws.rs.core.Context;
22 import javax.ws.rs.core.Response;
23 import javax.ws.rs.core.UriInfo;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.jupnp.UpnpService;
28 import org.jupnp.controlpoint.ControlPoint;
29 import org.jupnp.model.meta.Device;
30 import org.jupnp.model.meta.DeviceDetails;
31 import org.jupnp.model.meta.LocalDevice;
32 import org.jupnp.model.meta.RemoteDevice;
33 import org.jupnp.registry.Registry;
34 import org.jupnp.registry.RegistryListener;
35 import org.openhab.io.hueemulation.internal.ConfigStore;
36 import org.openhab.io.hueemulation.internal.upnp.UpnpServer;
37 import org.osgi.service.component.annotations.Component;
38 import org.osgi.service.component.annotations.Reference;
39 import org.osgi.service.component.annotations.ReferenceCardinality;
40 import org.osgi.service.component.annotations.ReferencePolicyOption;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * This class is used by the status REST API for troubleshoot purposes.
46  * <p>
47  * The UPNP announcement is tested, the /description.xml reachability is checked,
48  * and some statistics are gathered.
49  *
50  * @author David Graeff - Initial contribution
51  */
52 @Component(immediate = false, service = { StatusResource.class }, property = "com.eclipsesource.jaxrs.publish=false")
53 @Path("")
54 @NonNullByDefault
55 public class StatusResource implements RegistryListener {
56     @Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY)
57     protected @Nullable UpnpServer discovery;
58     @Reference
59     protected @NonNullByDefault({}) ConfigStore cs;
60     @Reference
61     protected @NonNullByDefault({}) UpnpService upnpService;
62
63     private enum upnpStatus {
64         service_not_registered,
65         service_registered_but_no_UPnP_traffic_yet,
66         upnp_announcement_thread_not_running,
67         any_device_but_not_this_one_found,
68         success
69     }
70
71     private upnpStatus selfTestUpnpFound = upnpStatus.service_not_registered;
72
73     private final Logger logger = LoggerFactory.getLogger(StatusResource.class);
74
75     /**
76      * This will register to the {@link UpnpService} registry to get notified of new UPNP devices and check all existing
77      * devices if they match our UPNP announcement.
78      */
79     public void startUpnpSelfTest() {
80         Registry registry = upnpService.getRegistry();
81         if (registry == null) {
82             logger.warn("upnp service registry is null!");
83             return;
84         }
85
86         selfTestUpnpFound = upnpStatus.service_registered_but_no_UPnP_traffic_yet;
87
88         for (RemoteDevice device : registry.getRemoteDevices()) {
89             remoteDeviceAdded(registry, device);
90         }
91         for (LocalDevice device : registry.getLocalDevices()) {
92             localDeviceAdded(registry, device);
93         }
94
95         registry.addListener(this);
96
97         ControlPoint controlPoint = upnpService.getControlPoint();
98         if (controlPoint != null) {
99             controlPoint.search();
100         }
101     }
102
103     @GET
104     @Path("status/link")
105     @Produces("text/plain")
106     public Response link(@Context UriInfo uri) {
107         cs.setLinkbutton(!cs.ds.config.linkbutton, true, uri.getQueryParameters().containsKey("v1"));
108         URI newuri = uri.getBaseUri().resolve("/api/status");
109         return Response.seeOther(newuri).build();
110     }
111
112     private static String toYesNo(boolean b) {
113         return b ? "yes" : "no";
114     }
115
116     private static String TD(String s) {
117         return "<td>" + s + "</td>";
118     }
119
120     private static String TR(String s) {
121         return "<tr>" + s + "</tr>";
122     }
123
124     @GET
125     @Path("status")
126     @Produces("text/html")
127     public String getStatus() {
128         UpnpServer localDiscovery = discovery;
129         if (localDiscovery == null) { // Optional service wiring
130             return "UPnP Server service not started!";
131         }
132
133         String format = "<html><body><h1>Self test</h1>" + //
134                 "<p>To access any links you need be in pairing mode!</p>" + //
135                 "<p>Pairing mode: %s (%s) <a href='%s/api/status/link'>Enable</a> | <a href='%s/api/status/link?V1=true'>Enable with bridge V1 emulation</a></p>"
136                 + //
137                 "%d published lights (see <a href='%s/api/testuser/lights'>%s/api/testuser/lights</a>)<br>" + //
138                 "%d published sensors (see <a href='%s/api/testuser/sensors'>%s/api/testuser/sensors</a>)<br>" + //
139                 "<h2>UPnP discovery test</h2>" + //
140                 "<p>%s</p>" + //
141                 "<table style='border:1px solid black'><tr><td>serial no</td><td>name</td></tr>%s</table>" + //
142                 "<h2>Reachability test</h2>" + //
143                 "<table style='border:1px solid black'><tr><td>URL</td><td>Responds?</td><td>Ours?</td></tr>%s</table>"
144                 + //
145                 "<h2>Users</h2><ul>%s</ul></body></html>";
146
147         String users = cs.ds.config.whitelist.entrySet().stream().map(user -> "<li>" + user.getKey() + " <b>"
148                 + user.getValue().name + "</b> <small>" + user.getValue().lastUseDate + "</small>")
149                 .collect(Collectors.joining("\n"));
150
151         String url = "http://" + cs.ds.config.ipaddress + ":" + String.valueOf(localDiscovery.getDefaultport());
152
153         String reachable = localDiscovery.selfTests().stream()
154                 .map(entry -> TR(TD(entry.address) + TD(toYesNo(entry.reachable)) + TD(toYesNo(entry.isOurs))))
155                 .collect(Collectors.joining("\n"));
156
157         Registry registry = upnpService.getRegistry();
158         String upnps;
159         if (registry != null) {
160             upnps = registry.getRemoteDevices().stream().map(device -> getDetails(device))
161                     .map(details -> TR(TD(details.getSerialNumber()) + TD(details.getFriendlyName())))
162                     .collect(Collectors.joining("\n"));
163         } else {
164             upnps = TR(TD("service not available") + TD(""));
165         }
166
167         if (!localDiscovery.upnpAnnouncementThreadRunning()) {
168             selfTestUpnpFound = upnpStatus.upnp_announcement_thread_not_running;
169         }
170
171         return String.format(format, cs.ds.config.linkbutton ? "On" : "Off",
172                 cs.getConfig().temporarilyEmulateV1bridge ? "V1" : "V2", url, url, //
173                 cs.ds.lights.size(), url, url, cs.ds.sensors.size(), url, url, //
174                 selfTestUpnpFound.name().replace('_', ' '), //
175                 upnps, reachable, users);
176     }
177
178     @NonNullByDefault({})
179     @Override
180     public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
181     }
182
183     @NonNullByDefault({})
184     @Override
185     public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception ex) {
186     }
187
188     @NonNullByDefault({})
189     @Override
190     public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
191         if (selfTestUpnpFound == upnpStatus.success) {
192             return;
193         }
194         checkForDevice(getDetails(device));
195     }
196
197     private static DeviceDetails getDetails(@Nullable Device<?, ?, ?> device) {
198         if (device != null) {
199             DeviceDetails details = device.getDetails();
200             if (details != null) {
201                 return details;
202             }
203         }
204         return new DeviceDetails(null, "", null, null, "", null, null, null, null, null);
205     }
206
207     private void checkForDevice(DeviceDetails details) {
208         selfTestUpnpFound = upnpStatus.any_device_but_not_this_one_found;
209         try {
210             if (cs.ds.config.bridgeid.equals(details.getSerialNumber())) {
211                 selfTestUpnpFound = upnpStatus.success;
212             }
213         } catch (Exception e) { // We really don't want the service to fail on any exception
214             logger.warn("upnp service: adding services failed: {}", details.getFriendlyName(), e);
215         }
216     }
217
218     @NonNullByDefault({})
219     @Override
220     public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
221     }
222
223     @NonNullByDefault({})
224     @Override
225     public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
226         UpnpServer localDiscovery = discovery;
227         if (selfTestUpnpFound != upnpStatus.success || localDiscovery == null
228                 || localDiscovery.upnpAnnouncementThreadRunning()) {
229             return;
230         }
231         DeviceDetails details = getDetails(device);
232         String serialNo = details.getSerialNumber();
233         if (cs.ds.config.bridgeid.equals(serialNo)) {
234             selfTestUpnpFound = upnpStatus.any_device_but_not_this_one_found;
235         }
236     }
237
238     @NonNullByDefault({})
239     @Override
240     public void localDeviceAdded(Registry registry, LocalDevice device) {
241         UpnpServer localDiscovery = discovery;
242         if (selfTestUpnpFound == upnpStatus.success || localDiscovery == null
243                 || localDiscovery.upnpAnnouncementThreadRunning()) {
244             return;
245         }
246         checkForDevice(getDetails(device));
247     }
248
249     @NonNullByDefault({})
250     @Override
251     public void localDeviceRemoved(Registry registry, LocalDevice device) {
252     }
253
254     @NonNullByDefault({})
255     @Override
256     public void beforeShutdown(Registry registry) {
257     }
258
259     @Override
260     public void afterShutdown() {
261         selfTestUpnpFound = upnpStatus.service_not_registered;
262     }
263 }