]> git.basschouten.com Git - openhab-addons.git/blob
9e19852481f0168c167de6f43be56f60cf74ee4b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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 import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
17
18 import java.io.IOException;
19 import java.util.*;
20 import java.util.concurrent.TimeUnit;
21
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;
33
34 import com.google.gson.Gson;
35 import com.google.gson.JsonParseException;
36 import com.google.gson.reflect.TypeToken;
37
38 /**
39  * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new hue bridges. It uses the 'NUPnP service
40  * provided by Philips'.
41  *
42  * @author Awelkiyar Wehabrebi - Initial contribution
43  * @author Christoph Knauf - Refactorings
44  * @author Andre Fuechsel - make {@link #startScan()} asynchronous
45  */
46 @NonNullByDefault
47 @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
48 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
49
50     private static final String MODEL_NAME_PHILIPS_HUE = "<modelName>Philips hue";
51
52     protected static final String BRIDGE_INDICATOR = "fffe";
53
54     private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
55
56     protected static final String LABEL_PATTERN = "Philips hue (IP)";
57
58     private static final String DESC_URL_PATTERN = "http://HOST/description.xml";
59
60     private static final int REQUEST_TIMEOUT = 5000;
61
62     private static final int DISCOVERY_TIMEOUT = 10;
63
64     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
65
66     private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
67
68     public HueBridgeNupnpDiscovery() {
69         super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
70     }
71
72     @Override
73     protected void startScan() {
74         scheduler.schedule(this::discoverHueBridges, 0, TimeUnit.SECONDS);
75     }
76
77     /**
78      * Discover available Hue Bridges and then add them in the discovery inbox
79      */
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)
89                         .build();
90                 thingDiscovered(result);
91             }
92         }
93     }
94
95     /**
96      * Builds the bridge properties.
97      *
98      * @param host the ip of the bridge
99      * @param serialNumber the id of the bridge
100      * @return the bridge properties
101      */
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);
106         return properties;
107     }
108
109     /**
110      * Checks if the Bridge is a reachable Hue Bridge with a valid id.
111      *
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
115      */
116     private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
117         String host = bridge.getInternalIpAddress();
118         String id = bridge.getId();
119         String description;
120         if (host == null) {
121             logger.debug("Bridge not discovered: ip is null");
122             return false;
123         }
124         if (id == null) {
125             logger.debug("Bridge not discovered: id is null");
126             return false;
127         }
128         if (id.length() < 10) {
129             logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
130             return false;
131         }
132         if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) {
133             logger.debug(
134                     "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
135                     id, BRIDGE_INDICATOR);
136             return false;
137         }
138         try {
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);
142             return false;
143         }
144         if (!description.contains(MODEL_NAME_PHILIPS_HUE)) {
145             logger.debug("Bridge not discovered: Description does not containing the model name: {}", description);
146             return false;
147         }
148         return true;
149     }
150
151     /**
152      * Use the Philips Hue NUPnP service to find Hue Bridges in local Network.
153      *
154      * @return a list of available Hue Bridges
155      */
156     private List<BridgeJsonParameters> getBridgeList() {
157         try {
158             Gson gson = new Gson();
159             String json = doGetRequest(DISCOVERY_URL);
160             List<BridgeJsonParameters> bridgeParameters = gson.fromJson(json,
161                     new TypeToken<List<BridgeJsonParameters>>() {
162                     }.getType());
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");
168         }
169         return new ArrayList<>();
170     }
171
172     /**
173      * Introduced in order to enable testing.
174      *
175      * @param url the url
176      * @return the http request result as String
177      * @throws IOException if request failed
178      */
179     protected String doGetRequest(String url) throws IOException {
180         return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);
181     }
182 }