org.openhab.binding.harmonyhub.discovery.HarmonyHubDiscovery.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.harmonyhub.discovery.HarmonyHubDiscovery.java

Source

/**
 * Copyright (c) 2010-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.openhab.binding.harmonyhub.discovery;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.core.common.ThreadPoolManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link HarmonyHubDiscovery} class discovers Logitech Harmony Hubs on the
 * network by broadcasting a discovery string to port 5224 on the
 * broadcast address. Hubs respond by making a TCP connection back to
 * the IP address that our packet was sent from and on the port we
 * advertised as part of the original discovery request.
 *
 * @author Dan Cunningham - Initial contribution
 *
 */
public class HarmonyHubDiscovery {

    private Logger logger = LoggerFactory.getLogger(HarmonyHubDiscovery.class);

    // notice the port appended to the end of the string
    private static final String DISCO_STRING = "_logitech-reverse-bonjour._tcp.local.\n%d";

    private static final int DISCO_PORT = 5224;

    static protected final ScheduledExecutorService scheduler = ThreadPoolManager
            .getScheduledPool(HarmonyHubDiscovery.class.getName());
    private ScheduledFuture<?> broadcastFuture;
    private ScheduledFuture<?> timeoutFuture;
    private ServerSocket serverSocket;
    private HarmonyServer server;
    private int timeout;
    private boolean running;
    private String optionalHost;

    private List<HarmonyHubDiscoveryListener> listeners = new CopyOnWriteArrayList<HarmonyHubDiscoveryListener>();

    /**
     *
     * @param timeout
     *            how long we discover for
     */
    public HarmonyHubDiscovery(int timeout, String optionalHost) {
        this.timeout = timeout;
        this.optionalHost = optionalHost;
        running = false;
        listeners = new LinkedList<HarmonyHubDiscoveryListener>();
    }

    /**
     * Adds a HarmonyHubDiscoveryListener
     *
     * @param listener
     */
    public void addListener(HarmonyHubDiscoveryListener listener) {
        listeners.add(listener);
    }

    /**
     * Removes a HarmonyHubDiscoveryListener
     *
     * @param listener
     */
    public void removeListener(HarmonyHubDiscoveryListener listener) {
        listeners.remove(listener);
    }

    /**
     * Starts discovery for Harmony Hubs
     */
    public synchronized void startDiscovery() {
        if (running) {
            return;
        }

        try {
            serverSocket = new ServerSocket(0);
            logger.debug("Creating Harmony server on port {}", serverSocket.getLocalPort());
            server = new HarmonyServer(serverSocket);
            server.start();

            broadcastFuture = scheduler.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    sendDiscoveryMessage(String.format(DISCO_STRING, serverSocket.getLocalPort()));
                }
            }, 0, 2, TimeUnit.SECONDS);

            timeoutFuture = scheduler.schedule(new Runnable() {
                @Override
                public void run() {
                    stopDiscovery();
                }
            }, timeout, TimeUnit.SECONDS);

            running = true;
        } catch (IOException e) {
            logger.error("Could not start Harmony discovery server ", e);
        }
    }

    /**
     * Stops discovery of Harmony Hubs
     */
    public synchronized void stopDiscovery() {
        if (broadcastFuture != null) {
            broadcastFuture.cancel(true);
        }
        if (timeoutFuture != null) {
            broadcastFuture.cancel(true);
        }
        if (server != null) {
            server.setRunning(false);
        }
        try {
            serverSocket.close();
        } catch (Exception e) {
            logger.error("Could not stop harmony discovery socket", e);
        }
        for (HarmonyHubDiscoveryListener listener : listeners) {
            listener.hubDiscoveryFinished();
        }
        running = false;

    }

    /**
     * Send broadcast message over all active interfaces
     *
     * @param discoverString
     *            String to be used for the discovery
     */
    private void sendDiscoveryMessage(String discoverString) {
        DatagramSocket bcSend = null;
        try {
            bcSend = new DatagramSocket();
            bcSend.setBroadcast(true);

            byte[] sendData = discoverString.getBytes();

            // Broadcast the message over all the network interfaces
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface networkInterface = interfaces.nextElement();
                if (networkInterface.isLoopback() || !networkInterface.isUp()) {
                    continue;
                }
                for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
                    InetAddress[] broadcast = null;

                    if (StringUtils.isNotBlank(optionalHost)) {
                        try {
                            broadcast = new InetAddress[] { InetAddress.getByName(optionalHost) };
                        } catch (Exception e) {
                            logger.error("Could not use host for hub discovery", e);
                            return;
                        }
                    } else {
                        broadcast = new InetAddress[] { InetAddress.getByName("224.0.0.1"),
                                InetAddress.getByName("255.255.255.255"), interfaceAddress.getBroadcast() };
                    }

                    for (InetAddress bc : broadcast) {
                        // Send the broadcast package!
                        if (bc != null) {
                            try {
                                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc,
                                        DISCO_PORT);
                                bcSend.send(sendPacket);
                            } catch (IOException e) {
                                logger.debug("IO error during HarmonyHub discovery: {}", e.getMessage());
                            } catch (Exception e) {
                                logger.debug(e.getMessage(), e);
                            }
                            logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
                                    networkInterface.getDisplayName());
                        }
                    }
                }
            }

        } catch (IOException e) {
            logger.debug("IO error during HarmonyHub discovery: {}", e.getMessage());
        } finally {
            try {
                if (bcSend != null) {
                    bcSend.close();
                }
            } catch (Exception e) {
                // Ignore
            }
        }

    }

    /**
     * Server which accepts TCP connections from Harmony Hubs during the discovery process
     *
     * @author Dan Cunningham - Initial contribution
     *
     */
    private class HarmonyServer extends Thread {
        private ServerSocket serverSocket;
        private boolean running;
        private List<String> responses = new ArrayList<String>();

        public HarmonyServer(ServerSocket serverSocket) {
            this.serverSocket = serverSocket;
            running = true;
        }

        @Override
        public void run() {
            while (running) {
                Socket socket = null;
                try {
                    socket = serverSocket.accept();
                    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String input;
                    while ((input = in.readLine()) != null) {
                        if (!running) {
                            break;
                        }
                        logger.trace("READ {}", input);
                        String propsString = input.replaceAll(";", "\n");
                        propsString = propsString.replaceAll(":", "=");
                        Properties props = new Properties();
                        props.load(new StringReader(propsString));
                        if (!responses.contains(props.getProperty("friendlyName"))) {
                            responses.add(props.getProperty("friendlyName"));
                            HarmonyHubDiscoveryResult result = new HarmonyHubDiscoveryResult(
                                    props.getProperty("ip"), props.getProperty("accountId"),
                                    props.getProperty("uuid"),
                                    props.getProperty("host_name").replaceAll("[^A-Za-z0-9\\-_]", ""),
                                    props.getProperty("friendlyName"));
                            for (HarmonyHubDiscoveryListener listener : listeners) {
                                listener.hubDiscovered(result);
                            }
                        }
                    }
                } catch (IOException e) {
                    if (running) {
                        logger.debug("Error connecting with found hub", e);
                    }
                } finally {
                    try {
                        if (socket != null) {
                            socket.close();
                        }
                    } catch (IOException e) {
                        logger.warn("could not close socket", e);
                    }
                }
            }
        }

        public void setRunning(boolean running) {
            this.running = running;
        }
    }
}