From 62c1b542edf2ff24baf1749c57293af94c8413e5 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Thu, 9 Nov 2017 15:03:35 +0100 Subject: [PATCH] Tellstick Duo acync callback fix (#10384) * Reverted commit 1c8f1796903d06786060c53b48f07733708853a1. This fixes issue: #10329 * convert callback to async * fix lint * cleanup * cleanup * cleanups * optimize initial handling * Update tellstick.py * Update tellstick.py * fix lint * fix lint * Update tellstick.py * Fixed code errors and lint problems. * fix bug * Reduce logic, migrate to dispatcher * Update tellstick.py * Update tellstick.py * fix lint * fix lint --- homeassistant/components/light/tellstick.py | 20 ++-- homeassistant/components/switch/tellstick.py | 22 ++-- homeassistant/components/tellstick.py | 118 ++++++++----------- 3 files changed, 64 insertions(+), 96 deletions(-) diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index 598cd22c9860b6..1bf7d632af5fc1 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -4,15 +4,13 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.tellstick/ """ -import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) from homeassistant.components.tellstick import ( DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG, - DOMAIN, TellstickDevice) + DATA_TELLSTICK, TellstickDevice) -PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): DOMAIN}) SUPPORT_TELLSTICK = SUPPORT_BRIGHTNESS @@ -27,17 +25,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): signal_repetitions = discovery_info.get( ATTR_DISCOVER_CONFIG, DEFAULT_SIGNAL_REPETITIONS) - add_devices(TellstickLight(tellcore_id, hass.data['tellcore_registry'], - signal_repetitions) - for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]) + add_devices([TellstickLight(hass.data[DATA_TELLSTICK][tellcore_id], + signal_repetitions) + for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]], + True) class TellstickLight(TellstickDevice, Light): """Representation of a Tellstick light.""" - def __init__(self, tellcore_id, tellcore_registry, signal_repetitions): + def __init__(self, tellcore_device, signal_repetitions): """Initialize the Tellstick light.""" - super().__init__(tellcore_id, tellcore_registry, signal_repetitions) + super().__init__(tellcore_device, signal_repetitions) self._brightness = 255 @@ -57,9 +56,8 @@ def _parse_ha_data(self, kwargs): def _parse_tellcore_data(self, tellcore_data): """Turn the value received from tellcore into something useful.""" - if tellcore_data is not None: - brightness = int(tellcore_data) - return brightness + if tellcore_data: + return int(tellcore_data) # brightness return None def _update_model(self, new_state, data): diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index de7a3bf454567e..ae19e77c2e5a92 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -4,16 +4,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.tellstick/ """ -import voluptuous as vol - -from homeassistant.components.tellstick import (DEFAULT_SIGNAL_REPETITIONS, - ATTR_DISCOVER_DEVICES, - ATTR_DISCOVER_CONFIG, - DOMAIN, TellstickDevice) +from homeassistant.components.tellstick import ( + DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, + ATTR_DISCOVER_CONFIG, DATA_TELLSTICK, TellstickDevice) from homeassistant.helpers.entity import ToggleEntity -PLATFORM_SCHEMA = vol.Schema({vol.Required("platform"): DOMAIN}) - # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): @@ -26,9 +21,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): signal_repetitions = discovery_info.get(ATTR_DISCOVER_CONFIG, DEFAULT_SIGNAL_REPETITIONS) - add_devices(TellstickSwitch(tellcore_id, hass.data['tellcore_registry'], - signal_repetitions) - for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]) + add_devices([TellstickSwitch(hass.data[DATA_TELLSTICK][tellcore_id], + signal_repetitions) + for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]], + True) class TellstickSwitch(TellstickDevice, ToggleEntity): @@ -36,11 +32,11 @@ class TellstickSwitch(TellstickDevice, ToggleEntity): def _parse_ha_data(self, kwargs): """Turn the value from HA into something useful.""" - return None + pass def _parse_tellcore_data(self, tellcore_data): """Turn the value received from tellcore into something useful.""" - return None + pass def _update_model(self, new_state, data): """Update the device entity state to match the arguments.""" diff --git a/homeassistant/components/tellstick.py b/homeassistant/components/tellstick.py index 85407ff4c7a035..91a7c0c69e5157 100644 --- a/homeassistant/components/tellstick.py +++ b/homeassistant/components/tellstick.py @@ -4,12 +4,14 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/tellstick/ """ +import asyncio import logging import threading import voluptuous as vol from homeassistant.helpers import discovery +from homeassistant.core import callback from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT) from homeassistant.helpers.entity import Entity @@ -26,6 +28,9 @@ DEFAULT_SIGNAL_REPETITIONS = 1 DOMAIN = 'tellstick' +DATA_TELLSTICK = 'tellstick_device' +SIGNAL_TELLCORE_CALLBACK = 'tellstick_callback' + # Use a global tellstick domain lock to avoid getting Tellcore errors when # calling concurrently. TELLSTICK_LOCK = threading.RLock() @@ -62,7 +67,7 @@ def _discover(hass, config, component_name, found_tellcore_devices): def setup(hass, config): """Set up the Tellstick component.""" from tellcore.constants import TELLSTICK_DIM - from tellcore.telldus import QueuedCallbackDispatcher + from tellcore.telldus import AsyncioCallbackDispatcher from tellcore.telldus import TelldusCore from tellcorenet import TellCoreClient @@ -83,85 +88,48 @@ def stop_tellcore_net(event): try: tellcore_lib = TelldusCore( - callback_dispatcher=QueuedCallbackDispatcher()) + callback_dispatcher=AsyncioCallbackDispatcher(hass.loop)) except OSError: _LOGGER.exception("Could not initialize Tellstick") return False # Get all devices, switches and lights alike - all_tellcore_devices = tellcore_lib.devices() + tellcore_devices = tellcore_lib.devices() # Register devices - tellcore_registry = TellstickRegistry(hass, tellcore_lib) - tellcore_registry.register_tellcore_devices(all_tellcore_devices) - hass.data['tellcore_registry'] = tellcore_registry + hass.data[DATA_TELLSTICK] = {device.id: device for + device in tellcore_devices} # Discover the switches _discover(hass, config, 'switch', - [tellcore_device.id for tellcore_device in all_tellcore_devices - if not tellcore_device.methods(TELLSTICK_DIM)]) + [device.id for device in tellcore_devices + if not device.methods(TELLSTICK_DIM)]) # Discover the lights _discover(hass, config, 'light', - [tellcore_device.id for tellcore_device in all_tellcore_devices - if tellcore_device.methods(TELLSTICK_DIM)]) - - return True - - -class TellstickRegistry(object): - """Handle everything around Tellstick callbacks. - - Keeps a map device ids to the tellcore device object, and - another to the HA device objects (entities). - - Also responsible for registering / cleanup of callbacks, and for - dispatching the callbacks to the corresponding HA device object. - - All device specific logic should be elsewhere (Entities). - """ - - def __init__(self, hass, tellcore_lib): - """Initialize the Tellstick mappings and callbacks.""" - # used when map callback device id to ha entities. - self._id_to_ha_device_map = {} - self._id_to_tellcore_device_map = {} - self._setup_tellcore_callback(hass, tellcore_lib) + [device.id for device in tellcore_devices + if device.methods(TELLSTICK_DIM)]) - def _tellcore_event_callback(self, tellcore_id, tellcore_command, - tellcore_data, cid): + @callback + def async_handle_callback(tellcore_id, tellcore_command, + tellcore_data, cid): """Handle the actual callback from Tellcore.""" - ha_device = self._id_to_ha_device_map.get(tellcore_id, None) - if ha_device is not None: - # Pass it on to the HA device object - ha_device.update_from_callback(tellcore_command, tellcore_data) + hass.helpers.dispatcher.async_dispatcher_send( + SIGNAL_TELLCORE_CALLBACK, tellcore_id, + tellcore_command, tellcore_data) - def _setup_tellcore_callback(self, hass, tellcore_lib): - """Register the callback handler.""" - callback_id = tellcore_lib.register_device_event( - self._tellcore_event_callback) + # Register callback + callback_id = tellcore_lib.register_device_event( + async_handle_callback) - def clean_up_callback(event): - """Unregister the callback bindings.""" - if callback_id is not None: - tellcore_lib.unregister_callback(callback_id) - _LOGGER.debug("Tellstick callback unregistered") + def clean_up_callback(event): + """Unregister the callback bindings.""" + if callback_id is not None: + tellcore_lib.unregister_callback(callback_id) - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, clean_up_callback) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, clean_up_callback) - def register_ha_device(self, tellcore_id, ha_device): - """Register a new HA device to receive callback updates.""" - self._id_to_ha_device_map[tellcore_id] = ha_device - - def register_tellcore_devices(self, tellcore_devices): - """Register a list of devices.""" - self._id_to_tellcore_device_map.update( - {tellcore_device.id: tellcore_device for tellcore_device - in tellcore_devices}) - - def get_tellcore_device(self, tellcore_id): - """Return a device by tellcore_id.""" - return self._id_to_tellcore_device_map.get(tellcore_id, None) + return True class TellstickDevice(Entity): @@ -170,7 +138,7 @@ class TellstickDevice(Entity): Contains the common logic for all Tellstick devices. """ - def __init__(self, tellcore_id, tellcore_registry, signal_repetitions): + def __init__(self, tellcore_device, signal_repetitions): """Init the Tellstick device.""" self._signal_repetitions = signal_repetitions self._state = None @@ -179,13 +147,16 @@ def __init__(self, tellcore_id, tellcore_registry, signal_repetitions): self._repeats_left = 0 # Look up our corresponding tellcore device - self._tellcore_device = tellcore_registry.get_tellcore_device( - tellcore_id) - self._name = self._tellcore_device.name - # Query tellcore for the current state - self._update_from_tellcore() - # Add ourselves to the mapping for callbacks - tellcore_registry.register_ha_device(tellcore_id, self) + self._tellcore_device = tellcore_device + self._name = tellcore_device.name + + @asyncio.coroutine + def async_added_to_hass(self): + """Register callbacks.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_TELLCORE_CALLBACK, + self.update_from_callback + ) @property def should_poll(self): @@ -275,15 +246,19 @@ def _update_model_from_command(self, tellcore_command, tellcore_data): self._update_model(tellcore_command != TELLSTICK_TURNOFF, self._parse_tellcore_data(tellcore_data)) - def update_from_callback(self, tellcore_command, tellcore_data): + def update_from_callback(self, tellcore_id, tellcore_command, + tellcore_data): """Handle updates from the tellcore callback.""" + if tellcore_id != self._tellcore_device.id: + return + self._update_model_from_command(tellcore_command, tellcore_data) self.schedule_update_ha_state() # This is a benign race on _repeats_left -- it's checked with the lock # in _send_repeated_command. if self._repeats_left > 0: - self.hass.async_add_job(self._send_repeated_command) + self._send_repeated_command() def _update_from_tellcore(self): """Read the current state of the device from the tellcore library.""" @@ -303,4 +278,3 @@ def _update_from_tellcore(self): def update(self): """Poll the current state of the device.""" self._update_from_tellcore() - self.schedule_update_ha_state()