org.openhab.binding.russound.internal.discovery.RioSystemDiscovery.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.russound.internal.discovery.RioSystemDiscovery.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.russound.internal.discovery;

import java.io.IOException;
import java.net.Inet6Address;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.util.SubnetUtils;
import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService;
import org.eclipse.smarthome.config.discovery.DiscoveryResult;
import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder;
import org.eclipse.smarthome.config.discovery.DiscoveryService;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.openhab.binding.russound.internal.net.SocketChannelSession;
import org.openhab.binding.russound.internal.net.SocketSession;
import org.openhab.binding.russound.internal.net.WaitingSessionListener;
import org.openhab.binding.russound.internal.rio.RioConstants;
import org.openhab.binding.russound.internal.rio.system.RioSystemConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableSet;

/**
 * This implementation of {@link DiscoveryService} will scan the network for any Russound RIO system devices. The scan
 * will occur against all network interfaces.
 *
 * @author Tim Roberts
 *
 */
public class RioSystemDiscovery extends AbstractDiscoveryService {
    /** The logger */
    private final Logger logger = LoggerFactory.getLogger(RioSystemDiscovery.class);

    /** The timeout to connect (in milliseconds) */
    private static final int CONN_TIMEOUT_IN_MS = 100;

    /** The {@link ExecutorService} to use for scanning - will be null if not scanning */
    private ExecutorService executorService = null;

    /** The number of network interfaces being scanned */
    private int nbrNetworkInterfacesScanning = 0;

    /**
     * Creates the system discovery service looking for {@link RioConstants#BRIDGE_TYPE_RIO}. The scan will take at most
     * 120 seconds (depending on how many network interfaces there are)
     */
    public RioSystemDiscovery() {
        super(ImmutableSet.of(RioConstants.BRIDGE_TYPE_RIO), 120);
    }

    /**
     * Starts the scan. For each network interface (that is up and not a loopback), all addresses will be iterated
     * and checked for something open on port 9621. If that port is open, a russound controller "type" command will be
     * issued. If the response is a correct pattern, we assume it's a rio system device and will emit a
     * {{@link #thingDiscovered(DiscoveryResult)}
     */
    @Override
    protected void startScan() {
        final List<NetworkInterface> interfaces;
        try {
            interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
        } catch (SocketException e1) {
            logger.debug("Exception getting network interfaces: {}", e1.getMessage(), e1);
            return;
        }

        nbrNetworkInterfacesScanning = interfaces.size();
        executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 10);

        for (final NetworkInterface networkInterface : interfaces) {
            try {
                if (networkInterface.isLoopback() || !networkInterface.isUp()) {
                    continue;
                }
            } catch (SocketException e) {
                continue;
            }

            for (Iterator<InterfaceAddress> it = networkInterface.getInterfaceAddresses().iterator(); it
                    .hasNext();) {
                final InterfaceAddress interfaceAddress = it.next();

                // don't bother with ipv6 addresses (russound doesn't support)
                if (interfaceAddress.getAddress() instanceof Inet6Address) {
                    continue;
                }

                final String subnetRange = interfaceAddress.getAddress().getHostAddress() + "/"
                        + interfaceAddress.getNetworkPrefixLength();

                logger.debug("Scanning subnet: {}", subnetRange);
                final SubnetUtils utils = new SubnetUtils(subnetRange);

                final String[] addresses = utils.getInfo().getAllAddresses();

                for (final String address : addresses) {
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            scanAddress(address);
                        }
                    });
                }
            }
        }

        // Finishes the scan and cleans up
        stopScan();
    }

    /**
     * Stops the scan by terminating the {@link #executorService} and shutting it down
     */
    @Override
    protected synchronized void stopScan() {
        super.stopScan();
        if (executorService == null) {
            return;
        }

        try {
            executorService.awaitTermination(CONN_TIMEOUT_IN_MS * nbrNetworkInterfacesScanning,
                    TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            // shutting down - doesn't matter
        }
        executorService.shutdown();
        executorService = null;

    }

    /**
     * Helper method to scan a specific address. Will open up port 9621 on the address and if opened, query for any
     * controller type (all 6 controllers are tested). If a valid type is found, a discovery result will be created.
     *
     * @param ipAddress a possibly null, possibly empty ip address (null/empty addresses will be ignored)
     */
    private void scanAddress(String ipAddress) {

        if (StringUtils.isEmpty(ipAddress)) {
            return;
        }

        final SocketSession session = new SocketChannelSession(ipAddress, RioConstants.RioPort);
        try {
            final WaitingSessionListener listener = new WaitingSessionListener();
            session.addListener(listener);
            session.connect(CONN_TIMEOUT_IN_MS);
            logger.debug("Connected to port {}:{} - testing to see if RIO", ipAddress, RioConstants.RioPort);

            // can't check for system properties because DMS responds to those -
            // need to check if any controllers are defined
            for (int c = 1; c < 7; c++) {
                session.sendCommand("GET C[" + c + "].type");
                final String resp = listener.getResponse();
                if (resp == null) {
                    continue;
                }
                if (!resp.startsWith("S C[" + c + "].type=\"")) {
                    continue;
                }
                final String type = resp.substring(13, resp.length() - 1);
                if (!StringUtils.isBlank(type)) {
                    logger.debug("Found a RIO type #{}", type);
                    addResult(ipAddress, type);
                    break;
                }
            }
        } catch (InterruptedException e) {
            logger.debug("Connection was interrupted to port {}:{}", ipAddress, RioConstants.RioPort);
        } catch (IOException e) {
            logger.trace("Connection couldn't be established to port {}:{}", ipAddress, RioConstants.RioPort);
        } finally {
            try {
                session.disconnect();
            } catch (IOException e) {
                // do nothing
            }
        }
    }

    /**
     * Helper method to add our ip address and system type as a discovery result.
     *
     * @param ipAddress a non-null, non-empty ip address
     * @param type a non-null, non-empty model type
     * @throws IllegalArgumentException if ipaddress or type is null or empty
     */
    private void addResult(String ipAddress, String type) {
        if (StringUtils.isEmpty(ipAddress)) {
            throw new IllegalArgumentException("ipAddress cannot be null or empty");
        }
        if (StringUtils.isEmpty(type)) {
            throw new IllegalArgumentException("type cannot be null or empty");
        }

        final Map<String, Object> properties = new HashMap<>(3);
        properties.put(RioSystemConfig.IpAddress, ipAddress);
        properties.put(RioSystemConfig.Ping, 30);
        properties.put(RioSystemConfig.RetryPolling, 10);
        properties.put(RioSystemConfig.ScanDevice, true);

        final String id = ipAddress.replace(".", "");
        final ThingUID uid = new ThingUID(RioConstants.BRIDGE_TYPE_RIO, id);
        if (uid != null) {
            final DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
                    .withLabel("Russound " + type).build();
            thingDiscovered(result);
        }

    }
}