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;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.core.config.discovery.AbstractDiscoveryService;
24 import org.openhab.core.config.discovery.DiscoveryResult;
25 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
26 import org.openhab.core.config.discovery.DiscoveryService;
27 import org.openhab.core.io.net.http.HttpUtil;
28 import org.openhab.core.thing.ThingTypeUID;
29 import org.openhab.core.thing.ThingUID;
30 import org.osgi.service.component.annotations.Component;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import com.google.gson.Gson;
35 import com.google.gson.JsonParseException;
36 import com.google.gson.reflect.TypeToken;
39 * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new hue bridges. It uses the 'NUPnP service
40 * provided by Philips'.
42 * @author Awelkiyar Wehabrebi - Initial contribution
43 * @author Christoph Knauf - Refactorings
44 * @author Andre Fuechsel - make {@link #startScan()} asynchronous
47 @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
48 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
50 private static final String MODEL_NAME_PHILIPS_HUE = "<modelName>Philips hue";
52 protected static final String BRIDGE_INDICATOR = "fffe";
54 private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
56 protected static final String LABEL_PATTERN = "Philips hue (IP)";
58 private static final String DESC_URL_PATTERN = "http://HOST/description.xml";
60 private static final int REQUEST_TIMEOUT = 5000;
62 private static final int DISCOVERY_TIMEOUT = 10;
64 private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
66 private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
68 public HueBridgeNupnpDiscovery() {
69 super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
73 protected void startScan() {
74 scheduler.schedule(this::discoverHueBridges, 0, TimeUnit.SECONDS);
78 * Discover available Hue Bridges and then add them in the discovery inbox
80 private void discoverHueBridges() {
81 for (BridgeJsonParameters bridge : getBridgeList()) {
82 if (isReachableAndValidHueBridge(bridge)) {
83 String host = bridge.getInternalIpAddress();
84 String serialNumber = bridge.getId().substring(0, 6) + bridge.getId().substring(10);
85 ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
86 DiscoveryResult result = DiscoveryResultBuilder.create(uid)
87 .withProperties(buildProperties(host, serialNumber))
88 .withLabel(LABEL_PATTERN.replace("IP", host)).withRepresentationProperty(PROPERTY_SERIAL_NUMBER)
90 thingDiscovered(result);
96 * Builds the bridge properties.
98 * @param host the ip of the bridge
99 * @param serialNumber the id of the bridge
100 * @return the bridge properties
102 private Map<String, Object> buildProperties(String host, String serialNumber) {
103 Map<String, Object> properties = new HashMap<>(2);
104 properties.put(HOST, host);
105 properties.put(PROPERTY_SERIAL_NUMBER, serialNumber);
110 * Checks if the Bridge is a reachable Hue Bridge with a valid id.
112 * @param bridge the {@link BridgeJsonParameters}s
113 * @return true if Bridge is a reachable Hue Bridge with a id containing
114 * BRIDGE_INDICATOR longer then 10
116 private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
117 String host = bridge.getInternalIpAddress();
118 String id = bridge.getId();
121 logger.debug("Bridge not discovered: ip is null");
125 logger.debug("Bridge not discovered: id is null");
128 if (id.length() < 10) {
129 logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
132 if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) {
134 "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
135 id, BRIDGE_INDICATOR);
139 description = doGetRequest(DESC_URL_PATTERN.replace("HOST", host));
140 } catch (IOException e) {
141 logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host);
144 if (!description.contains(MODEL_NAME_PHILIPS_HUE)) {
145 logger.debug("Bridge not discovered: Description does not containing the model name: {}", description);
152 * Use the Philips Hue NUPnP service to find Hue Bridges in local Network.
154 * @return a list of available Hue Bridges
156 private List<BridgeJsonParameters> getBridgeList() {
158 Gson gson = new Gson();
159 String json = doGetRequest(DISCOVERY_URL);
160 List<BridgeJsonParameters> bridgeParameters = gson.fromJson(json,
161 new TypeToken<List<BridgeJsonParameters>>() {
163 return Objects.requireNonNull(bridgeParameters);
164 } catch (IOException e) {
165 logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges");
166 } catch (JsonParseException je) {
167 logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges");
169 return new ArrayList<>();
173 * Introduced in order to enable testing.
176 * @return the http request result as String
177 * @throws IOException if request failed
179 protected String doGetRequest(String url) throws IOException {
180 return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);