core/homeassistant/components/wemo/__init__.py

194 lines
5.7 KiB
Python

"""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