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