]> git.basschouten.com Git - openhab-addons.git/blob
5adcc716b074f0bcac7435d046257520194b7c50
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.hue.internal.discovery;
14
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
16
17 import java.io.IOException;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Objects;
21 import java.util.concurrent.TimeUnit;
22
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;
35
36 import com.google.gson.Gson;
37 import com.google.gson.JsonParseException;
38 import com.google.gson.reflect.TypeToken;
39
40 /**
41  * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new Hue Bridges. It uses the 'NUPnP service
42  * provided by Philips'.
43  *
44  * @author Awelkiyar Wehabrebi - Initial contribution
45  * @author Christoph Knauf - Refactorings
46  * @author Andre Fuechsel - make {@link #startScan()} asynchronous
47  */
48 @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
49 @NonNullByDefault
50 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
51
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;
59
60     private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
61
62     public HueBridgeNupnpDiscovery() {
63         super(HueBridgeHandler.SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
64     }
65
66     @Override
67     protected void startScan() {
68         scheduler.schedule(this::discoverHueBridges, 0, TimeUnit.SECONDS);
69     }
70
71     /**
72      * Discover available Hue Bridges and then add them in the discovery inbox
73      */
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( //
82                                 HOST, host, //
83                                 Thing.PROPERTY_SERIAL_NUMBER, serialNumber)) //
84                         .withLabel(String.format(LABEL_PATTERN, host)) //
85                         .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
86                         .build();
87                 thingDiscovered(result);
88             }
89         }
90     }
91
92     /**
93      * Checks if the Bridge is a reachable Hue Bridge with a valid id.
94      *
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
98      */
99     private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
100         String host = bridge.getInternalIpAddress();
101         String id = bridge.getId();
102         String description;
103         if (host == null) {
104             logger.debug("Bridge not discovered: ip is null");
105             return false;
106         }
107         if (id == null) {
108             logger.debug("Bridge not discovered: id is null");
109             return false;
110         }
111         if (id.length() < 10) {
112             logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
113             return false;
114         }
115         if (!BRIDGE_INDICATOR.equals(id.substring(6, 10))) {
116             logger.debug(
117                     "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
118                     id, BRIDGE_INDICATOR);
119             return false;
120         }
121         try {
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);
125             return false;
126         }
127         if (!description.contains(MODEL_NAME_PHILIPS_HUE)) {
128             logger.debug("Bridge not discovered: Description does not containing the model name: {}", description);
129             return false;
130         }
131         return true;
132     }
133
134     /**
135      * Use the Philips Hue NUPnP service to find Hue Bridges in local Network.
136      *
137      * @return a list of available Hue Bridges
138      */
139     private List<BridgeJsonParameters> getBridgeList() {
140         try {
141             Gson gson = new Gson();
142             String json = doGetRequest(DISCOVERY_URL);
143             List<BridgeJsonParameters> bridgeParameters = gson.fromJson(json,
144                     new TypeToken<List<BridgeJsonParameters>>() {
145                     }.getType());
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");
151         }
152         return List.of();
153     }
154
155     /**
156      * Introduced in order to enable testing.
157      *
158      * @param url the url
159      * @return the http request result as String
160      * @throws IOException if request failed
161      */
162     protected String doGetRequest(String url) throws IOException {
163         return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);
164     }
165 }