]> git.basschouten.com Git - openhab-addons.git/blob
860e732b000c2a239e4c601e86b7e2e73f34d050
[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.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.Set;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.openhab.core.config.discovery.AbstractDiscoveryService;
29 import org.openhab.core.config.discovery.DiscoveryResult;
30 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
31 import org.openhab.core.config.discovery.DiscoveryService;
32 import org.openhab.core.io.net.http.HttpUtil;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.thing.ThingUID;
35 import org.osgi.service.component.annotations.Component;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import com.google.gson.Gson;
40 import com.google.gson.JsonParseException;
41 import com.google.gson.reflect.TypeToken;
42
43 /**
44  * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new hue bridges. It uses the 'NUPnP service
45  * provided by Philips'.
46  *
47  * @author Awelkiyar Wehabrebi - Initial contribution
48  * @author Christoph Knauf - Refactorings
49  * @author Andre Fuechsel - make {@link #startScan()} asynchronous
50  */
51 @NonNullByDefault
52 @Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.hue")
53 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
54
55     private static final String MODEL_NAME_PHILIPS_HUE = "<modelName>Philips hue";
56
57     protected static final String BRIDGE_INDICATOR = "fffe";
58
59     private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
60
61     protected static final String LABEL_PATTERN = "Philips hue (IP)";
62
63     private static final String DESC_URL_PATTERN = "http://HOST/description.xml";
64
65     private static final int REQUEST_TIMEOUT = 5000;
66
67     private static final int DISCOVERY_TIMEOUT = 10;
68
69     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
70
71     private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
72
73     public HueBridgeNupnpDiscovery() {
74         super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
75     }
76
77     @Override
78     protected void startScan() {
79         scheduler.schedule(this::discoverHueBridges, 0, TimeUnit.SECONDS);
80     }
81
82     /**
83      * Discover available Hue Bridges and then add them in the discovery inbox
84      */
85     private void discoverHueBridges() {
86         for (BridgeJsonParameters bridge : getBridgeList()) {
87             if (isReachableAndValidHueBridge(bridge)) {
88                 String host = bridge.getInternalIpAddress();
89                 String serialNumber = bridge.getId().substring(0, 6) + bridge.getId().substring(10);
90                 ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
91                 DiscoveryResult result = DiscoveryResultBuilder.create(uid)
92                         .withProperties(buildProperties(host, serialNumber))
93                         .withLabel(LABEL_PATTERN.replace("IP", host)).withRepresentationProperty(PROPERTY_SERIAL_NUMBER)
94                         .build();
95                 thingDiscovered(result);
96             }
97         }
98     }
99
100     /**
101      * Builds the bridge properties.
102      *
103      * @param host the ip of the bridge
104      * @param serialNumber the id of the bridge
105      * @return the bridge properties
106      */
107     private Map<String, Object> buildProperties(String host, String serialNumber) {
108         Map<String, Object> properties = new HashMap<>(2);
109         properties.put(HOST, host);
110         properties.put(PROPERTY_SERIAL_NUMBER, serialNumber);
111         return properties;
112     }
113
114     /**
115      * Checks if the Bridge is a reachable Hue Bridge with a valid id.
116      *
117      * @param bridge the {@link BridgeJsonParameters}s
118      * @return true if Bridge is a reachable Hue Bridge with a id containing
119      *         BRIDGE_INDICATOR longer then 10
120      */
121     private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
122         String host = bridge.getInternalIpAddress();
123         String id = bridge.getId();
124         String description;
125         if (host == null) {
126             logger.debug("Bridge not discovered: ip is null");
127             return false;
128         }
129         if (id == null) {
130             logger.debug("Bridge not discovered: id is null");
131             return false;
132         }
133         if (id.length() < 10) {
134             logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
135             return false;
136         }
137         if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) {
138             logger.debug(
139                     "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
140                     id, BRIDGE_INDICATOR);
141             return false;
142         }
143         try {
144             description = doGetRequest(DESC_URL_PATTERN.replace("HOST", host));
145         } catch (IOException e) {
146             logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host);
147             return false;
148         }
149         if (!description.contains(MODEL_NAME_PHILIPS_HUE)) {
150             logger.debug("Bridge not discovered: Description does not containing the model name: {}", description);
151             return false;
152         }
153         return true;
154     }
155
156     /**
157      * Use the Philips Hue NUPnP service to find Hue Bridges in local Network.
158      *
159      * @return a list of available Hue Bridges
160      */
161     private List<BridgeJsonParameters> getBridgeList() {
162         try {
163             Gson gson = new Gson();
164             String json = doGetRequest(DISCOVERY_URL);
165             return gson.fromJson(json, new TypeToken<List<BridgeJsonParameters>>() {
166             }.getType());
167         } catch (IOException e) {
168             logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges");
169         } catch (JsonParseException je) {
170             logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges");
171         }
172         return new ArrayList<>();
173     }
174
175     /**
176      * Introduced in order to enable testing.
177      *
178      * @param url the url
179      * @return the http request result as String
180      * @throws IOException if request failed
181      */
182     protected String doGetRequest(String url) throws IOException {
183         return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);
184     }
185 }