2 * Copyright (c) 2010-2022 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;
24 import java.util.Objects;
26 import java.util.concurrent.TimeUnit;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.openhab.core.config.discovery.AbstractDiscoveryService;
30 import org.openhab.core.config.discovery.DiscoveryResult;
31 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
32 import org.openhab.core.config.discovery.DiscoveryService;
33 import org.openhab.core.io.net.http.HttpUtil;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.ThingUID;
36 import org.osgi.service.component.annotations.Component;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import com.google.gson.Gson;
41 import com.google.gson.JsonParseException;
42 import com.google.gson.reflect.TypeToken;
45 * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new hue bridges. It uses the 'NUPnP service
46 * provided by Philips'.
48 * @author Awelkiyar Wehabrebi - Initial contribution
49 * @author Christoph Knauf - Refactorings
50 * @author Andre Fuechsel - make {@link #startScan()} asynchronous
53 @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
54 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
56 private static final String MODEL_NAME_PHILIPS_HUE = "<modelName>Philips hue";
58 protected static final String BRIDGE_INDICATOR = "fffe";
60 private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
62 protected static final String LABEL_PATTERN = "Philips hue (IP)";
64 private static final String DESC_URL_PATTERN = "http://HOST/description.xml";
66 private static final int REQUEST_TIMEOUT = 5000;
68 private static final int DISCOVERY_TIMEOUT = 10;
70 private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
72 private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
74 public HueBridgeNupnpDiscovery() {
75 super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
79 protected void startScan() {
80 scheduler.schedule(this::discoverHueBridges, 0, TimeUnit.SECONDS);
84 * Discover available Hue Bridges and then add them in the discovery inbox
86 private void discoverHueBridges() {
87 for (BridgeJsonParameters bridge : getBridgeList()) {
88 if (isReachableAndValidHueBridge(bridge)) {
89 String host = bridge.getInternalIpAddress();
90 String serialNumber = bridge.getId().substring(0, 6) + bridge.getId().substring(10);
91 serialNumber = serialNumber.toLowerCase();
92 ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
93 DiscoveryResult result = DiscoveryResultBuilder.create(uid)
94 .withProperties(buildProperties(host, serialNumber))
95 .withLabel(LABEL_PATTERN.replace("IP", host)).withRepresentationProperty(PROPERTY_SERIAL_NUMBER)
97 thingDiscovered(result);
103 * Builds the bridge properties.
105 * @param host the ip of the bridge
106 * @param serialNumber the id of the bridge
107 * @return the bridge properties
109 private Map<String, Object> buildProperties(String host, String serialNumber) {
110 Map<String, Object> properties = new HashMap<>(2);
111 properties.put(HOST, host);
112 properties.put(PROPERTY_SERIAL_NUMBER, serialNumber);
117 * Checks if the Bridge is a reachable Hue Bridge with a valid id.
119 * @param bridge the {@link BridgeJsonParameters}s
120 * @return true if Bridge is a reachable Hue Bridge with a id containing
121 * BRIDGE_INDICATOR longer then 10
123 private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
124 String host = bridge.getInternalIpAddress();
125 String id = bridge.getId();
128 logger.debug("Bridge not discovered: ip is null");
132 logger.debug("Bridge not discovered: id is null");
135 if (id.length() < 10) {
136 logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
139 if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) {
141 "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
142 id, BRIDGE_INDICATOR);
146 description = doGetRequest(DESC_URL_PATTERN.replace("HOST", host));
147 } catch (IOException e) {
148 logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host);
151 if (!description.contains(MODEL_NAME_PHILIPS_HUE)) {
152 logger.debug("Bridge not discovered: Description does not containing the model name: {}", description);
159 * Use the Philips Hue NUPnP service to find Hue Bridges in local Network.
161 * @return a list of available Hue Bridges
163 private List<BridgeJsonParameters> getBridgeList() {
165 Gson gson = new Gson();
166 String json = doGetRequest(DISCOVERY_URL);
167 List<BridgeJsonParameters> bridgeParameters = gson.fromJson(json,
168 new TypeToken<List<BridgeJsonParameters>>() {
170 return Objects.requireNonNull(bridgeParameters);
171 } catch (IOException e) {
172 logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges");
173 } catch (JsonParseException je) {
174 logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges");
176 return new ArrayList<>();
180 * Introduced in order to enable testing.
183 * @return the http request result as String
184 * @throws IOException if request failed
186 protected String doGetRequest(String url) throws IOException {
187 return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);