2 * Copyright (c) 2010-2023 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.*;
17 import java.io.IOException;
18 import java.util.List;
20 import java.util.Objects;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
25 import org.openhab.core.config.discovery.AbstractDiscoveryService;
26 import org.openhab.core.config.discovery.DiscoveryResult;
27 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
28 import org.openhab.core.config.discovery.DiscoveryService;
29 import org.openhab.core.io.net.http.HttpUtil;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingUID;
32 import org.osgi.service.component.annotations.Component;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
36 import com.google.gson.Gson;
37 import com.google.gson.JsonParseException;
38 import com.google.gson.reflect.TypeToken;
41 * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new Hue Bridges. It uses the 'NUPnP service
42 * provided by Philips'.
44 * @author Awelkiyar Wehabrebi - Initial contribution
45 * @author Christoph Knauf - Refactorings
46 * @author Andre Fuechsel - make {@link #startScan()} asynchronous
48 @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
50 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
52 private static final String MODEL_NAME_PHILIPS_HUE = "\"name\":\"Philips hue\"";
53 protected static final String BRIDGE_INDICATOR = "fffe";
54 private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
55 protected static final String LABEL_PATTERN = "Philips Hue (%s)";
56 private static final String CONFIG_URL_PATTERN = "http://%s/api/0/config";
57 private static final int REQUEST_TIMEOUT = 5000;
58 private static final int DISCOVERY_TIMEOUT = 10;
60 private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
62 public HueBridgeNupnpDiscovery() {
63 super(HueBridgeHandler.SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
67 protected void startScan() {
68 scheduler.schedule(this::discoverHueBridges, 0, TimeUnit.SECONDS);
72 * Discover available Hue Bridges and then add them in the discovery inbox
74 private void discoverHueBridges() {
75 for (BridgeJsonParameters bridge : getBridgeList()) {
76 if (isReachableAndValidHueBridge(bridge)) {
77 String host = bridge.getInternalIpAddress();
78 String serialNumber = bridge.getId().toLowerCase();
79 ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
80 DiscoveryResult result = DiscoveryResultBuilder.create(uid) //
81 .withProperties(Map.of( //
83 Thing.PROPERTY_SERIAL_NUMBER, serialNumber)) //
84 .withLabel(String.format(LABEL_PATTERN, host)) //
85 .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
87 thingDiscovered(result);
93 * Checks if the Bridge is a reachable Hue Bridge with a valid id.
95 * @param bridge the {@link BridgeJsonParameters}s
96 * @return true if Hue Bridge is a reachable Hue Bridge with an id containing
97 * BRIDGE_INDICATOR longer then 10
99 private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
100 String host = bridge.getInternalIpAddress();
101 String id = bridge.getId();
104 logger.debug("Bridge not discovered: ip is null");
108 logger.debug("Bridge not discovered: id is null");
111 if (id.length() < 10) {
112 logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
115 if (!BRIDGE_INDICATOR.equals(id.substring(6, 10))) {
117 "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
118 id, BRIDGE_INDICATOR);
122 description = doGetRequest(String.format(CONFIG_URL_PATTERN, host));
123 } catch (IOException e) {
124 logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host);
127 if (!description.contains(MODEL_NAME_PHILIPS_HUE)) {
128 logger.debug("Bridge not discovered: Description does not containing the model name: {}", description);
135 * Use the Philips Hue NUPnP service to find Hue Bridges in local Network.
137 * @return a list of available Hue Bridges
139 private List<BridgeJsonParameters> getBridgeList() {
141 Gson gson = new Gson();
142 String json = doGetRequest(DISCOVERY_URL);
143 List<BridgeJsonParameters> bridgeParameters = gson.fromJson(json,
144 new TypeToken<List<BridgeJsonParameters>>() {
146 return Objects.requireNonNull(bridgeParameters);
147 } catch (IOException e) {
148 logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges");
149 } catch (JsonParseException e) {
150 logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges");
156 * Introduced in order to enable testing.
159 * @return the http request result as String
160 * @throws IOException if request failed
162 protected String doGetRequest(String url) throws IOException {
163 return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);