diff --git a/homeassistant/components/binary_sensor/ring.py b/homeassistant/components/binary_sensor/ring.py index 1e926f00a2f..e84009301ab 100644 --- a/homeassistant/components/binary_sensor/ring.py +++ b/homeassistant/components/binary_sensor/ring.py @@ -27,7 +27,7 @@ SCAN_INTERVAL = timedelta(seconds=5) # Sensor types: Name, category, device_class SENSOR_TYPES = { - 'ding': ['Ding', ['doorbell', 'stickup_cams'], 'occupancy'], + 'ding': ['Ding', ['doorbell'], 'occupancy'], 'motion': ['Motion', ['doorbell', 'stickup_cams'], 'motion'], } diff --git a/homeassistant/components/camera/ring.py b/homeassistant/components/camera/ring.py index 70569825764..a5e9855bf37 100644 --- a/homeassistant/components/camera/ring.py +++ b/homeassistant/components/camera/ring.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/camera.ring/ import asyncio import logging -from datetime import datetime, timedelta +from datetime import timedelta import voluptuous as vol @@ -23,6 +23,8 @@ CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' DEPENDENCIES = ['ring', 'ffmpeg'] +FORCE_REFRESH_INTERVAL = timedelta(minutes=45) + _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=90) @@ -63,8 +65,8 @@ class RingCam(Camera): self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS) self._last_video_id = self._camera.last_recording_id self._video_url = self._camera.recording_url(self._last_video_id) - self._expires_at = None - self._utcnow = None + self._utcnow = dt_util.utcnow() + self._expires_at = FORCE_REFRESH_INTERVAL + self._utcnow @property def name(self): @@ -123,19 +125,19 @@ class RingCam(Camera): def update(self): """Update camera entity and refresh attributes.""" - # extract the video expiration from URL - x_amz_expires = int(self._video_url.split('&')[0].split('=')[-1]) - x_amz_date = self._video_url.split('&')[1].split('=')[-1] + _LOGGER.debug("Checking if Ring DoorBell needs to refresh video_url") + self._camera.update() self._utcnow = dt_util.utcnow() - self._expires_at = \ - timedelta(seconds=x_amz_expires) + \ - dt_util.as_utc(datetime.strptime(x_amz_date, "%Y%m%dT%H%M%SZ")) - if self._last_video_id != self._camera.last_recording_id: - _LOGGER.debug("Updated Ring DoorBell last_video_id") + last_recording_id = self._camera.last_recording_id + + if self._last_video_id != last_recording_id or \ + self._utcnow >= self._expires_at: + + _LOGGER.info("Ring DoorBell properties refreshed") + + # update attributes if new video or if URL has expired self._last_video_id = self._camera.last_recording_id - - if self._utcnow >= self._expires_at: - _LOGGER.debug("Updated Ring DoorBell video_url") self._video_url = self._camera.recording_url(self._last_video_id) + self._expires_at = FORCE_REFRESH_INTERVAL + self._utcnow diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index 598cd22c986..1bf7d632af5 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -4,15 +4,13 @@ Support for Tellstick lights. 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 @@ class TellstickLight(TellstickDevice, Light): 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/sensor/ring.py b/homeassistant/components/sensor/ring.py index 6c8794d096f..cae7690103d 100644 --- a/homeassistant/components/sensor/ring.py +++ b/homeassistant/components/sensor/ring.py @@ -34,7 +34,7 @@ SENSOR_TYPES = { 'Last Activity', ['doorbell', 'stickup_cams'], None, 'history', None], 'last_ding': [ - 'Last Ding', ['doorbell', 'stickup_cams'], None, 'history', 'ding'], + 'Last Ding', ['doorbell'], None, 'history', 'ding'], 'last_motion': [ 'Last Motion', ['doorbell', 'stickup_cams'], None, diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index de7a3bf4545..ae19e77c2e5 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -4,16 +4,11 @@ Support for Tellstick switches. 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 85407ff4c7a..91a7c0c69e5 100644 --- a/homeassistant/components/tellstick.py +++ b/homeassistant/components/tellstick.py @@ -4,12 +4,14 @@ Tellstick Component. 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 @@ CONF_SIGNAL_REPETITIONS = 'signal_repetitions' 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,94 +88,57 @@ def setup(hass, config): 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)]) + [device.id for device in tellcore_devices + if device.methods(TELLSTICK_DIM)]) + + @callback + def async_handle_callback(tellcore_id, tellcore_command, + tellcore_data, cid): + """Handle the actual callback from Tellcore.""" + hass.helpers.dispatcher.async_dispatcher_send( + SIGNAL_TELLCORE_CALLBACK, tellcore_id, + tellcore_command, tellcore_data) + + # 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) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, clean_up_callback) 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) - - def _tellcore_event_callback(self, 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) - - def _setup_tellcore_callback(self, hass, tellcore_lib): - """Register the callback handler.""" - callback_id = tellcore_lib.register_device_event( - self._tellcore_event_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") - - 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) - - class TellstickDevice(Entity): """Representation of a Tellstick device. 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 @@ class TellstickDevice(Entity): 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 @@ class TellstickDevice(Entity): 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 @@ class TellstickDevice(Entity): def update(self): """Poll the current state of the device.""" self._update_from_tellcore() - self.schedule_update_ha_state() diff --git a/homeassistant/const.py b/homeassistant/const.py index 64ccc1cc395..fc471c6323d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 57 -PATCH_VERSION = '2' +PATCH_VERSION = '3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 4, 2) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7da87160684..a2dc9572c81 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -6,6 +6,7 @@ jinja2>=2.9.6 voluptuous==0.10.5 typing>=3,<4 aiohttp==2.2.5 +yarl==0.13 async_timeout==2.0.0 chardet==3.0.4 astral==1.4 diff --git a/requirements_all.txt b/requirements_all.txt index a2aa2bb1057..fb22149e54d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -7,6 +7,7 @@ jinja2>=2.9.6 voluptuous==0.10.5 typing>=3,<4 aiohttp==2.2.5 +yarl==0.13 async_timeout==2.0.0 chardet==3.0.4 astral==1.4 diff --git a/setup.py b/setup.py index cd7043650ad..3eb636d0801 100755 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ REQUIRES = [ 'voluptuous==0.10.5', 'typing>=3,<4', 'aiohttp==2.2.5', + 'yarl==0.13', # Update this whenever you update aiohttp 'async_timeout==2.0.0', 'chardet==3.0.4', 'astral==1.4',