mirror of
https://github.com/home-assistant/core.git
synced 2025-04-27 18:57:57 +00:00
Various enhancements for WeMo component/platforms (#19419)
* WeMo - Various fixes and improvements Various fixes & improvements to the WeMo components, including: -- Fixes to rediscovery -- New reset filter service for the WeMo Humidifier -- Switched the remainder of the WeMo components to async IO -- Removed any remaining IO in entity properties and moved them to the polling/subscription update process * WeMo - Fix pywemo version and remove test code from WeMo fan component * WeMo Humidifier - Add services.yaml entry for reset filter life service * WeMo - Update binary_sensor component to use asyncio * WeMo - Add available property to binary_sensor component * WeMo - Fixed line length issue * WeMo - Fix issue with discovering the same device multiple times * WeMo - Fix for the fix for discovering devices multiple times * WeMo - Fix long lines * WeMo - Fixes from code review * WeMo - Breaking Change - entity_ids is now required on wemo_set_humidity * WeMo - Code review fixes * WeMo - Code review fixes * WeMo - Code review fixes
This commit is contained in:
parent
ef6c39f911
commit
7f0dd442fd
@ -4,7 +4,10 @@ Support for WeMo sensors.
|
|||||||
For more details about this component, please refer to the documentation at
|
For more details about this component, please refer to the documentation at
|
||||||
https://home-assistant.io/components/binary_sensor.wemo/
|
https://home-assistant.io/components/binary_sensor.wemo/
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
@ -41,48 +44,90 @@ class WemoBinarySensor(BinarySensorDevice):
|
|||||||
"""Initialize the WeMo sensor."""
|
"""Initialize the WeMo sensor."""
|
||||||
self.wemo = device
|
self.wemo = device
|
||||||
self._state = None
|
self._state = None
|
||||||
|
self._available = True
|
||||||
|
self._update_lock = None
|
||||||
|
self._model_name = self.wemo.model_name
|
||||||
|
self._name = self.wemo.name
|
||||||
|
self._serialnumber = self.wemo.serialnumber
|
||||||
|
|
||||||
wemo = hass.components.wemo
|
def _subscription_callback(self, _device, _type, _params):
|
||||||
wemo.SUBSCRIPTION_REGISTRY.register(self.wemo)
|
"""Update the state by the Wemo sensor."""
|
||||||
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)
|
_LOGGER.debug("Subscription update for %s", self.name)
|
||||||
|
|
||||||
def _update_callback(self, _device, _type, _params):
|
|
||||||
"""Handle state changes."""
|
|
||||||
_LOGGER.info("Subscription update for %s", _device)
|
|
||||||
updated = self.wemo.subscription_update(_type, _params)
|
updated = self.wemo.subscription_update(_type, _params)
|
||||||
self._update(force_update=(not updated))
|
self.hass.add_job(
|
||||||
|
self._async_locked_subscription_callback(not updated))
|
||||||
|
|
||||||
if not hasattr(self, 'hass'):
|
async def _async_locked_subscription_callback(self, force_update):
|
||||||
|
"""Handle an update from a subscription."""
|
||||||
|
# If an update is in progress, we don't do anything
|
||||||
|
if self._update_lock.locked():
|
||||||
return
|
return
|
||||||
self.schedule_update_ha_state()
|
|
||||||
|
|
||||||
@property
|
await self._async_locked_update(force_update)
|
||||||
def should_poll(self):
|
self.async_schedule_update_ha_state()
|
||||||
"""No polling needed with subscriptions."""
|
|
||||||
return False
|
async def async_added_to_hass(self):
|
||||||
|
"""Wemo sensor added to HASS."""
|
||||||
|
# Define inside async context so we know our event loop
|
||||||
|
self._update_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
|
||||||
|
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
||||||
|
registry.on(self.wemo, None, self._subscription_callback)
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update WeMo state.
|
||||||
|
|
||||||
|
Wemo has an aggressive retry logic that sometimes can take over a
|
||||||
|
minute to return. If we don't get a state after 5 seconds, assume the
|
||||||
|
Wemo sensor is unreachable. If update goes through, it will be made
|
||||||
|
available again.
|
||||||
|
"""
|
||||||
|
# If an update is in progress, we don't do anything
|
||||||
|
if self._update_lock.locked():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with async_timeout.timeout(5):
|
||||||
|
await asyncio.shield(self._async_locked_update(True))
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
_LOGGER.warning('Lost connection to %s', self.name)
|
||||||
|
self._available = False
|
||||||
|
|
||||||
|
async def _async_locked_update(self, force_update):
|
||||||
|
"""Try updating within an async lock."""
|
||||||
|
async with self._update_lock:
|
||||||
|
await self.hass.async_add_executor_job(self._update, force_update)
|
||||||
|
|
||||||
|
def _update(self, force_update=True):
|
||||||
|
"""Update the sensor state."""
|
||||||
|
try:
|
||||||
|
self._state = self.wemo.get_state(force_update)
|
||||||
|
|
||||||
|
if not self._available:
|
||||||
|
_LOGGER.info('Reconnected to %s', self.name)
|
||||||
|
self._available = True
|
||||||
|
except AttributeError as err:
|
||||||
|
_LOGGER.warning("Could not update status for %s (%s)",
|
||||||
|
self.name, err)
|
||||||
|
self._available = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the id of this WeMo device."""
|
"""Return the id of this WeMo sensor."""
|
||||||
return self.wemo.serialnumber
|
return self._serialnumber
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the service if any."""
|
"""Return the name of the service if any."""
|
||||||
return self.wemo.name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if sensor is on."""
|
"""Return true if sensor is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def update(self):
|
@property
|
||||||
"""Update WeMo state."""
|
def available(self):
|
||||||
self._update(force_update=True)
|
"""Return true if sensor is available."""
|
||||||
|
return self._available
|
||||||
def _update(self, force_update=True):
|
|
||||||
try:
|
|
||||||
self._state = self.wemo.get_state(force_update)
|
|
||||||
except AttributeError as err:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Could not update status for %s (%s)", self.name, err)
|
|
||||||
|
@ -209,8 +209,15 @@ wemo_set_humidity:
|
|||||||
description: Set the target humidity of WeMo humidifier devices.
|
description: Set the target humidity of WeMo humidifier devices.
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Names of the WeMo humidifier entities (0 or more entities, if no entity_id is provided, all WeMo humidifiers will have the target humidity set).
|
description: Names of the WeMo humidifier entities (1 or more entity_ids are required).
|
||||||
example: 'fan.wemo_humidifier'
|
example: 'fan.wemo_humidifier'
|
||||||
target_humidity:
|
target_humidity:
|
||||||
description: Target humidity. This is a float value between 0 and 100, but will be mapped to the humidity levels that WeMo humidifiers support (45, 50, 55, 60, and 100/Max) by rounding the value down to the nearest supported value.
|
description: Target humidity. This is a float value between 0 and 100, but will be mapped to the humidity levels that WeMo humidifiers support (45, 50, 55, 60, and 100/Max) by rounding the value down to the nearest supported value.
|
||||||
example: 56.5
|
example: 56.5
|
||||||
|
|
||||||
|
wemo_reset_filter_life:
|
||||||
|
description: Reset the WeMo Humidifier's filter life to 100%.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Names of the WeMo humidifier entities (1 or more entity_ids are required).
|
||||||
|
example: 'fan.wemo_humidifier'
|
||||||
|
@ -78,11 +78,17 @@ HASS_FAN_SPEED_TO_WEMO = {v: k for (k, v) in WEMO_FAN_SPEED_TO_HASS.items()
|
|||||||
SERVICE_SET_HUMIDITY = 'wemo_set_humidity'
|
SERVICE_SET_HUMIDITY = 'wemo_set_humidity'
|
||||||
|
|
||||||
SET_HUMIDITY_SCHEMA = vol.Schema({
|
SET_HUMIDITY_SCHEMA = vol.Schema({
|
||||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||||
vol.Required(ATTR_TARGET_HUMIDITY):
|
vol.Required(ATTR_TARGET_HUMIDITY):
|
||||||
vol.All(vol.Coerce(float), vol.Range(min=0, max=100))
|
vol.All(vol.Coerce(float), vol.Range(min=0, max=100))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
SERVICE_RESET_FILTER_LIFE = 'wemo_reset_filter_life'
|
||||||
|
|
||||||
|
RESET_FILTER_LIFE_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(ATTR_ENTITY_ID): cv.entity_ids
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up discovered WeMo humidifiers."""
|
"""Set up discovered WeMo humidifiers."""
|
||||||
@ -111,22 +117,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
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)
|
||||||
target_humidity = service.data.get(ATTR_TARGET_HUMIDITY)
|
|
||||||
|
|
||||||
if entity_ids:
|
humidifiers = [device for device in
|
||||||
humidifiers = [device for device in hass.data[DATA_KEY].values() if
|
hass.data[DATA_KEY].values() if
|
||||||
device.entity_id in entity_ids]
|
device.entity_id in entity_ids]
|
||||||
else:
|
|
||||||
humidifiers = hass.data[DATA_KEY].values()
|
if service.service == SERVICE_SET_HUMIDITY:
|
||||||
|
target_humidity = service.data.get(ATTR_TARGET_HUMIDITY)
|
||||||
|
|
||||||
for humidifier in humidifiers:
|
for humidifier in humidifiers:
|
||||||
humidifier.set_humidity(target_humidity)
|
humidifier.set_humidity(target_humidity)
|
||||||
|
elif service.service == SERVICE_RESET_FILTER_LIFE:
|
||||||
|
for humidifier in humidifiers:
|
||||||
|
humidifier.reset_filter_life()
|
||||||
|
|
||||||
# Register service(s)
|
# Register service(s)
|
||||||
hass.services.register(
|
hass.services.register(
|
||||||
DOMAIN, SERVICE_SET_HUMIDITY, service_handle,
|
DOMAIN, SERVICE_SET_HUMIDITY, service_handle,
|
||||||
schema=SET_HUMIDITY_SCHEMA)
|
schema=SET_HUMIDITY_SCHEMA)
|
||||||
|
|
||||||
|
hass.services.register(
|
||||||
|
DOMAIN, SERVICE_RESET_FILTER_LIFE, service_handle,
|
||||||
|
schema=RESET_FILTER_LIFE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
class WemoHumidifier(FanEntity):
|
class WemoHumidifier(FanEntity):
|
||||||
"""Representation of a WeMo humidifier."""
|
"""Representation of a WeMo humidifier."""
|
||||||
@ -137,7 +150,6 @@ class WemoHumidifier(FanEntity):
|
|||||||
self._state = None
|
self._state = None
|
||||||
self._available = True
|
self._available = True
|
||||||
self._update_lock = None
|
self._update_lock = None
|
||||||
|
|
||||||
self._fan_mode = None
|
self._fan_mode = None
|
||||||
self._target_humidity = None
|
self._target_humidity = None
|
||||||
self._current_humidity = None
|
self._current_humidity = None
|
||||||
@ -145,9 +157,6 @@ class WemoHumidifier(FanEntity):
|
|||||||
self._filter_life = None
|
self._filter_life = None
|
||||||
self._filter_expired = None
|
self._filter_expired = None
|
||||||
self._last_fan_on_mode = WEMO_FAN_MEDIUM
|
self._last_fan_on_mode = WEMO_FAN_MEDIUM
|
||||||
|
|
||||||
# look up model name, name, and serial number
|
|
||||||
# once as it incurs network traffic
|
|
||||||
self._model_name = self.wemo.model_name
|
self._model_name = self.wemo.model_name
|
||||||
self._name = self.wemo.name
|
self._name = self.wemo.name
|
||||||
self._serialnumber = self.wemo.serialnumber
|
self._serialnumber = self.wemo.serialnumber
|
||||||
@ -211,12 +220,12 @@ class WemoHumidifier(FanEntity):
|
|||||||
return WEMO_FAN_SPEED_TO_HASS.get(self._fan_mode)
|
return WEMO_FAN_SPEED_TO_HASS.get(self._fan_mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed_list(self: FanEntity) -> list:
|
def speed_list(self) -> list:
|
||||||
"""Get the list of available speeds."""
|
"""Get the list of available speeds."""
|
||||||
return SUPPORTED_SPEEDS
|
return SUPPORTED_SPEEDS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self: FanEntity) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORTED_FEATURES
|
return SUPPORTED_FEATURES
|
||||||
|
|
||||||
@ -276,22 +285,22 @@ class WemoHumidifier(FanEntity):
|
|||||||
self.name, err)
|
self.name, err)
|
||||||
self._available = False
|
self._available = False
|
||||||
|
|
||||||
def turn_on(self: FanEntity, speed: str = None, **kwargs) -> None:
|
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||||
"""Turn the switch on."""
|
"""Turn the switch on."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
self.wemo.set_state(self._last_fan_on_mode)
|
self.wemo.set_state(self._last_fan_on_mode)
|
||||||
else:
|
else:
|
||||||
self.set_speed(speed)
|
self.set_speed(speed)
|
||||||
|
|
||||||
def turn_off(self: FanEntity, **kwargs) -> None:
|
def turn_off(self, **kwargs) -> None:
|
||||||
"""Turn the switch off."""
|
"""Turn the switch off."""
|
||||||
self.wemo.set_state(WEMO_FAN_OFF)
|
self.wemo.set_state(WEMO_FAN_OFF)
|
||||||
|
|
||||||
def set_speed(self: FanEntity, speed: str) -> None:
|
def set_speed(self, speed: str) -> None:
|
||||||
"""Set the fan_mode of the Humidifier."""
|
"""Set the fan_mode of the Humidifier."""
|
||||||
self.wemo.set_state(HASS_FAN_SPEED_TO_WEMO.get(speed))
|
self.wemo.set_state(HASS_FAN_SPEED_TO_WEMO.get(speed))
|
||||||
|
|
||||||
def set_humidity(self: FanEntity, humidity: float) -> None:
|
def set_humidity(self, humidity: float) -> None:
|
||||||
"""Set the target humidity level for the Humidifier."""
|
"""Set the target humidity level for the Humidifier."""
|
||||||
if humidity < 50:
|
if humidity < 50:
|
||||||
self.wemo.set_humidity(WEMO_HUMIDITY_45)
|
self.wemo.set_humidity(WEMO_HUMIDITY_45)
|
||||||
@ -303,3 +312,7 @@ class WemoHumidifier(FanEntity):
|
|||||||
self.wemo.set_humidity(WEMO_HUMIDITY_60)
|
self.wemo.set_humidity(WEMO_HUMIDITY_60)
|
||||||
elif humidity >= 100:
|
elif humidity >= 100:
|
||||||
self.wemo.set_humidity(WEMO_HUMIDITY_100)
|
self.wemo.set_humidity(WEMO_HUMIDITY_100)
|
||||||
|
|
||||||
|
def reset_filter_life(self) -> None:
|
||||||
|
"""Reset the filter life to 100%."""
|
||||||
|
self.wemo.reset_filter_life()
|
||||||
|
@ -4,9 +4,12 @@ Support for Belkin WeMo lights.
|
|||||||
For more details about this component, please refer to the documentation at
|
For more details about this component, please refer to the documentation at
|
||||||
https://home-assistant.io/components/light.wemo/
|
https://home-assistant.io/components/light.wemo/
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import async_timeout
|
||||||
|
|
||||||
from homeassistant import util
|
from homeassistant import util
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
@ -74,40 +77,52 @@ class WemoLight(Light):
|
|||||||
|
|
||||||
def __init__(self, device, update_lights):
|
def __init__(self, device, update_lights):
|
||||||
"""Initialize the WeMo light."""
|
"""Initialize the WeMo light."""
|
||||||
self.light_id = device.name
|
|
||||||
self.wemo = device
|
self.wemo = device
|
||||||
self.update_lights = update_lights
|
self._state = None
|
||||||
|
self._update_lights = update_lights
|
||||||
|
self._available = True
|
||||||
|
self._update_lock = None
|
||||||
|
self._brightness = None
|
||||||
|
self._hs_color = None
|
||||||
|
self._color_temp = None
|
||||||
|
self._is_on = None
|
||||||
|
self._name = self.wemo.name
|
||||||
|
self._unique_id = self.wemo.uniqueID
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Wemo light added to HASS."""
|
||||||
|
# Define inside async context so we know our event loop
|
||||||
|
self._update_lock = asyncio.Lock()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the ID of this light."""
|
"""Return the ID of this light."""
|
||||||
return self.wemo.uniqueID
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the light."""
|
"""Return the name of the light."""
|
||||||
return self.wemo.name
|
return self._name
|
||||||
|
|
||||||
@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."""
|
||||||
return self.wemo.state.get('level', 255)
|
return self._brightness
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hs_color(self):
|
def hs_color(self):
|
||||||
"""Return the hs color values of this light."""
|
"""Return the hs color values of this light."""
|
||||||
xy_color = self.wemo.state.get('color_xy')
|
return self._hs_color
|
||||||
return color_util.color_xy_to_hs(*xy_color) if xy_color else None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_temp(self):
|
def color_temp(self):
|
||||||
"""Return the color temperature of this light in mireds."""
|
"""Return the color temperature of this light in mireds."""
|
||||||
return self.wemo.state.get('temperature_mireds')
|
return self._color_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
return self.wemo.state['onoff'] != 0
|
return self._is_on
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@ -117,7 +132,7 @@ class WemoLight(Light):
|
|||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return if light is available."""
|
"""Return if light is available."""
|
||||||
return self.wemo.state['available']
|
return self._available
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the light on."""
|
"""Turn the light on."""
|
||||||
@ -145,9 +160,40 @@ class WemoLight(Light):
|
|||||||
transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
|
transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
|
||||||
self.wemo.turn_off(transition=transitiontime)
|
self.wemo.turn_off(transition=transitiontime)
|
||||||
|
|
||||||
def update(self):
|
def _update(self, force_update=True):
|
||||||
"""Synchronize state with bridge."""
|
"""Synchronize state with bridge."""
|
||||||
self.update_lights(no_throttle=True)
|
self._update_lights(no_throttle=force_update)
|
||||||
|
self._state = self.wemo.state
|
||||||
|
|
||||||
|
self._is_on = self._state.get('onoff') != 0
|
||||||
|
self._brightness = self._state.get('level', 255)
|
||||||
|
self._color_temp = self._state.get('temperature_mireds')
|
||||||
|
self._available = True
|
||||||
|
|
||||||
|
xy_color = self._state.get('color_xy')
|
||||||
|
|
||||||
|
if xy_color:
|
||||||
|
self._hs_color = color_util.color_xy_to_hs(*xy_color)
|
||||||
|
else:
|
||||||
|
self._hs_color = None
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Synchronize state with bridge."""
|
||||||
|
# If an update is in progress, we don't do anything
|
||||||
|
if self._update_lock.locked():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with async_timeout.timeout(5):
|
||||||
|
await asyncio.shield(self._async_locked_update(True))
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
_LOGGER.warning('Lost connection to %s', self.name)
|
||||||
|
self._available = False
|
||||||
|
|
||||||
|
async def _async_locked_update(self, force_update):
|
||||||
|
"""Try updating within an async lock."""
|
||||||
|
async with self._update_lock:
|
||||||
|
await self.hass.async_add_executor_job(self._update, force_update)
|
||||||
|
|
||||||
|
|
||||||
class WemoDimmer(Light):
|
class WemoDimmer(Light):
|
||||||
@ -156,46 +202,79 @@ class WemoDimmer(Light):
|
|||||||
def __init__(self, device):
|
def __init__(self, device):
|
||||||
"""Initialize the WeMo dimmer."""
|
"""Initialize the WeMo dimmer."""
|
||||||
self.wemo = device
|
self.wemo = device
|
||||||
self._brightness = None
|
|
||||||
self._state = None
|
self._state = None
|
||||||
|
self._available = True
|
||||||
|
self._update_lock = None
|
||||||
|
self._brightness = None
|
||||||
|
self._model_name = self.wemo.model_name
|
||||||
|
self._name = self.wemo.name
|
||||||
|
self._serialnumber = self.wemo.serialnumber
|
||||||
|
|
||||||
|
def _subscription_callback(self, _device, _type, _params):
|
||||||
|
"""Update the state by the Wemo device."""
|
||||||
|
_LOGGER.debug("Subscription update for %s", self.name)
|
||||||
|
updated = self.wemo.subscription_update(_type, _params)
|
||||||
|
self.hass.add_job(
|
||||||
|
self._async_locked_subscription_callback(not updated))
|
||||||
|
|
||||||
|
async def _async_locked_subscription_callback(self, force_update):
|
||||||
|
"""Handle an update from a subscription."""
|
||||||
|
# If an update is in progress, we don't do anything
|
||||||
|
if self._update_lock.locked():
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._async_locked_update(force_update)
|
||||||
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Register update callback."""
|
"""Wemo dimmer added to HASS."""
|
||||||
wemo = self.hass.components.wemo
|
# Define inside async context so we know our event loop
|
||||||
# The register method uses a threading condition, so call via executor.
|
self._update_lock = asyncio.Lock()
|
||||||
# and await to wait until the task is done.
|
|
||||||
await self.hass.async_add_job(
|
|
||||||
wemo.SUBSCRIPTION_REGISTRY.register, self.wemo)
|
|
||||||
# The on method just appends to a defaultdict list.
|
|
||||||
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)
|
|
||||||
|
|
||||||
def _update_callback(self, _device, _type, _params):
|
registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
|
||||||
"""Update the state by the Wemo device."""
|
await self.hass.async_add_executor_job(registry.register, self.wemo)
|
||||||
_LOGGER.debug("Subscription update for %s", _device)
|
registry.on(self.wemo, None, self._subscription_callback)
|
||||||
updated = self.wemo.subscription_update(_type, _params)
|
|
||||||
self._update(force_update=(not updated))
|
async def async_update(self):
|
||||||
self.schedule_update_ha_state()
|
"""Update WeMo state.
|
||||||
|
|
||||||
|
Wemo has an aggressive retry logic that sometimes can take over a
|
||||||
|
minute to return. If we don't get a state after 5 seconds, assume the
|
||||||
|
Wemo dimmer is unreachable. If update goes through, it will be made
|
||||||
|
available again.
|
||||||
|
"""
|
||||||
|
# If an update is in progress, we don't do anything
|
||||||
|
if self._update_lock.locked():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with async_timeout.timeout(5):
|
||||||
|
await asyncio.shield(self._async_locked_update(True))
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
_LOGGER.warning('Lost connection to %s', self.name)
|
||||||
|
self._available = False
|
||||||
|
self.wemo.reconnect_with_device()
|
||||||
|
|
||||||
|
async def _async_locked_update(self, force_update):
|
||||||
|
"""Try updating within an async lock."""
|
||||||
|
async with self._update_lock:
|
||||||
|
await self.hass.async_add_executor_job(self._update, force_update)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the ID of this WeMo dimmer."""
|
"""Return the ID of this WeMo dimmer."""
|
||||||
return self.wemo.serialnumber
|
return self._serialnumber
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the dimmer if any."""
|
"""Return the name of the dimmer if any."""
|
||||||
return self.wemo.name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_BRIGHTNESS
|
return SUPPORT_BRIGHTNESS
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""No polling needed with subscriptions."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
"""Return the brightness of this light between 1 and 100."""
|
"""Return the brightness of this light between 1 and 100."""
|
||||||
@ -210,11 +289,17 @@ class WemoDimmer(Light):
|
|||||||
"""Update the device state."""
|
"""Update the device state."""
|
||||||
try:
|
try:
|
||||||
self._state = self.wemo.get_state(force_update)
|
self._state = self.wemo.get_state(force_update)
|
||||||
|
|
||||||
wemobrightness = int(self.wemo.get_brightness(force_update))
|
wemobrightness = int(self.wemo.get_brightness(force_update))
|
||||||
self._brightness = int((wemobrightness * 255) / 100)
|
self._brightness = int((wemobrightness * 255) / 100)
|
||||||
|
|
||||||
|
if not self._available:
|
||||||
|
_LOGGER.info('Reconnected to %s', self.name)
|
||||||
|
self._available = True
|
||||||
except AttributeError as err:
|
except AttributeError as err:
|
||||||
_LOGGER.warning("Could not update status for %s (%s)",
|
_LOGGER.warning("Could not update status for %s (%s)",
|
||||||
self.name, err)
|
self.name, err)
|
||||||
|
self._available = False
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the dimmer on."""
|
"""Turn the dimmer on."""
|
||||||
@ -232,3 +317,8 @@ class WemoDimmer(Light):
|
|||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
"""Turn the dimmer off."""
|
"""Turn the dimmer off."""
|
||||||
self.wemo.off()
|
self.wemo.off()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return if dimmer is available."""
|
||||||
|
return self._available
|
||||||
|
@ -64,10 +64,12 @@ class WemoSwitch(SwitchDevice):
|
|||||||
self.maker_params = None
|
self.maker_params = None
|
||||||
self.coffeemaker_mode = None
|
self.coffeemaker_mode = None
|
||||||
self._state = None
|
self._state = None
|
||||||
|
self._mode_string = None
|
||||||
self._available = True
|
self._available = True
|
||||||
self._update_lock = None
|
self._update_lock = None
|
||||||
# look up model name once as it incurs network traffic
|
|
||||||
self._model_name = self.wemo.model_name
|
self._model_name = self.wemo.model_name
|
||||||
|
self._name = self.wemo.name
|
||||||
|
self._serialnumber = self.wemo.serialnumber
|
||||||
|
|
||||||
def _subscription_callback(self, _device, _type, _params):
|
def _subscription_callback(self, _device, _type, _params):
|
||||||
"""Update the state by the Wemo device."""
|
"""Update the state by the Wemo device."""
|
||||||
@ -85,24 +87,15 @@ class WemoSwitch(SwitchDevice):
|
|||||||
await self._async_locked_update(force_update)
|
await self._async_locked_update(force_update)
|
||||||
self.async_schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""Device should poll.
|
|
||||||
|
|
||||||
Subscriptions push the state, however it won't detect if a device
|
|
||||||
is no longer available. Use polling to detect if a device is available.
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the ID of this WeMo switch."""
|
"""Return the ID of this WeMo switch."""
|
||||||
return self.wemo.serialnumber
|
return self._serialnumber
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the switch if any."""
|
"""Return the name of the switch if any."""
|
||||||
return self.wemo.name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
@ -169,7 +162,7 @@ class WemoSwitch(SwitchDevice):
|
|||||||
def detail_state(self):
|
def detail_state(self):
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
if self.coffeemaker_mode is not None:
|
if self.coffeemaker_mode is not None:
|
||||||
return self.wemo.mode_string
|
return self._mode_string
|
||||||
if self.insight_params:
|
if self.insight_params:
|
||||||
standby_state = int(self.insight_params['state'])
|
standby_state = int(self.insight_params['state'])
|
||||||
if standby_state == WEMO_ON:
|
if standby_state == WEMO_ON:
|
||||||
@ -242,6 +235,7 @@ class WemoSwitch(SwitchDevice):
|
|||||||
"""Update the device state."""
|
"""Update the device state."""
|
||||||
try:
|
try:
|
||||||
self._state = self.wemo.get_state(force_update)
|
self._state = self.wemo.get_state(force_update)
|
||||||
|
|
||||||
if self._model_name == 'Insight':
|
if self._model_name == 'Insight':
|
||||||
self.insight_params = self.wemo.insight_params
|
self.insight_params = self.wemo.insight_params
|
||||||
self.insight_params['standby_state'] = (
|
self.insight_params['standby_state'] = (
|
||||||
@ -250,6 +244,7 @@ class WemoSwitch(SwitchDevice):
|
|||||||
self.maker_params = self.wemo.maker_params
|
self.maker_params = self.wemo.maker_params
|
||||||
elif self._model_name == 'CoffeeMaker':
|
elif self._model_name == 'CoffeeMaker':
|
||||||
self.coffeemaker_mode = self.wemo.mode
|
self.coffeemaker_mode = self.wemo.mode
|
||||||
|
self._mode_string = self.wemo.mode_string
|
||||||
|
|
||||||
if not self._available:
|
if not self._available:
|
||||||
_LOGGER.info('Reconnected to %s', self.name)
|
_LOGGER.info('Reconnected to %s', self.name)
|
||||||
|
@ -96,6 +96,8 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# Only register a device once
|
# Only register a device once
|
||||||
if serial in KNOWN_DEVICES:
|
if serial in KNOWN_DEVICES:
|
||||||
|
_LOGGER.debug('Ignoring known device %s %s',
|
||||||
|
service, discovery_info)
|
||||||
return
|
return
|
||||||
_LOGGER.debug('Discovered unique device %s', serial)
|
_LOGGER.debug('Discovered unique device %s', serial)
|
||||||
KNOWN_DEVICES.append(serial)
|
KNOWN_DEVICES.append(serial)
|
||||||
@ -123,6 +125,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
|
_LOGGER.debug("Scanning statically configured WeMo devices...")
|
||||||
for host, port in config.get(DOMAIN, {}).get(CONF_STATIC, []):
|
for host, port in config.get(DOMAIN, {}).get(CONF_STATIC, []):
|
||||||
url = setup_url_for_address(host, port)
|
url = setup_url_for_address(host, port)
|
||||||
|
|
||||||
@ -139,16 +142,19 @@ def setup(hass, config):
|
|||||||
_LOGGER.error('Unable to access %s (%s)', url, err)
|
_LOGGER.error('Unable to access %s (%s)', url, err)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not [d[1] for d in devices
|
||||||
|
if d[1].serialnumber == device.serialnumber]:
|
||||||
devices.append((url, device))
|
devices.append((url, device))
|
||||||
|
|
||||||
if config.get(DOMAIN, {}).get(CONF_DISCOVERY):
|
if config.get(DOMAIN, {}).get(CONF_DISCOVERY):
|
||||||
_LOGGER.debug("Scanning for WeMo devices.")
|
_LOGGER.debug("Scanning for WeMo devices...")
|
||||||
devices.extend(
|
for device in pywemo.discover_devices():
|
||||||
(setup_url_for_device(device), device)
|
if not [d[1] for d in devices
|
||||||
for device in pywemo.discover_devices())
|
if d[1].serialnumber == device.serialnumber]:
|
||||||
|
devices.append((setup_url_for_device(device), device))
|
||||||
|
|
||||||
for url, device in devices:
|
for url, device in devices:
|
||||||
_LOGGER.debug('Adding wemo at %s:%i', device.host, device.port)
|
_LOGGER.debug('Adding WeMo device at %s:%i', device.host, device.port)
|
||||||
|
|
||||||
discovery_info = {
|
discovery_info = {
|
||||||
'model_name': device.model_name,
|
'model_name': device.model_name,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user