"""Support for WeMo device discovery.""" import logging import pywemo import requests import voluptuous as vol from homeassistant import config_entries from homeassistant.components.discovery import SERVICE_WEMO from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP DOMAIN = "wemo" # Mapping from Wemo model_name to component. WEMO_MODEL_DISPATCH = { "Bridge": "light", "CoffeeMaker": "switch", "Dimmer": "light", "Humidifier": "fan", "Insight": "switch", "LightSwitch": "switch", "Maker": "switch", "Motion": "binary_sensor", "Sensor": "binary_sensor", "Socket": "switch", } SUBSCRIPTION_REGISTRY = None KNOWN_DEVICES = [] _LOGGER = logging.getLogger(__name__) def coerce_host_port(value): """Validate that provided value is either just host or host:port. Returns (host, None) or (host, port) respectively. """ host, _, port = value.partition(":") if not host: raise vol.Invalid("host cannot be empty") if port: port = cv.port(port) else: port = None return host, port CONF_STATIC = "static" CONF_DISCOVERY = "discovery" DEFAULT_DISCOVERY = True CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { vol.Optional(CONF_STATIC, default=[]): vol.Schema( [vol.All(cv.string, coerce_host_port)] ), vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean, } ) }, extra=vol.ALLOW_EXTRA, ) def setup(hass, config): """Set up for WeMo devices.""" hass.data[DOMAIN] = config if DOMAIN in config: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT} ) ) return True async def async_setup_entry(hass, entry): """Set up a wemo config entry.""" config = hass.data[DOMAIN] # Keep track of WeMo devices devices = [] # Keep track of WeMo device subscriptions for push updates global SUBSCRIPTION_REGISTRY SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry() await hass.async_add_executor_job(SUBSCRIPTION_REGISTRY.start) def stop_wemo(event): """Shutdown Wemo subscriptions and subscription thread on exit.""" _LOGGER.debug("Shutting down WeMo event subscriptions") SUBSCRIPTION_REGISTRY.stop() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo) def setup_url_for_device(device): """Determine setup.xml url for given device.""" return f"http://{device.host}:{device.port}/setup.xml" def setup_url_for_address(host, port): """Determine setup.xml url for given host and port pair.""" if not port: port = pywemo.ouimeaux_device.probe_wemo(host) if not port: return None return f"http://{host}:{port}/setup.xml" def discovery_dispatch(service, discovery_info): """Dispatcher for incoming WeMo discovery events.""" # name, model, location, mac model_name = discovery_info.get("model_name") serial = discovery_info.get("serial") # Only register a device once if serial in KNOWN_DEVICES: _LOGGER.debug("Ignoring known device %s %s", service, discovery_info) return _LOGGER.debug("Discovered unique WeMo device: %s", serial) KNOWN_DEVICES.append(serial) component = WEMO_MODEL_DISPATCH.get(model_name, "switch") discovery.load_platform(hass, component, DOMAIN, discovery_info, config) discovery.async_listen(hass, SERVICE_WEMO, discovery_dispatch) def discover_wemo_devices(now): """Run discovery for WeMo devices.""" _LOGGER.debug("Beginning WeMo device discovery...") _LOGGER.debug("Adding statically configured WeMo devices...") for host, port in config.get(DOMAIN, {}).get(CONF_STATIC, []): url = setup_url_for_address(host, port) if not url: _LOGGER.error( "Unable to get description url for WeMo at: %s", f"{host}:{port}" if port else host, ) continue try: device = pywemo.discovery.device_from_description(url, None) except ( requests.exceptions.ConnectionError, requests.exceptions.Timeout, ) as err: _LOGGER.error("Unable to access WeMo at %s (%s)", url, err) continue if not [d[1] for d in devices if d[1].serialnumber == device.serialnumber]: devices.append((url, device)) if config.get(DOMAIN, {}).get(CONF_DISCOVERY, DEFAULT_DISCOVERY): _LOGGER.debug("Scanning network for WeMo devices...") for device in pywemo.discover_devices(): if not [ d[1] for d in devices if d[1].serialnumber == device.serialnumber ]: devices.append((setup_url_for_device(device), device)) for url, device in devices: _LOGGER.debug("Adding WeMo device at %s:%i", device.host, device.port) discovery_info = { "model_name": device.model_name, "serial": device.serialnumber, "mac_address": device.mac, "ssdp_description": url, } discovery_dispatch(SERVICE_WEMO, discovery_info) _LOGGER.debug("WeMo device discovery has finished") hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, discover_wemo_devices) return True