From 6ca2c4da3a6690254ca30c8ec66e38f1a5abc4b4 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 6 Apr 2020 15:17:45 -0600 Subject: [PATCH] =?UTF-8?q?Properly=20demarcate=20websocket=20and=20REST?= =?UTF-8?q?=20API=20callbacks=20in=20SimpliS=E2=80=A6=20(#33706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Properly demarcate websocket and REST API callbacks in SimpliSafe * Docstring * Method names * Cleanup * Remove spurious logging * Remove redundant method * Fix comment * Code review --- .../components/simplisafe/__init__.py | 174 ++++++++---------- 1 file changed, 79 insertions(+), 95 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 04c00171b43..53a844ccb9a 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -63,7 +63,8 @@ _LOGGER = logging.getLogger(__name__) CONF_ACCOUNTS = "accounts" DATA_LISTENER = "listener" -TOPIC_UPDATE = "simplisafe_update_data_{0}" +TOPIC_UPDATE_REST_API = "simplisafe_update_rest_api_{0}" +TOPIC_UPDATE_WEBSOCKET = "simplisafe_update_websocket_{0}" EVENT_SIMPLISAFE_EVENT = "SIMPLISAFE_EVENT" EVENT_SIMPLISAFE_NOTIFICATION = "SIMPLISAFE_NOTIFICATION" @@ -354,7 +355,6 @@ class SimpliSafeWebsocket: """Initialize.""" self._hass = hass self._websocket = websocket - self.last_events = {} @staticmethod def _on_connect(): @@ -369,8 +369,9 @@ class SimpliSafeWebsocket: def _on_event(self, event): """Define a handler to fire when a new SimpliSafe event arrives.""" _LOGGER.debug("New websocket event: %s", event) - self.last_events[event.system_id] = event - async_dispatcher_send(self._hass, TOPIC_UPDATE.format(event.system_id)) + async_dispatcher_send( + self._hass, TOPIC_UPDATE_WEBSOCKET.format(event.system_id), event + ) if event.event_type not in WEBSOCKET_EVENTS_TO_TRIGGER_HASS_EVENT: return @@ -491,7 +492,9 @@ class SimpliSafe: await system.update() self._async_process_new_notifications(system) _LOGGER.debug('Updated REST API data for "%s"', system.address) - async_dispatcher_send(self._hass, TOPIC_UPDATE.format(system.system_id)) + async_dispatcher_send( + self._hass, TOPIC_UPDATE_REST_API.format(system.system_id) + ) tasks = [update_system(system) for system in self.systems.values()] @@ -538,8 +541,6 @@ class SimpliSafeEntity(Entity): def __init__(self, simplisafe, system, name, *, serial=None): """Initialize.""" - self._async_unsub_dispatcher_connect = None - self._last_processed_websocket_event = None self._name = name self._online = True self._simplisafe = simplisafe @@ -606,90 +607,9 @@ class SimpliSafeEntity(Entity): """Return the unique ID of the entity.""" return self._serial - @callback - def _async_should_ignore_websocket_event(self, event): - """Return whether this entity should ignore a particular websocket event. - - Note that we can't check for a final condition – whether the event belongs to - a particular entity, like a lock – because some events (like arming the system - from a keypad _or_ from the website) should impact the same entity. - """ - # We've already processed this event: - if self._last_processed_websocket_event == event: - return True - - # This is an event for a system other than the one this entity belongs to: - if event.system_id != self._system.system_id: - return True - - # This isn't an event that this entity cares about: - if event.event_type not in self.websocket_events_to_listen_for: - return True - - # This event is targeted at a specific entity whose serial number is different - # from this one's: - if ( - event.event_type in WEBSOCKET_EVENTS_REQUIRING_SERIAL - and event.sensor_serial != self._serial - ): - return True - - return False - - async def async_added_to_hass(self): - """Register callbacks.""" - - @callback - def update(): - """Update the state.""" - self.update_from_latest_data() - self.async_write_ha_state() - - self._async_unsub_dispatcher_connect = async_dispatcher_connect( - self.hass, TOPIC_UPDATE.format(self._system.system_id), update - ) - - self.update_from_latest_data() - - @callback - def update_from_latest_data(self): - """Update the entity.""" - self.async_update_from_rest_api() - - last_websocket_event = self._simplisafe.websocket.last_events.get( - self._system.system_id - ) - - if self._async_should_ignore_websocket_event(last_websocket_event): - return - - self._last_processed_websocket_event = last_websocket_event - - if last_websocket_event.sensor_type: - sensor_type = last_websocket_event.sensor_type.name - else: - sensor_type = None - - self._attrs.update( - { - ATTR_LAST_EVENT_INFO: last_websocket_event.info, - ATTR_LAST_EVENT_SENSOR_NAME: last_websocket_event.sensor_name, - ATTR_LAST_EVENT_SENSOR_TYPE: sensor_type, - ATTR_LAST_EVENT_TIMESTAMP: last_websocket_event.timestamp, - } - ) - self._async_internal_update_from_websocket_event(last_websocket_event) - - @callback - def async_update_from_rest_api(self): - """Update the entity with the provided REST API data.""" - @callback def _async_internal_update_from_websocket_event(self, event): - """Check for connection events and set offline appropriately. - - Should not be called directly. - """ + """Perform internal websocket handling prior to handing off.""" if event.event_type == EVENT_CONNECTION_LOST: self._online = False elif event.event_type == EVENT_CONNECTION_RESTORED: @@ -701,13 +621,77 @@ class SimpliSafeEntity(Entity): if not self._online: return + if event.sensor_type: + sensor_type = event.sensor_type.name + else: + sensor_type = None + + self._attrs.update( + { + ATTR_LAST_EVENT_INFO: event.info, + ATTR_LAST_EVENT_SENSOR_NAME: event.sensor_name, + ATTR_LAST_EVENT_SENSOR_TYPE: sensor_type, + ATTR_LAST_EVENT_TIMESTAMP: event.timestamp, + } + ) + self.async_update_from_websocket_event(event) + async def async_added_to_hass(self): + """Register callbacks.""" + + @callback + def rest_api_update(): + """Update the entity with new REST API data.""" + self.async_update_from_rest_api() + self.async_write_ha_state() + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + TOPIC_UPDATE_REST_API.format(self._system.system_id), + rest_api_update, + ) + ) + + @callback + def websocket_update(event): + """Update the entity with new websocket data.""" + # Ignore this event if it belongs to a system other than this one: + if event.system_id != self._system.system_id: + return + + # Ignore this event if this entity hasn't expressed interest in its type: + if event.event_type not in self.websocket_events_to_listen_for: + return + + # Ignore this event if it belongs to a entity with a different serial + # number from this one's: + if ( + event.event_type in WEBSOCKET_EVENTS_REQUIRING_SERIAL + and event.sensor_serial != self._serial + ): + return + + self._async_internal_update_from_websocket_event(event) + self.async_write_ha_state() + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + TOPIC_UPDATE_WEBSOCKET.format(self._system.system_id), + websocket_update, + ) + ) + + self.async_update_from_rest_api() + + @callback + def async_update_from_rest_api(self): + """Update the entity with the provided REST API data.""" + raise NotImplementedError() + @callback def async_update_from_websocket_event(self, event): - """Update the entity with the provided websocket API data.""" - - async def async_will_remove_from_hass(self) -> None: - """Disconnect dispatcher listener when removed.""" - if self._async_unsub_dispatcher_connect: - self._async_unsub_dispatcher_connect() + """Update the entity with the provided websocket event.""" + raise NotImplementedError()