async def fetching_data(self, *_): """Get the latest data from yr.no.""" import xmltodict def try_again(err: str): """Retry in 15 to 20 minutes.""" minutes = 15 + randrange(6) _LOGGER.error("Retrying in %i minutes: %s", minutes, err) async_call_later(self.hass, minutes*60, self.fetching_data) try: websession = async_get_clientsession(self.hass) with async_timeout.timeout(10, loop=self.hass.loop): resp = await websession.get( self._url, params=self._urlparams) if resp.status != 200: try_again('{} returned {}'.format(resp.url, resp.status)) return text = await resp.text() except (asyncio.TimeoutError, aiohttp.ClientError) as err: try_again(err) return try: self.data = xmltodict.parse(text)['weatherdata'] except (ExpatError, IndexError) as err: try_again(err) return await self.updating_devices() async_call_later(self.hass, 60*60, self.fetching_data)
async def _fetch_data(self, *_): """Get the latest data from met.no.""" if not await self._weather_data.fetching_data(): # Retry in 15 to 20 minutes. minutes = 15 + randrange(6) _LOGGER.error("Retrying in %i minutes", minutes) async_call_later(self.hass, minutes*60, self._fetch_data) return async_call_later(self.hass, 60*60, self._fetch_data) await self._update()
async def _attempt_connect(self): """Attempt to connect to the socket (retrying later on fail).""" from aioambient.errors import WebsocketError try: await self.client.websocket.connect() except WebsocketError as err: _LOGGER.error("Error with the websocket connection: %s", err) self._ws_reconnect_delay = min( 2 * self._ws_reconnect_delay, 480) async_call_later( self._hass, self._ws_reconnect_delay, self.ws_connect)
def state_message_received(msg): """Handle a new received MQTT state message.""" payload = msg.payload value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: payload = value_template.async_render_with_possible_json_value( payload, variables={'entity_id': self.entity_id}) if payload == self._config.get(CONF_PAYLOAD_ON): self._state = True elif payload == self._config.get(CONF_PAYLOAD_OFF): self._state = False else: # Payload is not for this entity _LOGGER.warning('No matching payload found' ' for entity: %s with state_topic: %s', self._config.get(CONF_NAME), self._config.get(CONF_STATE_TOPIC)) return if self._delay_listener is not None: self._delay_listener() self._delay_listener = None off_delay = self._config.get(CONF_OFF_DELAY) if (self._state and off_delay is not None): self._delay_listener = evt.async_call_later( self.hass, off_delay, off_delay_listener) self.async_write_ha_state()
def parse_data(self, data, raw_data): """Parse data sent by gateway. Polling (proto v1, firmware version 1.4.1_159.0143) >> { "cmd":"read","sid":"158..."} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'read_ack', 'data': '{"voltage":3005}'} Multicast messages (proto v1, firmware version 1.4.1_159.0143) << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"status":"motion"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"no_motion":"120"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"no_motion":"180"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"no_motion":"300"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'heartbeat', 'data': '{"voltage":3005}'} """ if raw_data['cmd'] == 'heartbeat': _LOGGER.debug( 'Skipping heartbeat of the motion sensor. ' 'It can introduce an incorrect state because of a firmware ' 'bug (https://github.com/home-assistant/home-assistant/pull/' '11631#issuecomment-357507744).') return if NO_MOTION in data: self._no_motion_since = data[NO_MOTION] self._state = False return True value = data.get(self._data_key) if value is None: return False if value == MOTION: if self._data_key == 'motion_status': if self._unsub_set_no_motion: self._unsub_set_no_motion() self._unsub_set_no_motion = async_call_later( self._hass, 120, self._async_set_no_motion ) if self.entity_id is not None: self._hass.bus.fire('xiaomi_aqara.motion', { 'entity_id': self.entity_id }) self._no_motion_since = 0 if self._state: return False self._state = True return True
def on_connect(): """Define a handler to fire when the websocket is connected.""" _LOGGER.info('Connected to websocket') _LOGGER.debug('Watchdog starting') if self._watchdog_listener: self._watchdog_listener() self._watchdog_listener = async_call_later( self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)
def parse_data(self, data, raw_data): """Parse data sent by gateway.""" value = data.get(VERIFIED_WRONG_KEY) if value is not None: self._verified_wrong_times = int(value) return True for key in (FINGER_KEY, PASSWORD_KEY, CARD_KEY): value = data.get(key) if value is not None: self._changed_by = int(value) self._verified_wrong_times = 0 self._state = STATE_UNLOCKED async_call_later(self.hass, UNLOCK_MAINTAIN_TIME, self.clear_unlock_state) return True return False
def on_data(data): """Define a handler to fire when the data is received.""" mac_address = data['macAddress'] if data != self.stations[mac_address][ATTR_LAST_DATA]: _LOGGER.debug('New data received: %s', data) self.stations[mac_address][ATTR_LAST_DATA] = data async_dispatcher_send(self._hass, TOPIC_UPDATE) _LOGGER.debug('Resetting watchdog') self._watchdog_listener() self._watchdog_listener = async_call_later( self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)
def get_segment(self, sequence: int = None) -> Any: """Retrieve a specific segment, or the whole list.""" self.idle = False # Reset idle timeout if self._unsub is not None: self._unsub() self._unsub = async_call_later(self._stream.hass, 300, self._timeout) if not sequence: return self._segments for segment in self._segments: if segment.sequence == sequence: return segment return None
def async_delay_save(self, data_func: Callable[[], Dict], delay: Optional[int] = None): """Save data with an optional delay.""" self._data = { 'version': self.version, 'key': self.key, 'data_func': data_func, } self._async_cleanup_delay_listener() self._unsub_delay_listener = async_call_later( self.hass, delay, self._async_callback_delayed_write) self._async_ensure_stop_listener()
def test_async_call_later(hass): """Test calling an action later.""" def action(): pass now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC) with patch('homeassistant.helpers.event' '.async_track_point_in_utc_time') as mock, \ patch('homeassistant.util.dt.utcnow', return_value=now): remove = async_call_later(hass, 3, action) assert len(mock.mock_calls) == 1 p_hass, p_action, p_point = mock.mock_calls[0][1] assert p_hass is hass assert p_action is action assert p_point == now + timedelta(seconds=3) assert remove is mock()
async def update(self, *args): """Periodically poll the servers for current state.""" try: if not await self._hass.async_add_executor_job( self._client.update): _LOGGER.warning('Failed request') return dev_ids = {dev.device_id for dev in self._client.devices} new_devices = dev_ids - self._known_devices # just await each discover as `gather` use up all HTTPAdapter pools for d_id in new_devices: await self._discover(d_id) self._known_devices |= new_devices async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY) finally: self._hass.data[INTERVAL_TRACKER] = async_call_later( self._hass, self._interval, self.update)
def parse_data(self, data, raw_data): """Parse data sent by gateway.""" if raw_data['cmd'] == 'heartbeat': _LOGGER.debug( 'Skipping heartbeat of the motion sensor. ' 'It can introduce an incorrect state because of a firmware ' 'bug (https://github.com/home-assistant/home-assistant/pull/' '11631#issuecomment-357507744).') return self._should_poll = False if NO_MOTION in data: # handle push from the hub self._no_motion_since = data[NO_MOTION] self._state = False return True value = data.get(self._data_key) if value is None: return False if value == MOTION: if self._data_key == 'motion_status': if self._unsub_set_no_motion: self._unsub_set_no_motion() self._unsub_set_no_motion = async_call_later( self._hass, 180, self._async_set_no_motion ) else: self._should_poll = True if self.entity_id is not None: self._hass.bus.fire('motion', { 'entity_id': self.entity_id }) self._no_motion_since = 0 if self._state: return False self._state = True return True if value == NO_MOTION: if not self._state: return False self._state = False return True
def put(self, segment: Segment) -> None: """Store output.""" # Start idle timeout when we start recieving data if self._unsub is None: self._unsub = async_call_later( self._stream.hass, self.timeout, self._timeout) if segment is None: self._event.set() # Cleanup provider if self._unsub is not None: self._unsub() self.cleanup() return self._segments.append(segment) self._event.set() self._event.clear()
def _handle_event(self, event): """Domain specific event handler.""" command = event['command'] if command == 'on': self._state = True elif command == 'off': self._state = False if (self._state and self._off_delay is not None): def off_delay_listener(now): """Switch device off after a delay.""" self._delay_listener = None self._state = False self.async_schedule_update_ha_state() if self._delay_listener is not None: self._delay_listener() self._delay_listener = evt.async_call_later( self.hass, self._off_delay, off_delay_listener)
async def async_save(self, data: Dict, *, delay: Optional[int] = None): """Save data with an optional delay.""" self._data = { 'version': self.version, 'key': self.key, 'data': data, } self._async_cleanup_delay_listener() if delay is None: self._async_cleanup_stop_listener() await self._async_handle_write_data() return self._unsub_delay_listener = async_call_later( self.hass, delay, self._async_callback_delayed_write) self._async_ensure_stop_listener()
def state_message_received(_topic, payload, _qos): """Handle a new received MQTT state message.""" if self._template is not None: payload = self._template.async_render_with_possible_json_value( payload) if payload == self._payload_on: self._state = True elif payload == self._payload_off: self._state = False else: # Payload is not for this entity _LOGGER.warning('No matching payload found' ' for entity: %s with state_topic: %s', self._name, self._state_topic) return if self._delay_listener is not None: self._delay_listener() self._delay_listener = None if (self._state and self._off_delay is not None): self._delay_listener = evt.async_call_later( self.hass, self._off_delay, off_delay_listener) self.async_schedule_update_ha_state()
return services try: services = await account_link.async_fetch_available_services( hass.data[DOMAIN]) except (aiohttp.ClientError, asyncio.TimeoutError): return [] hass.data[DATA_SERVICES] = services @callback def clear_services(_now): """Clear services cache.""" hass.data.pop(DATA_SERVICES, None) event.async_call_later(hass, CACHE_TIMEOUT, clear_services) return services class CloudOAuth2Implementation( config_entry_oauth2_flow.AbstractOAuth2Implementation): """Cloud implementation of the OAuth2 flow.""" def __init__(self, hass: HomeAssistant, service: str) -> None: """Initialize cloud OAuth2 implementation.""" self.hass = hass self.service = service @property def name(self) -> str: """Name of the implementation."""
def _state_updated(entity_id, old_state, new_state): """Schedule an update.""" async_call_later(self.hass, 4, _refresh_associated) self.async_schedule_update_ha_state()
def _update_device_soon(self): if self._update_device_cb is not None: self._update_device_cb() # unsubscribe self._update_device_cb = async_call_later(self._hass, 1, self._update_device)
def parse_data(self, data, raw_data): """Parse data sent by gateway. Polling (proto v1, firmware version 1.4.1_159.0143) >> { "cmd":"read","sid":"158..."} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'read_ack', 'data': '{"voltage":3005}'} Multicast messages (proto v1, firmware version 1.4.1_159.0143) << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"status":"motion"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"no_motion":"120"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"no_motion":"180"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'report', 'data': '{"no_motion":"300"}'} << {'model': 'motion', 'sid': '158...', 'short_id': 26331, 'cmd': 'heartbeat', 'data': '{"voltage":3005}'} """ _LOGGER.debug("Motion Sensor Data %s", data) if raw_data["cmd"] == "heartbeat": _LOGGER.debug( "Skipping heartbeat of the motion sensor. " "It can introduce an incorrect state because of a firmware " "bug (https://github.com/home-assistant/home-assistant/pull/" "11631#issuecomment-357507744)." ) return if NO_MOTION in data: self._no_motion_since = data[NO_MOTION] self._state = False return True value = data.get(self._data_key) if value is None: return False if value == MOTION: if self._data_key == "motion_status": if self._unsub_set_no_motion: self._unsub_set_no_motion() self._unsub_set_no_motion = async_call_later( self._hass, MOTION_SENSOR_NO_MOTION_TIMEOUT, self._async_set_no_motion, ) if self.entity_id is not None: self._hass.bus.fire( "xiaomi_aqara.motion", {"entity_id": self.entity_id} ) self._no_motion_since = 0 if self._state: return False self._state = True return True
async def async_setup(hass, config): """Set up ZHA from config.""" if DOMAIN not in config: return True try: zha_gateway = hass.data["zha"]["zha_gateway"] except KeyError: return False builder = TopologyBuilder(hass, zha_gateway) hass.data[DOMAIN] = {ATTR_TOPO: builder} output_dir = os.path.join(hass.config.config_dir, CONFIG_OUTPUT_DIR_NAME) hass.data[DOMAIN][ATTR_OUTPUT_DIR] = output_dir def mkdir(dir): try: os.mkdir(dir) return True except OSError as exc: LOGGER.error("Couldn't create '%s' dir: %s", dir, exc) return False if not os.path.isdir(output_dir): if not await hass.async_add_executor_job(mkdir, output_dir): return False async def setup_scanner(_now): async_track_time_interval(hass, builder.time_tracker, AWAKE_INTERVAL) await builder.time_tracker() async_call_later(hass, CONFIG_INITIAL_SCAN_DELAY, setup_scanner) async def scan_now_handler(service): """Scan topology right now.""" await builder.preempt_build() hass.services.async_register( DOMAIN, SERVICE_SCAN_NOW, scan_now_handler, schema=SERVICE_SCHEMAS[SERVICE_SCAN_NOW], ) @websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command( {vol.Required(ATTR_TYPE): f"{DOMAIN}/devices"}) async def websocket_get_devices(hass, connection, msg): """Get ZHA Map devices.""" response = { "time": builder.timestamp, "devices": [nei.json() for nei in builder.current.values()], } connection.send_result(msg["id"], response) websocket_api.async_register_command(hass, websocket_get_devices) return True
def schedule_import(_): """Schedule delayed import after HA is fully started.""" async_call_later(hass, 10, do_import)
def try_again(err: str): """Retry in 15 to 20 minutes.""" minutes = 15 + randrange(6) _LOGGER.error("Retrying in %i minutes: %s", minutes, err) async_call_later(self.hass, minutes * 60, self.fetching_data)
async def hacs_startup(): """HACS startup tasks.""" hacs = get_hacs() if hacs.configuration.debug: try: await hacs.hass.services.async_call("logger", "set_level", {"hacs": "debug"}) await hacs.hass.services.async_call("logger", "set_level", {"queueman": "debug"}) await hacs.hass.services.async_call("logger", "set_level", {"AioGitHub": "debug"}) except ServiceNotFound: hacs.logger.error( "Could not set logging level to debug, logger is not enabled") lovelace_info = await system_health_info(hacs.hass) hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}") hacs.version = VERSION hacs.logger.info(STARTUP) hacs.system.config_path = hacs.hass.config.path() hacs.system.ha_version = HAVERSION hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml") hacs.system.disabled = False hacs.github = GitHub(hacs.configuration.token, async_create_clientsession(hacs.hass)) hacs.data = HacsData() can_update = await get_fetch_updates_for(hacs.github) if can_update == 0: hacs.logger.info( "HACS is ratelimited, repository updates will resume in 1h.") else: hacs.logger.debug(f"Can update {can_update} repositories") # Check HACS Constrains if not await hacs.hass.async_add_executor_job(check_constrains): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Set up frontend await setup_frontend() if not await hacs.hass.async_add_executor_job(internet_connectivity_check): hacs.logger.critical("No network connectivity") return False # Load HACS if not await load_hacs_repository(): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Restore from storefiles if not await hacs.data.restore(): hacs_repo = hacs.get_by_name("hacs/integration") hacs_repo.pending_restart = True if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Add aditional categories hacs.common.categories = ELEMENT_TYPES if hacs.configuration.appdaemon: hacs.common.categories.append("appdaemon") if hacs.configuration.netdaemon: hacs.common.categories.append("netdaemon") # Setup startup tasks if hacs.configuration.config_type == "yaml": hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, hacs.startup_tasks()) else: async_call_later(hacs.hass, 5, hacs.startup_tasks()) # Show the configuration hacs.configuration.print() # Set up sensor await hacs.hass.async_add_executor_job(add_sensor) # Mischief managed! return True
async def async_refresh_sensor(self, sessions): """Set instance object and trigger an entity state update.""" _LOGGER.debug("Refreshing sensor [%s]", self.unique_id) self.sessions = sessions update_failed = False @callback def update_plex(_): async_dispatcher_send( self.hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format( self._server.machine_identifier), ) now_playing = [] for sess in self.sessions: if sess.TYPE == "photo": _LOGGER.debug("Photo session detected, skipping: %s", sess) continue if not sess.usernames: _LOGGER.debug( "Session temporarily incomplete, will try again: %s", sess) update_failed = True continue user = sess.usernames[0] device = sess.players[0].title now_playing_user = f"{user} - {device}" now_playing_title = "" if sess.TYPE == "episode": # example: # "Supernatural (2005) - s01e13 - Route 666" def sync_io_attributes(session): year = None try: year = session.show().year except TypeError: pass return (year, session.seasonEpisode) year, season_episode = await self.hass.async_add_executor_job( sync_io_attributes, sess) season_title = sess.grandparentTitle if year is not None: season_title += f" ({year!s})" episode_title = sess.title now_playing_title = ( f"{season_title} - {season_episode} - {episode_title}") elif sess.TYPE == "track": # example: # "Billy Talent - Afraid of Heights - Afraid of Heights" track_artist = sess.grandparentTitle track_album = sess.parentTitle track_title = sess.title now_playing_title = f"{track_artist} - {track_album} - {track_title}" elif sess.TYPE == "movie": # example: # "picture_of_last_summer_camp (2015)" # "The Incredible Hulk (2008)" now_playing_title = sess.title year = await self.hass.async_add_executor_job( getattr, sess, "year") if year is not None: now_playing_title += f" ({year})" else: now_playing_title = sess.title now_playing.append((now_playing_user, now_playing_title)) self._state = len(self.sessions) self._now_playing = now_playing self.async_write_ha_state() if update_failed: async_call_later(self.hass, 5, update_plex)
def awake(self) -> None: """Keep the idle time alive by resetting the timeout.""" self.idle = False # Reset idle timeout self.clear() self._unsub = async_call_later(self._hass, self._timeout, self.fire)
def start(self) -> None: """Start the idle timer if not already started.""" self.idle = False if self._unsub is None: self._unsub = async_call_later(self._hass, self._timeout, self.fire)
async def async_hacs_startup(): """HACS startup tasks.""" hacs = get_hacs() hacs.hass.data[DOMAIN] = hacs try: lovelace_info = await system_health_info(hacs.hass) except TypeError: # If this happens, the users YAML is not valid, we assume YAML mode lovelace_info = {"mode": "yaml"} hacs.log.debug(f"Configuration type: {hacs.configuration.config_type}") hacs.version = VERSION hacs.log.info(STARTUP) hacs.core.config_path = hacs.hass.config.path() hacs.system.ha_version = HAVERSION # Setup websocket API await async_setup_hacs_websockt_api() # Set up frontend await async_setup_frontend() # Clear old storage files await async_clear_storage() hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml") hacs.system.disabled = False hacs.github = GitHub( hacs.configuration.token, async_create_clientsession(hacs.hass) ) hacs.data = HacsData() can_update = await get_fetch_updates_for(hacs.github) if can_update is None: hacs.log.critical("Your GitHub token is not valid") return False if can_update != 0: hacs.log.debug(f"Can update {can_update} repositories") else: hacs.log.info( "HACS is ratelimited, repository updates will resume when the limit is cleared, this can take up to 1 hour" ) return False # Check HACS Constrains if not await hacs.hass.async_add_executor_job(check_constrains): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Load HACS if not await async_load_hacs_repository(): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Restore from storefiles if not await hacs.data.restore(): hacs_repo = hacs.get_by_name("hacs/integration") hacs_repo.pending_restart = True if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Setup startup tasks if hacs.status.new: async_call_later(hacs.hass, 5, hacs.startup_tasks) else: hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, hacs.startup_tasks) # Set up sensor await async_add_sensor() # Mischief managed! await hacs.async_set_stage(HacsStage.WAITING) hacs.log.info( "Setup complete, waiting for Home Assistant before startup tasks starts" ) return True
async def _attempt_connect(self, callback_dataload_complete): """Attempt to connect to the socket (retrying later on fail).""" def on_data(data): """Define a handler to fire when the data is received.""" msg_type = data.WhichOneof('msg') if (msg_type == "vepUpdate"): #VEPUpdate LOGGER.debug("vepUpdate") return if (msg_type == "vepUpdates"): #VEPUpdatesByVIN self._process_vep_updates(data) sequence_number = data.vepUpdates.sequence_number LOGGER.debug(f"vepUpdates Sequence: {sequence_number}") ack_command = client_pb2.ClientMessage() ack_command.acknowledge_vep_updates_by_vin.sequence_number = sequence_number return ack_command if (msg_type == "debugMessage"): #DebugMessage if data.debugMessage: LOGGER.debug( f"debugMessage - Data: {data.debugMessage.message}") return if (msg_type == "service_status_update"): LOGGER.debug( f"service_status_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) return if (msg_type == "user_data_update"): LOGGER.debug( f"user_data_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) return if (msg_type == "user_vehicle_auth_changed_update"): LOGGER.debug( f"user_vehicle_auth_changed_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) return if (msg_type == "user_picture_update"): LOGGER.debug( f"user_picture_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) return if (msg_type == "user_pin_update"): LOGGER.debug( f"user_pin_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) return if (msg_type == "vehicle_updated"): LOGGER.debug( f"vehicle_updated - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) return if (msg_type == "preferred_dealer_change"): LOGGER.debug( f"preferred_dealer_change - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) return if (msg_type == "apptwin_command_status_updates_by_vin"): LOGGER.debug( f"apptwin_command_status_updates_by_vin - Data: {MessageToJson(data, preserving_proto_field_name=True)}" ) sequence_number = data.apptwin_command_status_updates_by_vin.sequence_number LOGGER.debug("apptwin_command_status_updates_by_vin: %s", sequence_number) ack_command = client_pb2.ClientMessage() ack_command.acknowledge_apptwin_command_status_update_by_vin.sequence_number = sequence_number return ack_command if (msg_type == "apptwin_pending_command_request"): #LOGGER.debug(f"apptwin_pending_command_request - Data: {MessageToJson(data, preserving_proto_field_name=True)}") return if (msg_type == "assigned_vehicles"): LOGGER.debug("assigned_vehicles") self._process_assigned_vehicles(data) return LOGGER.debug(f"Message Type not implemented - {msg_type}") try: self._on_dataload_complete = callback_dataload_complete await self.websocket.async_connect(on_data) except (WebsocketError) as err: LOGGER.error("Error with the websocket connection: %s", err) self._ws_reconnect_delay = self._ws_reconnect_delay async_call_later(self._hass, self._ws_reconnect_delay, self.websocket.async_connect(on_data))
async def hacs_startup(hacs): """HACS startup tasks.""" if hacs.configuration.debug: try: await hacs.hass.services.async_call("logger", "set_level", {"hacs": "debug"}) except ServiceNotFound: hacs.logger.error( "Could not set logging level to debug, logger is not enabled") lovelace_info = await system_health_info(hacs.hass) hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}") hacs.version = VERSION hacs.logger.info(STARTUP) hacs.system.config_path = hacs.hass.config.path() hacs.system.ha_version = HAVERSION hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml") hacs.system.disabled = False hacs.github = AIOGitHub(hacs.configuration.token, async_create_clientsession(hacs.hass)) hacs.data = HacsData() # Check HACS Constrains if not await hacs.hass.async_add_executor_job(check_constans, hacs): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Set up frontend await setup_frontend(hacs) # Set up sensor await hacs.hass.async_add_executor_job(add_sensor, hacs) # Load HACS if not await load_hacs_repository(hacs): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Restore from storefiles if not await hacs.data.restore(): hacs_repo = hacs().get_by_name("hacs/integration") hacs_repo.pending_restart = True if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Add aditional categories hacs.common.categories = ELEMENT_TYPES if hacs.configuration.appdaemon: hacs.common.categories.append("appdaemon") if hacs.configuration.python_script: hacs.configuration.python_script = False if hacs.configuration.config_type == "yaml": hacs.logger.warning( "Configuration option 'python_script' is deprecated and you should remove it from your configuration, HACS will know if you use 'python_script' in your Home Assistant configuration, this option will be removed in a future release." ) if hacs.configuration.theme: hacs.configuration.theme = False if hacs.configuration.config_type == "yaml": hacs.logger.warning( "Configuration option 'theme' is deprecated and you should remove it from your configuration, HACS will know if you use 'theme' in your Home Assistant configuration, this option will be removed in a future release." ) # Setup startup tasks if hacs.configuration.config_type == "yaml": hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, hacs().startup_tasks()) else: async_call_later(hacs.hass, 5, hacs().startup_tasks()) # Show the configuration hacs.configuration.print() # Mischief managed! return True
def async_schedule_pull(self) -> None: """Schedule async_pull_messages to run.""" self._unsub_refresh = async_call_later(self.hass, 1, self.async_pull_messages)
async def update(self, *args): data = token = None try: r = await self.session.get('https://yandex.ru/web-maps/covid19') text = await r.text() m = RE_HTML.search(text) data = json.loads(m[1]) token = data['csrfToken'] data = data['covidData'] except Exception as e: _LOGGER.error(f"Load Data error: {e}") try: items = [p for p in data['items'] if p['name'] in self.include] \ if self.include else data['items'] self._attrs = { p['name']: { 'cases': p['cases'], 'cured': p['cured'], 'deaths': p['deaths'], 'new_cases': (p['histogram'][-1]['value'] - p['histogram'][-2]['value']) if 'histogram' in p and len(p['histogram']) >= 2 else 0 } for p in items } except Exception as e: _LOGGER.error(f"Update Places error: {e}") if not self.include or 'Россия' in self.include: try: self._attrs['Россия'] = ru = { 'cases': 0, 'cured': 0, 'deaths': 0, 'new_cases': (data['histogram'][-1]['value'] - data['histogram'][-2]['value']), 'tests': int(data['tests'].replace(' ', '')) } for p in data['items']: if p.get('ru'): ru['cases'] += p['cases'] ru['cured'] += p['cured'] ru['deaths'] += p['deaths'] except Exception as e: _LOGGER.error(f"Update Russia error: {e}") if not self.include or 'Мир' in self.include: try: self._attrs['Мир'] = world = { 'cases': 0, 'cured': 0, 'deaths': 0 } for p in data['items']: if 'ru' not in p: world['cases'] += p['cases'] world['cured'] += p['cured'] world['deaths'] += p['deaths'] except Exception as e: _LOGGER.error(f"Update World error: {e}") try: ts = datetime.fromtimestamp(data['ts']) self._state = ts.strftime("%H:%M") except Exception as e: _LOGGER.error(f"Update Sensor error: {e}") try: r = await self.session.get('https://yandex.ru/web-maps/api/covid', params={ 'csrfToken': token, 'isolation': 'true' }) data = await r.json() if self.include: data['data']['cities'] = [ p for p in data['data']['cities'] if p['name'] in self.include ] for city in data['data']['cities']: name = city['name'] if name in self._attrs: self._attrs[name]['isolation'] = city['level'] else: self._attrs[name] = {'isolation': city['level']} except Exception as e: _LOGGER.error(f"Update Isolation error: {e}") self.async_schedule_update_ha_state() async_call_later(self.hass, 60 * 60, self.update)
async def ws_connect(self): """Register handlers and connect to the websocket.""" from aioambient.errors import WebsocketError async def _ws_reconnect(event_time): """Forcibly disconnect from and reconnect to the websocket.""" _LOGGER.debug('Watchdog expired; forcing socket reconnection') await self.client.websocket.disconnect() await self.client.websocket.connect() def on_connect(): """Define a handler to fire when the websocket is connected.""" _LOGGER.info('Connected to websocket') _LOGGER.debug('Watchdog starting') self._watchdog_listener = async_call_later( self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect) def on_data(data): """Define a handler to fire when the data is received.""" mac_address = data['macAddress'] if data != self.stations[mac_address][ATTR_LAST_DATA]: _LOGGER.debug('New data received: %s', data) self.stations[mac_address][ATTR_LAST_DATA] = data async_dispatcher_send(self._hass, TOPIC_UPDATE) _LOGGER.debug('Resetting watchdog') self._watchdog_listener() self._watchdog_listener = async_call_later( self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect) def on_disconnect(): """Define a handler to fire when the websocket is disconnected.""" _LOGGER.info('Disconnected from websocket') def on_subscribed(data): """Define a handler to fire when the subscription is set.""" for station in data['devices']: if station['macAddress'] in self.stations: continue _LOGGER.debug('New station subscription: %s', data) # If the user hasn't specified monitored conditions, use only # those that their station supports (and which are defined # here): if not self.monitored_conditions: self.monitored_conditions = [ k for k in station['lastData'].keys() if k in SENSOR_TYPES ] self.stations[station['macAddress']] = { ATTR_LAST_DATA: station['lastData'], ATTR_LOCATION: station.get('info', {}).get('location'), ATTR_NAME: station.get('info', {}).get( 'name', station['macAddress']), } for component in ('binary_sensor', 'sensor'): self._hass.async_create_task( self._hass.config_entries.async_forward_entry_setup( self._config_entry, component)) self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY self.client.websocket.on_connect(on_connect) self.client.websocket.on_data(on_data) self.client.websocket.on_disconnect(on_disconnect) self.client.websocket.on_subscribed(on_subscribed) try: await self.client.websocket.connect() except WebsocketError as err: _LOGGER.error("Error with the websocket connection: %s", err) self._ws_reconnect_delay = min(2 * self._ws_reconnect_delay, 480) async_call_later( self._hass, self._ws_reconnect_delay, self.ws_connect)
def _fetch_callback(self, *_): async_call_later( self._hass, randrange(0, 60 * self._random_fetch_time_offset), self._fetch_now_callback, )
def try_again(err: str): # Retry secs = random.randint(30, 60) _LOGGER.error("Retrying in %i seconds: %s", secs, err) async_call_later(self.hass, secs, self.fetching_data)
async def almond_hass_start(_event): event.async_call_later(hass, ALMOND_SETUP_DELAY, configure_almond)
siren_duration = duration if (tone := kwargs.get(ATTR_TONE)) is not None: siren_tone = tone if (level := kwargs.get(ATTR_VOLUME_LEVEL)) is not None: siren_level = int(level) await self._channel.issue_start_warning( mode=siren_tone, warning_duration=siren_duration, siren_level=siren_level, strobe=should_strobe, strobe_duty_cycle=50 if should_strobe else 0, strobe_intensity=strobe_level, ) self._attr_is_on = True self._off_listener = async_call_later(self._zha_device.hass, siren_duration, self.async_set_off) self.async_write_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Turn off siren.""" await self._channel.issue_start_warning( mode=WARNING_DEVICE_MODE_STOP, strobe=WARNING_DEVICE_STROBE_NO) self._attr_is_on = False self.async_write_ha_state() @callback def async_set_off(self, _) -> None: """Set is_on to False and write HA state.""" self._attr_is_on = False if self._off_listener:
async def async_update(self): """Get the latest details on a media player. Because media players spend the majority of time idle, an adaptive update should be used to avoid flooding Amazon focusing on known play states. An initial version included an update_devices call on every update. However, this quickly floods the network for every new device added. This should only call refresh() to call the AlexaAPI. """ try: if not self.enabled: return except AttributeError: pass email = self._login.email if (self.entity_id is None # Device has not initialized yet or email not in self.hass.data[DATA_ALEXAMEDIA]["accounts"] or self._login.session.closed): return device = self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][ "media_player"][self.unique_id] seen_commands = ( self.hass.data[DATA_ALEXAMEDIA]["accounts"][email] ["websocket_commands"].keys() if "websocket_commands" in (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]) else None) await self.refresh( # pylint: disable=unexpected-keyword-arg device, no_throttle=True) websocket_enabled = self.hass.data[DATA_ALEXAMEDIA]["accounts"][ email].get("websocket") if (self.state in [STATE_PLAYING] and # only enable polling if websocket not connected (not websocket_enabled or not seen_commands or not ("PUSH_AUDIO_PLAYER_STATE" in seen_commands or "PUSH_MEDIA_CHANGE" in seen_commands or "PUSH_MEDIA_PROGRESS_CHANGE" in seen_commands))): self._should_poll = False # disable polling since manual update if (self._last_update == 0 or util.dt.as_timestamp(util.utcnow()) - util.dt.as_timestamp(self._last_update) > PLAY_SCAN_INTERVAL): _LOGGER.debug( "%s playing; scheduling update in %s seconds", self.name, PLAY_SCAN_INTERVAL, ) async_call_later( self.hass, PLAY_SCAN_INTERVAL, lambda _: self.async_schedule_update_ha_state(force_refresh =True), ) elif self._should_poll: # Not playing, one last poll self._should_poll = False if not websocket_enabled: _LOGGER.debug( "Disabling polling and scheduling last update in" " 300 seconds for %s", self.name, ) async_call_later( self.hass, 300, lambda _: self.async_schedule_update_ha_state(force_refresh =True), ) else: _LOGGER.debug("Disabling polling for %s", self.name) self._last_update = util.utcnow() self.async_schedule_update_ha_state()
async def register_webhook(event): if CONF_WEBHOOK_ID not in entry.data: data = {**entry.data, CONF_WEBHOOK_ID: secrets.token_hex()} hass.config_entries.async_update_entry(entry, data=data) if hass.components.cloud.async_active_subscription(): if CONF_CLOUDHOOK_URL not in entry.data: webhook_url = await hass.components.cloud.async_create_cloudhook( entry.data[CONF_WEBHOOK_ID] ) data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url} hass.config_entries.async_update_entry(entry, data=data) else: webhook_url = entry.data[CONF_CLOUDHOOK_URL] else: webhook_url = hass.components.webhook.async_generate_url( entry.data[CONF_WEBHOOK_ID] ) if entry.data["auth_implementation"] == "cloud" and not webhook_url.startswith( "https://" ): _LOGGER.warning( "Webhook not registered - " "https and port 443 is required to register the webhook" ) return try: webhook_register( hass, DOMAIN, "Netatmo", entry.data[CONF_WEBHOOK_ID], async_handle_webhook, ) async def handle_event(event): """Handle webhook events.""" if event["data"]["push_type"] == "webhook_activation": if activation_listener is not None: activation_listener() if activation_timeout is not None: activation_timeout() activation_listener = async_dispatcher_connect( hass, f"signal-{DOMAIN}-webhook-None", handle_event, ) activation_timeout = async_call_later(hass, 10, unregister_webhook) await hass.async_add_executor_job( hass.data[DOMAIN][entry.entry_id][AUTH].addwebhook, webhook_url ) _LOGGER.info("Register Netatmo webhook: %s", webhook_url) except pyatmo.ApiError as err: _LOGGER.error("Error during webhook registration - %s", err) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
async def hacs_startup(hacs): """HACS startup tasks.""" hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}") hacs.version = const.VERSION hacs.logger.info(const.STARTUP) hacs.system.config_path = hacs.hass.config.path() hacs.system.ha_version = HAVERSION hacs.system.disabled = False hacs.github = AIOGitHub(hacs.configuration.token, async_create_clientsession(hacs.hass)) hacs.data = HacsData() # Check minimum version if not check_version(hacs): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Check custom_updater if not check_custom_updater(hacs): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Set up frontend await setup_frontend(hacs) # Load HACS if not await load_hacs_repository(hacs): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False val = ValidateData() if not val.validate_local_data_file(): if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False else: if os.path.exists(f"{hacs.system.config_path}/.storage/hacs"): os.remove(f"{hacs.system.config_path}/.storage/hacs") # Restore from storefiles if not await hacs.data.restore(): hacs_repo = hacs().get_by_name("custom-components/hacs") hacs_repo.pending_restart = True if hacs.configuration.config_type == "flow": if hacs.configuration.config_entry is not None: await async_remove_entry(hacs.hass, hacs.configuration.config_entry) return False # Add aditional categories if hacs.configuration.appdaemon: const.ELEMENT_TYPES.append("appdaemon") if hacs.configuration.python_script: const.ELEMENT_TYPES.append("python_script") if hacs.configuration.theme: const.ELEMENT_TYPES.append("theme") hacs.common.categories = sorted(const.ELEMENT_TYPES) # Setup startup tasks if hacs.configuration.config_type == "yaml": hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, hacs().startup_tasks()) else: async_call_later(hacs.hass, 5, hacs().startup_tasks()) # Print DEV warning if hacs.configuration.dev: hacs.logger.error(const.DEV_MODE) hacs.hass.components.persistent_notification.create( title="HACS DEV MODE", message=const.DEV_MODE, notification_id="hacs_dev_mode", ) # Add sensor add_sensor(hacs) # Set up services await add_services(hacs) # Mischief managed! return True
async def update_system_info(now): nonlocal system_info system_info = await async_get_system_info(hass) # Update system info every hour async_call_later(hass, 3600, update_system_info)
async def update_devices(login_obj): """Ping Alexa API to identify all devices, bluetooth, and last called device. This will add new devices and services when discovered. By default this runs every SCAN_INTERVAL seconds unless another method calls it. if websockets is connected, it will increase the delay 10-fold between updates. While throttled at MIN_TIME_BETWEEN_SCANS, care should be taken to reduce the number of runs to avoid flooding. Slow changing states should be checked here instead of in spawned components like media_player since this object is one per account. Each AlexaAPI call generally results in two webpage requests. """ from alexapy import AlexaAPI email: Text = login_obj.email if email not in hass.data[DATA_ALEXAMEDIA]["accounts"]: return existing_serials = _existing_serials() existing_entities = hass.data[DATA_ALEXAMEDIA]["accounts"][email][ "entities"]["media_player"].values() websocket_enabled = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get( "websocket") auth_info = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get( "auth_info") new_devices = hass.data[DATA_ALEXAMEDIA]["accounts"][email][ "new_devices"] devices = {} bluetooth = {} preferences = {} dnd = {} raw_notifications = {} try: if new_devices: auth_info = await AlexaAPI.get_authentication(login_obj) devices = await AlexaAPI.get_devices(login_obj) bluetooth = await AlexaAPI.get_bluetooth(login_obj) preferences = await AlexaAPI.get_device_preferences(login_obj) dnd = await AlexaAPI.get_dnd_state(login_obj) raw_notifications = await AlexaAPI.get_notifications(login_obj) _LOGGER.debug( "%s: Found %s devices, %s bluetooth", hide_email(email), len(devices) if devices is not None else "", len(bluetooth.get("bluetoothStates", [])) if bluetooth is not None else "", ) if (devices is None or bluetooth is None) and not (hass.data[ DATA_ALEXAMEDIA]["accounts"][email]["configurator"]): raise AlexapyLoginError() except (AlexapyLoginError, RuntimeError): _LOGGER.debug("%s: Alexa API disconnected; attempting to relogin", hide_email(email)) await login_obj.login_with_cookie() await test_login_status(hass, config_entry, login_obj, setup_platform_callback) return await process_notifications(login_obj, raw_notifications) # Process last_called data to fire events await update_last_called(login_obj) new_alexa_clients = [] # list of newly discovered device names exclude_filter = [] include_filter = [] for device in devices: serial = device["serialNumber"] dev_name = device["accountName"] if include and dev_name not in include: include_filter.append(dev_name) if "appDeviceList" in device: for app in device["appDeviceList"]: (hass.data[DATA_ALEXAMEDIA]["accounts"][email] ["excluded"][app["serialNumber"]]) = device hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][ serial] = device continue elif exclude and dev_name in exclude: exclude_filter.append(dev_name) if "appDeviceList" in device: for app in device["appDeviceList"]: (hass.data[DATA_ALEXAMEDIA]["accounts"][email] ["excluded"][app["serialNumber"]]) = device hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][ serial] = device continue if "bluetoothStates" in bluetooth: for b_state in bluetooth["bluetoothStates"]: if serial == b_state["deviceSerialNumber"]: device["bluetooth_state"] = b_state break if "devicePreferences" in preferences: for dev in preferences["devicePreferences"]: if dev["deviceSerialNumber"] == serial: device["locale"] = dev["locale"] device["timeZoneId"] = dev["timeZoneId"] _LOGGER.debug( "%s: Locale %s timezone %s", dev_name, device["locale"], device["timeZoneId"], ) break if "doNotDisturbDeviceStatusList" in dnd: for dev in dnd["doNotDisturbDeviceStatusList"]: if dev["deviceSerialNumber"] == serial: device["dnd"] = dev["enabled"] _LOGGER.debug("%s: DND %s", dev_name, device["dnd"]) hass.data[DATA_ALEXAMEDIA]["accounts"][email][ "devices"]["switch"].setdefault( serial, {"dnd": True}) break hass.data[DATA_ALEXAMEDIA]["accounts"][email][ "auth_info"] = device["auth_info"] = auth_info hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][ "media_player"][serial] = device if serial not in existing_serials: new_alexa_clients.append(dev_name) elif serial in existing_entities: await hass.data[DATA_ALEXAMEDIA]["accounts"][email][ "entities"]["media_player"].get(serial).refresh( device, no_api=True) _LOGGER.debug( "%s: Existing: %s New: %s;" " Filtered out by not being in include: %s " "or in exclude: %s", hide_email(email), list(existing_entities), new_alexa_clients, include_filter, exclude_filter, ) if new_alexa_clients: cleaned_config = config.copy() cleaned_config.pop(CONF_PASSWORD, None) # CONF_PASSWORD contains sensitive info which is no longer needed for component in ALEXA_COMPONENTS: if component == "notify": hass.async_create_task( async_load_platform( hass, component, DOMAIN, { CONF_NAME: DOMAIN, "config": cleaned_config }, config, )) else: hass.async_add_job( hass.config_entries.async_forward_entry_setup( config_entry, component)) hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False async_call_later( hass, scan_interval if not websocket_enabled else scan_interval * 10, lambda _: hass.async_create_task( update_devices( # pylint: disable=unexpected-keyword-arg login_obj, no_throttle=True)), )
async def async_setup_entry(hass, config_entry): """Setup JLR InConnect component""" _async_import_options_from_data_if_missing(hass, config_entry) health_update_track = None def get_schema(schema_list): s = {} for schema in schema_list: s.update(eval(schema)) return vol.Schema(s) data = JLRApiHandler(hass, config_entry) update_interval = config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) health_update_interval = config_entry.options.get( CONF_HEALTH_UPDATE_INTERVAL, 0) try: if await data.async_connect(): if not data.connection.vehicles: # No vehicles or wrong credentials _LOGGER.error( "Unable to get vehicles from api. Check credentials") return False except Exception: return False # Do first update await data.async_update() # Poll for updates in background _LOGGER.info("Update from InControl servers on {} minute interval".format( int(update_interval))) update_track = async_track_time_interval( hass, data.do_status_update, timedelta(minutes=update_interval), ) # Schedule health update and repeat interval if health_update_interval and health_update_interval > 0: _LOGGER.info("Vehicle health update on {} minute interval.".format( int(health_update_interval))) # Do initial call to health_update service after HASS start up. # This speeds up restart. # 30 seconds should do it. async_call_later(hass, 30, data.do_health_update) health_update_track = async_track_time_interval( hass, data.do_health_update, timedelta(minutes=data.health_update_interval), ) else: _LOGGER.info("Scheduled vehicle health update is disabled. " + "Set interval in options to enable.") update_listener = config_entry.add_update_listener(_async_update_listener) hass.data[DOMAIN][config_entry.entry_id] = { JLR_DATA: data, STATUS_UPDATE_TRACKER: update_track, HEALTH_UPDATE_TRACKER: health_update_track, UPDATE_LISTENER: update_listener, } # for vehicle in data.vehicles: for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup( config_entry, platform)) # Add services for service, service_info in JLR_SERVICES.items(): _LOGGER.debug("Adding {} service".format(service)) hass.services.async_register( DOMAIN, service, data.async_call_service, schema=get_schema(service_info.get("schema")), ) # Create vehicle devices await async_update_device_registry(hass, config_entry, data.connection.vehicles, data) return True
async def update_devices(login_obj): """Ping Alexa API to identify all devices, bluetooth, and last called device. This will add new devices and services when discovered. By default this runs every SCAN_INTERVAL seconds unless another method calls it. if websockets is connected, it will return immediately unless 'new_devices' has been set to True. While throttled at MIN_TIME_BETWEEN_SCANS, care should be taken to reduce the number of runs to avoid flooding. Slow changing states should be checked here instead of in spawned components like media_player since this object is one per account. Each AlexaAPI call generally results in two webpage requests. """ from alexapy import AlexaAPI email: Text = login_obj.email existing_serials = (hass.data[DATA_ALEXAMEDIA] ['accounts'] [email] ['entities'] ['media_player'].keys() if 'entities' in ( hass.data[DATA_ALEXAMEDIA] ['accounts'] [email]) else []) existing_entities = (hass.data[DATA_ALEXAMEDIA] ['accounts'] [email] ['entities'] ['media_player'].values()) if ('websocket' in hass.data[DATA_ALEXAMEDIA]['accounts'][email] and hass.data[DATA_ALEXAMEDIA]['accounts'][email]['websocket'] and not (hass.data[DATA_ALEXAMEDIA] ['accounts'][email]['new_devices'])): return hass.data[DATA_ALEXAMEDIA]['accounts'][email]['new_devices'] = False devices = await AlexaAPI.get_devices(login_obj) bluetooth = await AlexaAPI.get_bluetooth(login_obj) preferences = await AlexaAPI.get_device_preferences(login_obj) dnd = await AlexaAPI.get_dnd_state(login_obj) _LOGGER.debug("%s: Found %s devices, %s bluetooth", hide_email(email), len(devices) if devices is not None else '', len(bluetooth) if bluetooth is not None else '') if ((devices is None or bluetooth is None) and not (hass.data[DATA_ALEXAMEDIA] ['accounts'][email]['config'])): _LOGGER.debug("%s: Alexa API disconnected; attempting to relogin", hide_email(email)) await login_obj.login() await test_login_status(hass, config, login_obj, setup_platform_callback) return new_alexa_clients = [] # list of newly discovered device names exclude_filter = [] include_filter = [] for device in devices: if include and device['accountName'] not in include: include_filter.append(device['accountName']) if 'appDeviceList' in device: for app in device['appDeviceList']: (hass.data[DATA_ALEXAMEDIA] ['accounts'] [email] ['excluded'] [app['serialNumber']]) = device (hass.data[DATA_ALEXAMEDIA] ['accounts'] [email] ['excluded'] [device['serialNumber']]) = device continue elif exclude and device['accountName'] in exclude: exclude_filter.append(device['accountName']) if 'appDeviceList' in device: for app in device['appDeviceList']: (hass.data[DATA_ALEXAMEDIA] ['accounts'] [email] ['excluded'] [app['serialNumber']]) = device (hass.data[DATA_ALEXAMEDIA] ['accounts'] [email] ['excluded'] [device['serialNumber']]) = device continue if 'bluetoothStates' in bluetooth: for b_state in bluetooth['bluetoothStates']: if device['serialNumber'] == b_state['deviceSerialNumber']: device['bluetooth_state'] = b_state if 'devicePreferences' in preferences: for dev in preferences['devicePreferences']: if dev['deviceSerialNumber'] == device['serialNumber']: device['locale'] = dev['locale'] _LOGGER.debug("Locale %s found for %s", device['locale'], hide_serial(device['serialNumber'])) if 'doNotDisturbDeviceStatusList' in dnd: for dev in dnd['doNotDisturbDeviceStatusList']: if dev['deviceSerialNumber'] == device['serialNumber']: device['dnd'] = dev['enabled'] _LOGGER.debug("DND %s found for %s", device['dnd'], hide_serial(device['serialNumber'])) (hass.data[DATA_ALEXAMEDIA] ['accounts'] [email] ['devices'] ['media_player'] [device['serialNumber']]) = device if device['serialNumber'] not in existing_serials: new_alexa_clients.append(device['accountName']) _LOGGER.debug("%s: Existing: %s New: %s;" " Filtered out by not being in include: %s " "or in exclude: %s", hide_email(email), list(existing_entities), new_alexa_clients, include_filter, exclude_filter) if new_alexa_clients: cleaned_config = config.copy() cleaned_config.pop(CONF_SCAN_INTERVAL, None) # CONF_SCAN_INTERVAL causes a json error in the recorder because it # is a timedelta object. cleaned_config.pop(CONF_PASSWORD, None) # CONF_PASSWORD contains sensitive info which is no longer needed for component in ALEXA_COMPONENTS: hass.async_create_task( async_load_platform(hass, component, DOMAIN, {CONF_NAME: DOMAIN, "config": cleaned_config}, config)) # Process last_called data to fire events await update_last_called(login_obj) scan_interval = config.get(CONF_SCAN_INTERVAL) async_call_later(hass, scan_interval.total_seconds(), lambda _: hass.async_create_task( update_devices(login_obj, no_throttle=True)))
def try_again(err: str): """Retry in 15 to 20 minutes.""" minutes = 15 + randrange(6) _LOGGER.error("Retrying in %i minutes: %s", minutes, err) async_call_later(self.hass, minutes*60, self.fetching_data)
def state_message_received(msg): """Handle a new received MQTT state message.""" payload = msg.payload # auto-expire enabled? expire_after = self._config.get(CONF_EXPIRE_AFTER) if expire_after is not None and expire_after > 0: # When expire_after is set, and we receive a message, assume device is # not expired since it has to be to receive the message self._expired = False # Reset old trigger if self._expiration_trigger: self._expiration_trigger() self._expiration_trigger = None # Set new trigger expiration_at = dt_util.utcnow() + timedelta(seconds=expire_after) self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at ) value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: payload = value_template.async_render_with_possible_json_value( payload, variables={"entity_id": self.entity_id} ) if not payload.strip(): # No output from template, ignore _LOGGER.debug( "Empty template output for entity: %s with state topic: %s. Payload: '%s', with value template '%s'", self._config[CONF_NAME], self._config[CONF_STATE_TOPIC], msg.payload, value_template, ) return if payload == self._config[CONF_PAYLOAD_ON]: self._state = True elif payload == self._config[CONF_PAYLOAD_OFF]: self._state = False else: # Payload is not for this entity template_info = "" if value_template is not None: template_info = f", template output: '{payload}', with value template '{str(value_template)}'" _LOGGER.info( "No matching payload found for entity: %s with state topic: %s. Payload: '%s'%s", self._config[CONF_NAME], self._config[CONF_STATE_TOPIC], msg.payload, template_info, ) return if self._delay_listener is not None: self._delay_listener() self._delay_listener = None off_delay = self._config.get(CONF_OFF_DELAY) if self._state and off_delay is not None: self._delay_listener = evt.async_call_later( self.hass, off_delay, off_delay_listener ) self.async_write_ha_state()
async def lock_action(self, action): await self.hass.async_add_executor_job(self.kia_uvo_api.lock_action, self.token, action) self.lock_action_loop_count = 0 self.lock_action_loop = async_call_later(self.hass, 5, self.force_update_loop)
def __init__(self, key, delay, params): self.key = key self.params = params async_call_later(_hass, delay, self.call)