org.eclipse.smarthome.binding.wemo.discovery.WemoLinkDiscoveryService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smarthome.binding.wemo.discovery.WemoLinkDiscoveryService.java

Source

/**
 * 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.wemo.discovery;

import static org.eclipse.smarthome.binding.wemo.WemoBindingConstants.*;

import java.io.StringReader;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.binding.wemo.handler.WemoBridgeHandler;
import org.eclipse.smarthome.binding.wemo.internal.http.WemoHttpCall;
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.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.io.transport.upnp.UpnpIOParticipant;
import org.eclipse.smarthome.io.transport.upnp.UpnpIOService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * The {@link WemoLinkDiscoveryService} is responsible for discovering new and
 * removed WeMo devices connected to the WeMo Link Bridge.
 *
 * @author Hans-Jrg Merk - Initial contribution
 *
 */
public class WemoLinkDiscoveryService extends AbstractDiscoveryService implements UpnpIOParticipant {

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

    public final static Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_MZ100);

    public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";

    /**
     * Maximum time to search for devices in seconds.
     */
    private final static int SEARCH_TIME = 20;

    /**
     * Initial delay for scanning job in seconds.
     */
    private final static int INITIAL_DELAY = 5;

    /**
     * Scan interval for scanning job in seconds.
     */
    private final static int SCAN_INTERVAL = 120;

    /**
     * The handler for WeMo Link bridge
     */
    private WemoBridgeHandler wemoBridgeHandler;

    /**
     * Job which will do the background scanning
     */
    private WemoLinkScan scanningRunnable;

    /**
     * Schedule for scanning
     */
    private ScheduledFuture<?> scanningJob;

    /**
     * The Upnp service
     */
    private UpnpIOService service;

    public WemoLinkDiscoveryService(WemoBridgeHandler wemoBridgeHandler, UpnpIOService upnpIOService) {
        super(SEARCH_TIME);
        this.wemoBridgeHandler = wemoBridgeHandler;

        if (upnpIOService != null) {
            this.service = upnpIOService;
        } else {
            logger.debug("upnpIOService not set.");
        }

        this.scanningRunnable = new WemoLinkScan();
        if (wemoBridgeHandler == null) {
            logger.warn("no bridge handler for scan given");
        }
        this.activate(null);
    }

    public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
        return SUPPORTED_THING_TYPES;
    }

    @Override
    protected void startScan() {

        logger.trace("Starting WeMoEndDevice discovery on WeMo Link {}", wemoBridgeHandler.getThing().getUID());
        try {

            String devUDN = "uuid:" + wemoBridgeHandler.getThing().getConfiguration().get(UDN).toString();
            logger.trace("devUDN = '{}'", devUDN);

            String soapHeader = "\"urn:Belkin:service:bridge:1#GetEndDevices\"";
            String content = "<?xml version=\"1.0\"?>"
                    + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
                    + "<s:Body>" + "<u:GetEndDevices xmlns:u=\"urn:Belkin:service:bridge:1\">" + "<DevUDN>" + devUDN
                    + "</DevUDN><ReqListType>PAIRED_LIST</ReqListType>" + "</u:GetEndDevices>" + "</s:Body>"
                    + "</s:Envelope>";

            URL descriptorURL = service.getDescriptorURL(this);

            if (descriptorURL != null) {
                String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
                String wemoURL = deviceURL + "/upnp/control/bridge1";

                String endDeviceRequest = WemoHttpCall.executeCall(wemoURL, soapHeader, content);

                if (endDeviceRequest != null) {
                    logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);

                    try {
                        String stringParser = StringUtils.substringBetween(endDeviceRequest, "<DeviceLists>",
                                "</DeviceLists>");

                        stringParser = StringEscapeUtils.unescapeXml(stringParser);

                        // check if there are already paired devices with WeMo Link
                        if ("0".equals(stringParser)) {
                            logger.debug("There are no devices connected with WeMo Link. Exit discovery");
                            return;
                        }

                        // Build parser for received <DeviceList>
                        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                        DocumentBuilder db = dbf.newDocumentBuilder();
                        InputSource is = new InputSource();
                        is.setCharacterStream(new StringReader(stringParser));

                        Document doc = db.parse(is);
                        NodeList nodes = doc.getElementsByTagName("DeviceInfo");

                        // iterate the devices
                        for (int i = 0; i < nodes.getLength(); i++) {
                            Element element = (Element) nodes.item(i);

                            NodeList deviceIndex = element.getElementsByTagName("DeviceIndex");
                            Element line = (Element) deviceIndex.item(0);
                            logger.trace("DeviceIndex: " + getCharacterDataFromElement(line));

                            NodeList deviceID = element.getElementsByTagName("DeviceID");
                            line = (Element) deviceID.item(0);
                            String endDeviceID = getCharacterDataFromElement(line);
                            logger.trace("DeviceID: " + endDeviceID);

                            NodeList friendlyName = element.getElementsByTagName("FriendlyName");
                            line = (Element) friendlyName.item(0);
                            String endDeviceName = getCharacterDataFromElement(line);
                            logger.trace("FriendlyName: " + endDeviceName);

                            NodeList vendor = element.getElementsByTagName("Manufacturer");
                            line = (Element) vendor.item(0);
                            String endDeviceVendor = getCharacterDataFromElement(line);
                            logger.trace("Manufacturer: " + endDeviceVendor);

                            NodeList model = element.getElementsByTagName("ModelCode");
                            line = (Element) model.item(0);
                            String endDeviceModelID = getCharacterDataFromElement(line);
                            endDeviceModelID = endDeviceModelID.replaceAll(NORMALIZE_ID_REGEX, "_");

                            logger.trace("ModelCode: " + endDeviceModelID);

                            if (SUPPORTED_THING_TYPES.contains(new ThingTypeUID(BINDING_ID, endDeviceModelID))) {
                                logger.debug("Discovered a WeMo LED Light thing with ID '{}'", endDeviceID);

                                ThingUID bridgeUID = wemoBridgeHandler.getThing().getUID();
                                ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, endDeviceModelID);

                                if (thingTypeUID.equals(THING_TYPE_MZ100)) {
                                    String thingLightId = endDeviceID;
                                    ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, thingLightId);

                                    Map<String, Object> properties = new HashMap<>(1);
                                    properties.put(DEVICE_ID, endDeviceID);

                                    DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
                                            .withProperties(properties)
                                            .withBridge(wemoBridgeHandler.getThing().getUID())
                                            .withLabel(endDeviceName).build();

                                    thingDiscovered(discoveryResult);
                                }

                            } else {
                                logger.debug("Discovered an unsupported device :");
                                logger.debug("DeviceIndex : " + getCharacterDataFromElement(line));
                                logger.debug("DeviceID    : " + endDeviceID);
                                logger.debug("FriendlyName: " + endDeviceName);
                                logger.debug("Manufacturer: " + endDeviceVendor);
                                logger.debug("ModelCode   : " + endDeviceModelID);
                            }

                        }
                    } catch (Exception e) {
                        logger.error("Failed to parse endDevices for bridge '{}'",
                                wemoBridgeHandler.getThing().getUID(), e);
                    }
                }

            }
        } catch (Exception e) {
            logger.error("Failed to get endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e);
        }
    }

    @Override
    protected void startBackgroundDiscovery() {
        logger.trace("Start WeMo device background discovery");

        if (scanningJob == null || scanningJob.isCancelled()) {
            this.scanningJob = AbstractDiscoveryService.scheduler.scheduleWithFixedDelay(this.scanningRunnable,
                    INITIAL_DELAY, SCAN_INTERVAL, TimeUnit.SECONDS);
        } else {
            logger.trace("scanningJob active");
        }
    }

    @Override
    protected void stopBackgroundDiscovery() {
        logger.debug("Stop WeMo device background discovery");

        if (scanningJob != null && !scanningJob.isCancelled()) {
            scanningJob.cancel(true);
            scanningJob = null;
        }
    }

    @Override
    public String getUDN() {
        return (String) this.wemoBridgeHandler.getThing().getConfiguration().get(UDN);
    }

    @Override
    public void onServiceSubscribed(String service, boolean succeeded) {
    }

    @Override
    public void onValueReceived(String variable, String value, String service) {
    }

    @Override
    public void onStatusChanged(boolean status) {
    }

    public static String getCharacterDataFromElement(Element e) {
        Node child = e.getFirstChild();
        if (child instanceof CharacterData) {
            CharacterData cd = (CharacterData) child;
            return cd.getData();
        }
        return "?";
    }

    public class WemoLinkScan implements Runnable {
        @Override
        public void run() {
            startScan();
        }
    }

}