Improve Wemo config entry support, add device info (#30963)
* Improve config entry support, add device info * async_dispatch_connect * Fix I/O in event loop * Do not raise PlatformNotReady inside dispatcher * Make main discovery process async * Do discovery as part of set up. * Greatly simplify set up * Add parallel updates to fan&switch * mini cleanup * Address comments
This commit is contained in:
parent
90e811df20
commit
f14d34560e
5 changed files with 206 additions and 189 deletions
|
@ -1,4 +1,5 @@
|
||||||
"""Support for WeMo device discovery."""
|
"""Support for WeMo device discovery."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pywemo
|
import pywemo
|
||||||
|
@ -6,9 +7,9 @@ import requests
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.discovery import SERVICE_WEMO
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers import config_validation as cv, discovery
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
@ -26,9 +27,6 @@ WEMO_MODEL_DISPATCH = {
|
||||||
"Socket": "switch",
|
"Socket": "switch",
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBSCRIPTION_REGISTRY = None
|
|
||||||
KNOWN_DEVICES = []
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,9 +68,13 @@ CONFIG_SCHEMA = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up for WeMo devices."""
|
"""Set up for WeMo devices."""
|
||||||
hass.data[DOMAIN] = config
|
hass.data[DOMAIN] = {
|
||||||
|
"config": config.get(DOMAIN, {}),
|
||||||
|
"registry": None,
|
||||||
|
"pending": {},
|
||||||
|
}
|
||||||
|
|
||||||
if DOMAIN in config:
|
if DOMAIN in config:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
|
@ -86,106 +88,103 @@ def setup(hass, config):
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry):
|
async def async_setup_entry(hass, entry):
|
||||||
"""Set up a wemo config entry."""
|
"""Set up a wemo config entry."""
|
||||||
|
config = hass.data[DOMAIN].pop("config")
|
||||||
config = hass.data[DOMAIN]
|
|
||||||
|
|
||||||
# Keep track of WeMo devices
|
|
||||||
devices = []
|
|
||||||
|
|
||||||
# Keep track of WeMo device subscriptions for push updates
|
# Keep track of WeMo device subscriptions for push updates
|
||||||
global SUBSCRIPTION_REGISTRY
|
registry = hass.data[DOMAIN]["registry"] = pywemo.SubscriptionRegistry()
|
||||||
SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry()
|
await hass.async_add_executor_job(registry.start)
|
||||||
await hass.async_add_executor_job(SUBSCRIPTION_REGISTRY.start)
|
|
||||||
|
|
||||||
def stop_wemo(event):
|
def stop_wemo(event):
|
||||||
"""Shutdown Wemo subscriptions and subscription thread on exit."""
|
"""Shutdown Wemo subscriptions and subscription thread on exit."""
|
||||||
_LOGGER.debug("Shutting down WeMo event subscriptions")
|
_LOGGER.debug("Shutting down WeMo event subscriptions")
|
||||||
SUBSCRIPTION_REGISTRY.stop()
|
registry.stop()
|
||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo)
|
||||||
|
|
||||||
def setup_url_for_device(device):
|
devices = {}
|
||||||
"""Determine setup.xml url for given device."""
|
|
||||||
return f"http://{device.host}:{device.port}/setup.xml"
|
|
||||||
|
|
||||||
def setup_url_for_address(host, port):
|
static_conf = config.get(CONF_STATIC, [])
|
||||||
"""Determine setup.xml url for given host and port pair."""
|
if static_conf:
|
||||||
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...")
|
_LOGGER.debug("Adding statically configured WeMo devices...")
|
||||||
for host, port in config.get(DOMAIN, {}).get(CONF_STATIC, []):
|
for device in await asyncio.gather(
|
||||||
url = setup_url_for_address(host, port)
|
*[
|
||||||
|
hass.async_add_executor_job(validate_static_config, host, port)
|
||||||
if not url:
|
for host, port in static_conf
|
||||||
_LOGGER.error(
|
]
|
||||||
"Unable to get description url for WeMo at: %s",
|
):
|
||||||
f"{host}:{port}" if port else host,
|
if device is None:
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
devices.setdefault(device.serialnumber, device)
|
||||||
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]:
|
if config.get(CONF_DISCOVERY, DEFAULT_DISCOVERY):
|
||||||
devices.append((url, device))
|
_LOGGER.debug("Scanning network for WeMo devices...")
|
||||||
|
for device in await hass.async_add_executor_job(pywemo.discover_devices):
|
||||||
|
devices.setdefault(
|
||||||
|
device.serialnumber, device,
|
||||||
|
)
|
||||||
|
|
||||||
if config.get(DOMAIN, {}).get(CONF_DISCOVERY, DEFAULT_DISCOVERY):
|
loaded_components = set()
|
||||||
_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:
|
for device in devices.values():
|
||||||
_LOGGER.debug("Adding WeMo device at %s:%i", device.host, device.port)
|
_LOGGER.debug(
|
||||||
|
"Adding WeMo device at %s:%i (%s)",
|
||||||
|
device.host,
|
||||||
|
device.port,
|
||||||
|
device.serialnumber,
|
||||||
|
)
|
||||||
|
|
||||||
discovery_info = {
|
component = WEMO_MODEL_DISPATCH.get(device.model_name, "switch")
|
||||||
"model_name": device.model_name,
|
|
||||||
"serial": device.serialnumber,
|
|
||||||
"mac_address": device.mac,
|
|
||||||
"ssdp_description": url,
|
|
||||||
}
|
|
||||||
|
|
||||||
discovery_dispatch(SERVICE_WEMO, discovery_info)
|
# Three cases:
|
||||||
|
# - First time we see component, we need to load it and initialize the backlog
|
||||||
|
# - Component is being loaded, add to backlog
|
||||||
|
# - Component is loaded, backlog is gone, dispatch discovery
|
||||||
|
|
||||||
_LOGGER.debug("WeMo device discovery has finished")
|
if component not in loaded_components:
|
||||||
|
hass.data[DOMAIN]["pending"][component] = [device]
|
||||||
|
loaded_components.add(component)
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, discover_wemo_devices)
|
elif component in hass.data[DOMAIN]["pending"]:
|
||||||
|
hass.data[DOMAIN]["pending"].append(device)
|
||||||
|
|
||||||
|
else:
|
||||||
|
async_dispatcher_send(
|
||||||
|
hass, f"{DOMAIN}.{component}", device,
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_static_config(host, port):
|
||||||
|
"""Handle a static config."""
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
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)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return device
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
|
@ -3,41 +3,36 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from pywemo import discovery
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import SUBSCRIPTION_REGISTRY
|
from .const import DOMAIN as WEMO_DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Register discovered WeMo binary sensors."""
|
"""Set up WeMo binary sensors."""
|
||||||
|
|
||||||
if discovery_info is not None:
|
async def _discovered_wemo(device):
|
||||||
location = discovery_info["ssdp_description"]
|
"""Handle a discovered Wemo device."""
|
||||||
mac = discovery_info["mac_address"]
|
async_add_entities([WemoBinarySensor(device)])
|
||||||
|
|
||||||
try:
|
async_dispatcher_connect(hass, f"{WEMO_DOMAIN}.binary_sensor", _discovered_wemo)
|
||||||
device = discovery.device_from_description(location, mac)
|
|
||||||
except (
|
|
||||||
requests.exceptions.ConnectionError,
|
|
||||||
requests.exceptions.Timeout,
|
|
||||||
) as err:
|
|
||||||
_LOGGER.error("Unable to access %s (%s)", location, err)
|
|
||||||
raise PlatformNotReady
|
|
||||||
|
|
||||||
if device:
|
await asyncio.gather(
|
||||||
add_entities([WemoBinarySensor(hass, device)])
|
*[
|
||||||
|
_discovered_wemo(device)
|
||||||
|
for device in hass.data[WEMO_DOMAIN]["pending"].pop("binary_sensor")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WemoBinarySensor(BinarySensorDevice):
|
class WemoBinarySensor(BinarySensorDevice):
|
||||||
"""Representation a WeMo binary sensor."""
|
"""Representation a WeMo binary sensor."""
|
||||||
|
|
||||||
def __init__(self, hass, device):
|
def __init__(self, device):
|
||||||
"""Initialize the WeMo sensor."""
|
"""Initialize the WeMo sensor."""
|
||||||
self.wemo = device
|
self.wemo = device
|
||||||
self._state = None
|
self._state = None
|
||||||
|
@ -67,7 +62,7 @@ class WemoBinarySensor(BinarySensorDevice):
|
||||||
# Define inside async context so we know our event loop
|
# Define inside async context so we know our event loop
|
||||||
self._update_lock = asyncio.Lock()
|
self._update_lock = asyncio.Lock()
|
||||||
|
|
||||||
registry = SUBSCRIPTION_REGISTRY
|
registry = self.hass.data[WEMO_DOMAIN]["registry"]
|
||||||
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
||||||
registry.on(self.wemo, None, self._subscription_callback)
|
registry.on(self.wemo, None, self._subscription_callback)
|
||||||
|
|
||||||
|
@ -126,3 +121,13 @@ class WemoBinarySensor(BinarySensorDevice):
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return true if sensor is available."""
|
"""Return true if sensor is available."""
|
||||||
return self._available
|
return self._available
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info."""
|
||||||
|
return {
|
||||||
|
"name": self.wemo.name,
|
||||||
|
"identifiers": {(WEMO_DOMAIN, self.wemo.serialnumber)},
|
||||||
|
"model": self.wemo.model_name,
|
||||||
|
"manufacturer": "Belkin",
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,6 @@ from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from pywemo import discovery
|
|
||||||
import requests
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
|
@ -17,14 +15,17 @@ from homeassistant.components.fan import (
|
||||||
FanEntity,
|
FanEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import SUBSCRIPTION_REGISTRY
|
from .const import (
|
||||||
from .const import DOMAIN, SERVICE_RESET_FILTER_LIFE, SERVICE_SET_HUMIDITY
|
DOMAIN as WEMO_DOMAIN,
|
||||||
|
SERVICE_RESET_FILTER_LIFE,
|
||||||
|
SERVICE_SET_HUMIDITY,
|
||||||
|
)
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=10)
|
SCAN_INTERVAL = timedelta(seconds=10)
|
||||||
DATA_KEY = "fan.wemo"
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -91,36 +92,30 @@ SET_HUMIDITY_SCHEMA = vol.Schema(
|
||||||
RESET_FILTER_LIFE_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_ids})
|
RESET_FILTER_LIFE_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_ids})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up discovered WeMo humidifiers."""
|
"""Set up WeMo binary sensors."""
|
||||||
|
entities = []
|
||||||
|
|
||||||
if DATA_KEY not in hass.data:
|
async def _discovered_wemo(device):
|
||||||
hass.data[DATA_KEY] = {}
|
"""Handle a discovered Wemo device."""
|
||||||
|
entity = WemoHumidifier(device)
|
||||||
|
entities.append(entity)
|
||||||
|
async_add_entities([entity])
|
||||||
|
|
||||||
if discovery_info is None:
|
async_dispatcher_connect(hass, f"{WEMO_DOMAIN}.fan", _discovered_wemo)
|
||||||
return
|
|
||||||
|
|
||||||
location = discovery_info["ssdp_description"]
|
await asyncio.gather(
|
||||||
mac = discovery_info["mac_address"]
|
*[
|
||||||
|
_discovered_wemo(device)
|
||||||
try:
|
for device in hass.data[WEMO_DOMAIN]["pending"].pop("fan")
|
||||||
device = WemoHumidifier(discovery.device_from_description(location, mac))
|
]
|
||||||
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as err:
|
)
|
||||||
_LOGGER.error("Unable to access %s (%s)", location, err)
|
|
||||||
raise PlatformNotReady
|
|
||||||
|
|
||||||
hass.data[DATA_KEY][device.entity_id] = device
|
|
||||||
add_entities([device])
|
|
||||||
|
|
||||||
def service_handle(service):
|
def service_handle(service):
|
||||||
"""Handle the WeMo humidifier services."""
|
"""Handle the WeMo humidifier services."""
|
||||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||||
|
|
||||||
humidifiers = [
|
humidifiers = [entity for entity in entities if entity.entity_id in entity_ids]
|
||||||
device
|
|
||||||
for device in hass.data[DATA_KEY].values()
|
|
||||||
if device.entity_id in entity_ids
|
|
||||||
]
|
|
||||||
|
|
||||||
if service.service == SERVICE_SET_HUMIDITY:
|
if service.service == SERVICE_SET_HUMIDITY:
|
||||||
target_humidity = service.data.get(ATTR_TARGET_HUMIDITY)
|
target_humidity = service.data.get(ATTR_TARGET_HUMIDITY)
|
||||||
|
@ -132,12 +127,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
humidifier.reset_filter_life()
|
humidifier.reset_filter_life()
|
||||||
|
|
||||||
# Register service(s)
|
# Register service(s)
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_SET_HUMIDITY, service_handle, schema=SET_HUMIDITY_SCHEMA
|
WEMO_DOMAIN, SERVICE_SET_HUMIDITY, service_handle, schema=SET_HUMIDITY_SCHEMA
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN,
|
WEMO_DOMAIN,
|
||||||
SERVICE_RESET_FILTER_LIFE,
|
SERVICE_RESET_FILTER_LIFE,
|
||||||
service_handle,
|
service_handle,
|
||||||
schema=RESET_FILTER_LIFE_SCHEMA,
|
schema=RESET_FILTER_LIFE_SCHEMA,
|
||||||
|
@ -199,6 +194,16 @@ class WemoHumidifier(FanEntity):
|
||||||
"""Return true if switch is available."""
|
"""Return true if switch is available."""
|
||||||
return self._available
|
return self._available
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info."""
|
||||||
|
return {
|
||||||
|
"name": self.wemo.name,
|
||||||
|
"identifiers": {(WEMO_DOMAIN, self.wemo.serialnumber)},
|
||||||
|
"model": self.wemo.model_name,
|
||||||
|
"manufacturer": "Belkin",
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Return the icon of device based on its type."""
|
"""Return the icon of device based on its type."""
|
||||||
|
@ -236,7 +241,7 @@ class WemoHumidifier(FanEntity):
|
||||||
# Define inside async context so we know our event loop
|
# Define inside async context so we know our event loop
|
||||||
self._update_lock = asyncio.Lock()
|
self._update_lock = asyncio.Lock()
|
||||||
|
|
||||||
registry = SUBSCRIPTION_REGISTRY
|
registry = self.hass.data[WEMO_DOMAIN]["registry"]
|
||||||
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
||||||
registry.on(self.wemo, None, self._subscription_callback)
|
registry.on(self.wemo, None, self._subscription_callback)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from pywemo import discovery
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from homeassistant import util
|
from homeassistant import util
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
|
@ -19,10 +17,10 @@ from homeassistant.components.light import (
|
||||||
SUPPORT_TRANSITION,
|
SUPPORT_TRANSITION,
|
||||||
Light,
|
Light,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
from . import SUBSCRIPTION_REGISTRY
|
from .const import DOMAIN as WEMO_DOMAIN
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
||||||
|
@ -34,29 +32,29 @@ SUPPORT_WEMO = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up discovered WeMo switches."""
|
"""Set up WeMo lights."""
|
||||||
|
|
||||||
if discovery_info is not None:
|
|
||||||
location = discovery_info["ssdp_description"]
|
|
||||||
mac = discovery_info["mac_address"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
device = discovery.device_from_description(location, mac)
|
|
||||||
except (
|
|
||||||
requests.exceptions.ConnectionError,
|
|
||||||
requests.exceptions.Timeout,
|
|
||||||
) as err:
|
|
||||||
_LOGGER.error("Unable to access %s (%s)", location, err)
|
|
||||||
raise PlatformNotReady
|
|
||||||
|
|
||||||
|
async def _discovered_wemo(device):
|
||||||
|
"""Handle a discovered Wemo device."""
|
||||||
if device.model_name == "Dimmer":
|
if device.model_name == "Dimmer":
|
||||||
add_entities([WemoDimmer(device)])
|
async_add_entities([WemoDimmer(device)])
|
||||||
else:
|
else:
|
||||||
setup_bridge(device, add_entities)
|
await hass.async_add_executor_job(
|
||||||
|
setup_bridge, hass, device, async_add_entities
|
||||||
|
)
|
||||||
|
|
||||||
|
async_dispatcher_connect(hass, f"{WEMO_DOMAIN}.light", _discovered_wemo)
|
||||||
|
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
_discovered_wemo(device)
|
||||||
|
for device in hass.data[WEMO_DOMAIN]["pending"].pop("light")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_bridge(bridge, add_entities):
|
def setup_bridge(hass, bridge, async_add_entities):
|
||||||
"""Set up a WeMo link."""
|
"""Set up a WeMo link."""
|
||||||
lights = {}
|
lights = {}
|
||||||
|
|
||||||
|
@ -73,7 +71,7 @@ def setup_bridge(bridge, add_entities):
|
||||||
new_lights.append(lights[light_id])
|
new_lights.append(lights[light_id])
|
||||||
|
|
||||||
if new_lights:
|
if new_lights:
|
||||||
add_entities(new_lights)
|
hass.add_job(async_add_entities, new_lights)
|
||||||
|
|
||||||
update_lights()
|
update_lights()
|
||||||
|
|
||||||
|
@ -110,6 +108,16 @@ class WemoLight(Light):
|
||||||
"""Return the name of the light."""
|
"""Return the name of the light."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info."""
|
||||||
|
return {
|
||||||
|
"name": self.wemo.name,
|
||||||
|
"identifiers": {(WEMO_DOMAIN, self.wemo.serialnumber)},
|
||||||
|
"model": self.wemo.model_name,
|
||||||
|
"manufacturer": "Belkin",
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
|
@ -235,7 +243,7 @@ class WemoDimmer(Light):
|
||||||
# Define inside async context so we know our event loop
|
# Define inside async context so we know our event loop
|
||||||
self._update_lock = asyncio.Lock()
|
self._update_lock = asyncio.Lock()
|
||||||
|
|
||||||
registry = SUBSCRIPTION_REGISTRY
|
registry = self.hass.data[WEMO_DOMAIN]["registry"]
|
||||||
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
||||||
registry.on(self.wemo, None, self._subscription_callback)
|
registry.on(self.wemo, None, self._subscription_callback)
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,16 @@ from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from pywemo import discovery
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util import convert
|
from homeassistant.util import convert
|
||||||
|
|
||||||
from . import SUBSCRIPTION_REGISTRY
|
from .const import DOMAIN as WEMO_DOMAIN
|
||||||
from .const import DOMAIN
|
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=10)
|
SCAN_INTERVAL = timedelta(seconds=10)
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -32,24 +30,21 @@ WEMO_OFF = 0
|
||||||
WEMO_STANDBY = 8
|
WEMO_STANDBY = 8
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up discovered WeMo switches."""
|
"""Set up WeMo switches."""
|
||||||
|
|
||||||
if discovery_info is not None:
|
async def _discovered_wemo(device):
|
||||||
location = discovery_info["ssdp_description"]
|
"""Handle a discovered Wemo device."""
|
||||||
mac = discovery_info["mac_address"]
|
async_add_entities([WemoSwitch(device)])
|
||||||
|
|
||||||
try:
|
async_dispatcher_connect(hass, f"{WEMO_DOMAIN}.switch", _discovered_wemo)
|
||||||
device = discovery.device_from_description(location, mac)
|
|
||||||
except (
|
|
||||||
requests.exceptions.ConnectionError,
|
|
||||||
requests.exceptions.Timeout,
|
|
||||||
) as err:
|
|
||||||
_LOGGER.error("Unable to access %s (%s)", location, err)
|
|
||||||
raise PlatformNotReady
|
|
||||||
|
|
||||||
if device:
|
await asyncio.gather(
|
||||||
add_entities([WemoSwitch(device)])
|
*[
|
||||||
|
_discovered_wemo(device)
|
||||||
|
for device in hass.data[WEMO_DOMAIN]["pending"].pop("switch")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WemoSwitch(SwitchDevice):
|
class WemoSwitch(SwitchDevice):
|
||||||
|
@ -97,7 +92,12 @@ class WemoSwitch(SwitchDevice):
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
"""Return the device info."""
|
"""Return the device info."""
|
||||||
return {"name": self._name, "identifiers": {(DOMAIN, self._serialnumber)}}
|
return {
|
||||||
|
"name": self.wemo.name,
|
||||||
|
"identifiers": {(WEMO_DOMAIN, self.wemo.serialnumber)},
|
||||||
|
"model": self.wemo.model_name,
|
||||||
|
"manufacturer": "Belkin",
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
|
@ -200,7 +200,7 @@ class WemoSwitch(SwitchDevice):
|
||||||
# Define inside async context so we know our event loop
|
# Define inside async context so we know our event loop
|
||||||
self._update_lock = asyncio.Lock()
|
self._update_lock = asyncio.Lock()
|
||||||
|
|
||||||
registry = SUBSCRIPTION_REGISTRY
|
registry = self.hass.data[WEMO_DOMAIN]["registry"]
|
||||||
await self.hass.async_add_job(registry.register, self.wemo)
|
await self.hass.async_add_job(registry.register, self.wemo)
|
||||||
registry.on(self.wemo, None, self._subscription_callback)
|
registry.on(self.wemo, None, self._subscription_callback)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue