async def _update_activity_callback(self, message: dict = None) -> None: """Update current activity when changed.""" _LOGGER.debug("%s: New activity was started", self.name) new_activity = None message_data = message.get('data') if message_data is not None: new_activity = int(message_data.get('activityId')) if new_activity is None: await self._get_current_activity() return self._current_activity_id = new_activity _LOGGER.debug("%s: New activity: %s(%s)", self.name, self.get_activity_name(self._current_activity_id), self._current_activity_id) # If we were provided a callback handler then call it now. if self._callbacks.new_activity: call_callback(callback_handler=self._callbacks.new_activity, result=(self._current_activity_id, self.get_activity_name( self._current_activity_id)), callback_uuid=self._ip_address, callback_name='new_activity_callback')
async def refresh_info_from_hub(self) -> None: _LOGGER.debug("%s: Retrieving HUB information", self.name) async with self._sync_lck: try: # Retrieve configuration and HUB version config. with timeout(DEFAULT_TIMEOUT): await self._get_config() except asyncio.TimeoutError: _LOGGER.error("%s: Timeout trying to retrieve configuraton.", self.name) raise aioexc.TimeOut try: # Retrieve current activity, done only once config received. with timeout(DEFAULT_TIMEOUT): await self._get_current_activity() except asyncio.TimeoutError: _LOGGER.error( "%s: Timeout trying to retrieve current " "activity.", self.name) return # If we were provided a callback handler then call it now. if self._callbacks.config_updated: call_callback(callback_handler=self._callbacks.config_updated, result=self._hub_config.config, callback_uuid=self._ip_address, callback_name='config_updated_callback')
async def connect(self) -> bool: """ :return: True if connection was successful, False if it was not. :rtype: bool :raises: :class:`~aioharmony.exceptions.TimeOut` """ if self._hub_connection is None: if not await self._websocket_or_xmpp(): return False try: with timeout(DEFAULT_TIMEOUT): if not await self._hub_connection.hub_connect(): return False except asyncio.TimeoutError: raise aioexc.TimeOut # Initiate a sync. That will then result in our notification handler # to receive the response and set our current config version # accordingly. results = await asyncio.gather( self.send_to_hub(command='get_current_state'), self.refresh_info_from_hub(), return_exceptions=True) for idx, result in enumerate(results): if isinstance(result, Exception): if not isinstance(result, aioexc.TimeOut): raise result if idx == 0: _LOGGER.error("%s: Timeout trying to sync hub.", self.name) continue if idx == 0: resp_data = result.get('data') if resp_data is not None: self._hub_config = self._hub_config._replace( config_version=resp_data.get('configVersion')) self._hub_config = self._hub_config._replace( info=resp_data) _LOGGER.debug("%s: HUB configuration version is: %s", self.name, self._hub_config.config_version) if self._hub_connection.callbacks.connect is None and \ self._callbacks.connect is not None: # First time call, add the callback handler now and run it. _LOGGER.debug("%s, calling connect callback for first time", self.name) call_callback(callback_handler=self._callbacks.connect, result=self._ip_address, callback_uuid=self._ip_address, callback_name='connected') self._hub_connection.callbacks = ConnectorCallbackType( self._callbacks.connect, self._callbacks.disconnect) return True
def _connected_handler(self, _) -> None: """Call handler for connection.""" self._connected = True call_callback(callback_handler=self._callbacks.connect, result=self._ip_address, callback_uuid=self._ip_address, callback_name='connected' )
async def _get_current_activity(self) -> bool: """Update current activity when changed.""" _LOGGER.debug("%s: Retrieving current activity", self.name) # Send the command to the HUB try: with timeout(DEFAULT_TIMEOUT / 2): response = await self.send_to_hub( command='get_current_activity', send_timeout=DEFAULT_TIMEOUT / 4) except (asyncio.TimeoutError, aioexc.TimeOut): _LOGGER.debug( "%s: Timeout trying to retrieve current activity, retrying.", self.name) try: with timeout(DEFAULT_TIMEOUT / 2): response = await self.send_to_hub( command='get_current_activity', send_timeout=DEFAULT_TIMEOUT / 4) except (asyncio.TimeoutError, aioexc.TimeOut): _LOGGER.error( "%s: Timeout trying to retrieve current activity.", self.name) response = None if not response: # There was an issue return False if response.get('code') != 200: _LOGGER.error( "%s: Incorrect status code %s received trying to get" "current activity for %s", self.name, response.get('code'), self._ip_address) return False self._current_activity_id = int(response['data']['result']) _LOGGER.debug("%s: Current activity: %s(%s)", self.name, self.get_activity_name(self._current_activity_id), self._current_activity_id) # If we were provided a callback handler then call it now. if self._callbacks.new_activity: _LOGGER.debug("%s: Calling callback handler for new_activity", self.name) call_callback(callback_handler=self._callbacks.new_activity, result=(self._current_activity_id, self.get_activity_name( activity_id=self._current_activity_id)), callback_uuid=self._ip_address, callback_name='new_activity_callback') return True
async def _reconnect(self) -> None: """Perform reconnect to HUB if connection failed""" call_callback(callback_handler=self._callbacks.disconnect, result=self._ip_address, callback_uuid=self._ip_address, callback_name='disconnected') if not self._connected: _LOGGER.debug( "%s: Connection was closed through " "disconnect, not reconnecting", self._ip_address) return if not self._auto_reconnect: _LOGGER.debug("%s: Connection closed, auto-reconnect disabled", self._ip_address) return _LOGGER.debug("%s: Connection closed, reconnecting", self._ip_address) async with self._connect_disconnect_lock: # It is possible that the web socket hasn't been closed yet, # if this is the case then close it now. if self._websocket is not None and not self._websocket.closed: _LOGGER.debug("%s: Web Socket half-closed, closing first", self._ip_address) with suppress(asyncio.TimeoutError), timeout(DEFAULT_TIMEOUT): await self._websocket.close() if self._aiohttp_session is not None and not \ self._aiohttp_session.closed: _LOGGER.debug("%s: Closing sessions", self._ip_address) with suppress(asyncio.TimeoutError), timeout(DEFAULT_TIMEOUT): await self._aiohttp_session.close() # Set web socket to none allowing for reconnect. self._websocket = None self._aiohttp_session = None is_reconnect = False self._connected = False sleep_time = 1 await asyncio.sleep(sleep_time) while not await self.hub_connect(is_reconnect=is_reconnect): await asyncio.sleep(sleep_time) sleep_time = sleep_time * 2 sleep_time = min(sleep_time, 30) is_reconnect = True
async def refresh_info_from_hub(self) -> None: _LOGGER.debug("%s: Retrieving HUB information", self.name) async with self._sync_lck: try: # Retrieve configuration and HUB version config. with timeout(DEFAULT_TIMEOUT * 4): results = await asyncio.gather(self._get_config(), self._retrieve_hub_info(), return_exceptions=True) except asyncio.TimeoutError: _LOGGER.error("%s: Timeout trying to retrieve configuraton.", self.name) raise aioexc.TimeOut for idx, result in enumerate(results): if isinstance(result, aioexc.TimeOut): # Timeout exception, just put out error then. if idx == 0: result_name = 'config' else: result_name = 'hub info' _LOGGER.error("%s: Timeout trying to retrieve %s.", self.name, result_name) return elif isinstance(result, Exception): # Other exception, raise it. raise result try: # Retrieve current activity, done only once config received. with timeout(DEFAULT_TIMEOUT): await self._get_current_activity() except asyncio.TimeoutError: _LOGGER.error( "%s: Timeout trying to retrieve current " "activity.", self.name) return # If we were provided a callback handler then call it now. if self._callbacks.config_updated: _LOGGER.debug("%s: Calling callback handler for config_updated", self.name) call_callback(callback_handler=self._callbacks.config_updated, result=self._hub_config.config, callback_uuid=self._ip_address, callback_name='config_updated_callback')
async def _disconnected_handler(self, _) -> None: """Perform reconnect to HUB if connection failed""" call_callback(callback_handler=self._callbacks.disconnect, result=self._ip_address, callback_uuid=self._ip_address, callback_name='disconnected' ) if not self._connected: _LOGGER.debug("%s: Connection was closed through " "disconnect, not reconnecting", self._ip_address) return if not self._auto_reconnect: _LOGGER.debug("%s: Connection closed, auto-reconnect disabled", self._ip_address) return _LOGGER.debug("%s: Connection closed, reconnecting", self._ip_address) self._connected = False is_reconnect = False self._deregister_handlers() self._init_super() sleep_time = 1 await asyncio.sleep(sleep_time) while True: try: if await self.hub_connect(is_reconnect=is_reconnect): # Exit loop if connected. break except IqTimeout: pass finally: # Wait and try again. await asyncio.sleep(sleep_time) sleep_time = sleep_time * 2 sleep_time = min(sleep_time, 30) is_reconnect = True
async def _update_start_activity_callback(self, message: dict = None) -> None: """Update current activity when changed.""" _LOGGER.debug("%s: New activity starting notification", self.name) message_data = message.get('data') if message_data is not None and message_data.get( 'activityStatus') == 0: # The HUB sends a power off notification again that it is starting when it is done # thus intercepting this so we do not redo the callback. if int(message_data.get( 'activityId')) == -1 and self._current_activity_id == -1: return self._current_activity_id = -1 _LOGGER.debug("%s: Powering off from activity: %s(%s)", self.name, self.get_activity_name(self._current_activity_id), self._current_activity_id) self._current_activity_id = -1 else: if message_data is not None: self._current_activity_id = int(message_data.get('activityId')) else: self._current_activity_id = None _LOGGER.debug("%s: New activity starting: %s(%s)", self.name, self.get_activity_name(self._current_activity_id), self._current_activity_id) # If we were provided a callback handler then call it now. if self._callbacks.new_activity_starting: _LOGGER.debug( "%s: Calling callback handler for new_activity_starting", self.name) call_callback( callback_handler=self._callbacks.new_activity_starting, result=(self._current_activity_id, self.get_activity_name(self._current_activity_id)), callback_uuid=self._ip_address, callback_name='new_activity_starting_callback')
async def hub_connect(self, is_reconnect: bool = False) -> bool: """Connect to Hub Web Socket""" # Acquire the lock. if self._connect_disconnect_lock.locked(): _LOGGER.debug("%s: Waiting for other connect", self._ip_address) async with self._connect_disconnect_lock: # Return connected if we are already connected. if self._websocket is not None and not self._websocket.closed: return True _LOGGER.debug("%s: Starting connect.", self._ip_address) if is_reconnect: log_level = 10 else: log_level = 40 if await self._get_remote_id() is None: # No remote ID means no connect. _LOGGER.log(log_level, "%s: Unable to retrieve HUB id", self._ip_address) return False _LOGGER.debug("%s: Connecting for hub %s", self._ip_address, self._remote_id) try: self._websocket = await self._session.ws_connect( 'ws://{}:{}/?domain={}&hubId={}'.format( self._ip_address, DEFAULT_HUB_PORT, self._domain, self._remote_id), heartbeat=10) except (aiohttp.ServerTimeoutError, aiohttp.ClientError, aiohttp.WSServerHandshakeError) as exc: if isinstance(exc, aiohttp.ServerTimeoutError): _LOGGER.log(log_level, "%s: Connection timed out for hub %s", self._ip_address, self._remote_id) elif isinstance(exc, aiohttp.ClientError): _LOGGER.log( log_level, "%s: Exception trying to establish web " "socket connection for hub %s: %s", self._ip_address, self._remote_id, exc) else: _LOGGER.log( log_level, "%s: Invalid status code %s received " "trying to connect for hub %s: %s", self._ip_address, exc.status, self._remote_id, exc) self._websocket = None return False _LOGGER.debug("%s: Connected to hub %s", self._ip_address, self._remote_id) # Now put the listener on the loop. if not self._listener_task: self._listener_task = asyncio.ensure_future( self._listener(self._websocket)) # Set connected to True, disconnect sets this to False to # prevent automatic reconnect when disconnect is explicitly called self._connected = True call_callback(callback_handler=self._callbacks.connect, result=self._ip_address, callback_uuid=self._ip_address, callback_name='connected') _LOGGER.debug("%s: Connected to hub", self._ip_address) return True
async def _callback_handler(self) -> None: """ Listens on the queue for JSON messages and then processes them by calling any handler(s) """ _LOGGER.debug("%s: Callback handler started", self._name) while True: # Put everything here in a try block, we do not want this # to stop running out due to an exception. try: # Wait for something to appear on the queue. message = await self._message_queue.get() _LOGGER.debug("%s: Message received: %s", self._name, message) # Go through list and call for handler in self._get_handlers(message=message): # Make sure handler hasn't expired yet. if self._unregister_expired_handlers( single_handler=handler): # Was expired and now removed, go on with next one. continue call_callback( callback_handler=handler.handler.handler_obj, result=message, callback_uuid=handler.handler_uuid, callback_name=handler.handler.handler_name ) # Remove the handler from the list if it was only to be # called once. if handler.handler.once: self.unregister_handler(handler.handler_uuid) # Go through all handlers and remove expired ones IF # currently # nothing in the queue. if self._message_queue.empty(): # Go through list and remove all expired ones. _LOGGER.debug("%s: Checking for expired handlers", self._name ) self._unregister_expired_handlers() except asyncio.CancelledError: _LOGGER.debug("%s: Received STOP for callback handler", self._name ) break # Need to catch everything here to prevent an issue in a # from causing the handler to exit. except Exception as exc: _LOGGER.exception("%s: Exception in callback handler: %s", self._name, exc) # Reset the queue. self._message_queue = asyncio.Queue() _LOGGER.debug("%s: Callback handler stopped.", self._name)