/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.binding.hue.internal; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.smarthome.binding.hue.internal.HttpClient.Result; import org.eclipse.smarthome.binding.hue.internal.exceptions.ApiException; import com.google.gson.Gson; /** * Various ways of discovering bridges. * * @author Q42, standalone Jue library (https://github.com/Q42/Jue) * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup */ public class BridgeDiscovery { private BridgeDiscovery() { } /** * Search for bridges on the local network using UPnP, and * more specifically the SSDP protocol. * * It usually takes a while for devices to respond, so a * typical timeout would be 30 seconds. This function will * only return bridge objects with the IP address set, you * still need to authenticate manually. * * You can optionally specify a callback to get bridges as * they're being discovered. Pass null if you don't need that. * * @param timeout time to search in milliseconds * @param callback function to call when a new bridge is discovered (can be null) * @return list of bridges discovered */ public static List<HueBridge> searchUPnP(int timeout, BridgeDiscoveryCallback callback) throws IOException { // Send out SSDP discovery broadcast String ssdpBroadcast = "M-SEARCH * HTTP/1.1\nHOST: 239.255.255.250:1900\nMAN: ssdp:discover\nMX: 8\nST:SsdpSearch:all"; DatagramSocket upnpSock = new DatagramSocket(); upnpSock.setSoTimeout(100); upnpSock.send(new DatagramPacket(ssdpBroadcast.getBytes(), ssdpBroadcast.length(), new InetSocketAddress("239.255.255.250", 1900))); // Start waiting HashSet<String> ips = new HashSet<>(); ArrayList<HueBridge> bridges = new ArrayList<>(); long start = System.currentTimeMillis(); long nextBroadcast = start + 5000; while (System.currentTimeMillis() - start < timeout) { // Send a new discovery broadcast every 5 seconds just in case if (System.currentTimeMillis() > nextBroadcast) { upnpSock.send(new DatagramPacket(ssdpBroadcast.getBytes(), ssdpBroadcast.length(), new InetSocketAddress("239.255.255.250", 1900))); nextBroadcast = System.currentTimeMillis() + 5000; } byte[] responseBuffer = new byte[1024]; DatagramPacket responsePacket = new DatagramPacket(responseBuffer, responseBuffer.length); try { upnpSock.receive(responsePacket); } catch (SocketTimeoutException e) { if (System.currentTimeMillis() - start > timeout) { break; } else { continue; } } final String ip = responsePacket.getAddress().getHostAddress(); final String response = new String(responsePacket.getData()); if (!ips.contains(ip)) { Matcher m = Pattern.compile("LOCATION: (.*)", Pattern.CASE_INSENSITIVE).matcher(response); if (m.find()) { final String description = new HttpClient().get(m.group(1)).getBody(); // Parsing with RegEx allowed here because the output format is fairly strict final String modelName = Util.quickMatch("<modelName>(.*?)</modelName>", description); // Check from description if we're dealing with a hue bridge or some other device if (modelName.toLowerCase().contains("philips hue bridge")) { try { final HueBridge bridgeToBe = new HueBridge(ip); bridgeToBe.getConfig(); bridges.add(bridgeToBe); if (callback != null) { callback.onBridgeDiscovered(bridgeToBe); } } catch (ApiException e) { // Do nothing, this basically serves as an extra check to see if it's really a hue bridge } } } // Ignore subsequent packets ips.add(ip); } } return bridges; } /** * Search for local bridges using the portal API. * See: http://developers.meethue.com/5_portalapi.html * * @return list of bridges */ public static List<HueBridge> searchPortal() throws IOException { Result result = new HttpClient().get("http://www.meethue.com/api/nupnp"); if (result.getResponseCode() == 200) { return new Gson().fromJson(result.getBody(), PortalDiscoveryResult.gsonType); } else { throw new IOException(); } } /** * Callback for intermediary bridge search results. */ public interface BridgeDiscoveryCallback { /** * Called when a new bridge has been discovered by UPnP. * No attempt to authenticate will be made, so you'll * have to call authenticate() on the object manually. * * @param bridge Object representing found bridge */ void onBridgeDiscovered(HueBridge bridge); } }