2 * Copyright (c) 2010-2020 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.io.hueemulation.internal.rest;
16 import java.util.stream.Collectors;
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;
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;
45 * This class is used by the status REST API for troubleshoot purposes.
47 * The UPNP announcement is tested, the /description.xml reachability is checked,
48 * and some statistics are gathered.
50 * @author David Graeff - Initial contribution
52 @Component(immediate = false, service = { StatusResource.class }, property = "com.eclipsesource.jaxrs.publish=false")
55 public class StatusResource implements RegistryListener {
56 @Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY)
57 protected @Nullable UpnpServer discovery;
59 protected @NonNullByDefault({}) ConfigStore cs;
61 protected @NonNullByDefault({}) UpnpService upnpService;
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,
71 private upnpStatus selfTestUpnpFound = upnpStatus.service_not_registered;
73 private final Logger logger = LoggerFactory.getLogger(StatusResource.class);
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.
79 public void startUpnpSelfTest() {
80 Registry registry = upnpService.getRegistry();
81 if (registry == null) {
82 logger.warn("upnp service registry is null!");
86 selfTestUpnpFound = upnpStatus.service_registered_but_no_UPnP_traffic_yet;
88 for (RemoteDevice device : registry.getRemoteDevices()) {
89 remoteDeviceAdded(registry, device);
91 for (LocalDevice device : registry.getLocalDevices()) {
92 localDeviceAdded(registry, device);
95 registry.addListener(this);
97 ControlPoint controlPoint = upnpService.getControlPoint();
98 if (controlPoint != null) {
99 controlPoint.search();
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();
112 private static String toYesNo(boolean b) {
113 return b ? "yes" : "no";
116 private static String TD(String s) {
117 return "<td>" + s + "</td>";
120 private static String TR(String s) {
121 return "<tr>" + s + "</tr>";
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!";
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>"
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>" + //
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>"
145 "<h2>Users</h2><ul>%s</ul></body></html>";
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"));
151 String url = "http://" + cs.ds.config.ipaddress + ":" + String.valueOf(localDiscovery.getDefaultport());
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"));
157 Registry registry = upnpService.getRegistry();
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"));
164 upnps = TR(TD("service not available") + TD(""));
167 if (!localDiscovery.upnpAnnouncementThreadRunning()) {
168 selfTestUpnpFound = upnpStatus.upnp_announcement_thread_not_running;
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);
178 @NonNullByDefault({})
180 public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
183 @NonNullByDefault({})
185 public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception ex) {
188 @NonNullByDefault({})
190 public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
191 if (selfTestUpnpFound == upnpStatus.success) {
194 checkForDevice(getDetails(device));
197 private static DeviceDetails getDetails(@Nullable Device<?, ?, ?> device) {
198 if (device != null) {
199 DeviceDetails details = device.getDetails();
200 if (details != null) {
204 return new DeviceDetails(null, "", null, null, "", null, null, null, null, null);
207 private void checkForDevice(DeviceDetails details) {
208 selfTestUpnpFound = upnpStatus.any_device_but_not_this_one_found;
210 if (cs.ds.config.bridgeid.equals(details.getSerialNumber())) {
211 selfTestUpnpFound = upnpStatus.success;
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);
218 @NonNullByDefault({})
220 public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
223 @NonNullByDefault({})
225 public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
226 UpnpServer localDiscovery = discovery;
227 if (selfTestUpnpFound != upnpStatus.success || localDiscovery == null
228 || localDiscovery.upnpAnnouncementThreadRunning()) {
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;
238 @NonNullByDefault({})
240 public void localDeviceAdded(Registry registry, LocalDevice device) {
241 UpnpServer localDiscovery = discovery;
242 if (selfTestUpnpFound == upnpStatus.success || localDiscovery == null
243 || localDiscovery.upnpAnnouncementThreadRunning()) {
246 checkForDevice(getDetails(device));
249 @NonNullByDefault({})
251 public void localDeviceRemoved(Registry registry, LocalDevice device) {
254 @NonNullByDefault({})
256 public void beforeShutdown(Registry registry) {
260 public void afterShutdown() {
261 selfTestUpnpFound = upnpStatus.service_not_registered;