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.binding.hue.internal.discovery;
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
16 import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.List;
25 import java.util.concurrent.TimeUnit;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.openhab.core.config.discovery.AbstractDiscoveryService;
29 import org.openhab.core.config.discovery.DiscoveryResult;
30 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
31 import org.openhab.core.config.discovery.DiscoveryService;
32 import org.openhab.core.io.net.http.HttpUtil;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.thing.ThingUID;
35 import org.osgi.service.component.annotations.Component;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import com.google.gson.Gson;
40 import com.google.gson.JsonParseException;
41 import com.google.gson.reflect.TypeToken;
44 * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new hue bridges. It uses the 'NUPnP service
45 * provided by Philips'.
47 * @author Awelkiyar Wehabrebi - Initial contribution
48 * @author Christoph Knauf - Refactorings
49 * @author Andre Fuechsel - make {@link #startScan()} asynchronous
52 @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
53 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
55 private static final String MODEL_NAME_PHILIPS_HUE = "<modelName>Philips hue";
57 protected static final String BRIDGE_INDICATOR = "fffe";
59 private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
61 protected static final String LABEL_PATTERN = "Philips hue (IP)";
63 private static final String DESC_URL_PATTERN = "http://HOST/description.xml";
65 private static final int REQUEST_TIMEOUT = 5000;
67 private static final int DISCOVERY_TIMEOUT = 10;
69 private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
71 private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
73 public HueBridgeNupnpDiscovery() {
74 super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
78 protected void startScan() {
79 scheduler.schedule(this::discoverHueBridges, 0, TimeUnit.SECONDS);
83 * Discover available Hue Bridges and then add them in the discovery inbox
85 private void discoverHueBridges() {
86 for (BridgeJsonParameters bridge : getBridgeList()) {
87 if (isReachableAndValidHueBridge(bridge)) {
88 String host = bridge.getInternalIpAddress();
89 String serialNumber = bridge.getId().substring(0, 6) + bridge.getId().substring(10);
90 ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
91 DiscoveryResult result = DiscoveryResultBuilder.create(uid)
92 .withProperties(buildProperties(host, serialNumber))
93 .withLabel(LABEL_PATTERN.replace("IP", host)).withRepresentationProperty(PROPERTY_SERIAL_NUMBER)
95 thingDiscovered(result);
101 * Builds the bridge properties.
103 * @param host the ip of the bridge
104 * @param serialNumber the id of the bridge
105 * @return the bridge properties
107 private Map<String, Object> buildProperties(String host, String serialNumber) {
108 Map<String, Object> properties = new HashMap<>(2);
109 properties.put(HOST, host);
110 properties.put(PROPERTY_SERIAL_NUMBER, serialNumber);
115 * Checks if the Bridge is a reachable Hue Bridge with a valid id.
117 * @param bridge the {@link BridgeJsonParameters}s
118 * @return true if Bridge is a reachable Hue Bridge with a id containing
119 * BRIDGE_INDICATOR longer then 10
121 private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
122 String host = bridge.getInternalIpAddress();
123 String id = bridge.getId();
126 logger.debug("Bridge not discovered: ip is null");
130 logger.debug("Bridge not discovered: id is null");
133 if (id.length() < 10) {
134 logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
137 if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) {
139 "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
140 id, BRIDGE_INDICATOR);
144 description = doGetRequest(DESC_URL_PATTERN.replace("HOST", host));
145 } catch (IOException e) {
146 logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host);
149 if (!description.contains(MODEL_NAME_PHILIPS_HUE)) {
150 logger.debug("Bridge not discovered: Description does not containing the model name: {}", description);
157 * Use the Philips Hue NUPnP service to find Hue Bridges in local Network.
159 * @return a list of available Hue Bridges
161 private List<BridgeJsonParameters> getBridgeList() {
163 Gson gson = new Gson();
164 String json = doGetRequest(DISCOVERY_URL);
165 return gson.fromJson(json, new TypeToken<List<BridgeJsonParameters>>() {
167 } catch (IOException e) {
168 logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges");
169 } catch (JsonParseException je) {
170 logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges");
172 return new ArrayList<>();
176 * Introduced in order to enable testing.
179 * @return the http request result as String
180 * @throws IOException if request failed
182 protected String doGetRequest(String url) throws IOException {
183 return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);