def async_setup(hass, config): """Initialize the DuckDNS component.""" domain = config[DOMAIN][CONF_DOMAIN] token = config[DOMAIN][CONF_ACCESS_TOKEN] session = async_get_clientsession(hass) result = yield from _update_duckdns(session, domain, token) if not result: return False @asyncio.coroutine def update_domain_interval(now): """Update the DuckDNS entry.""" yield from _update_duckdns(session, domain, token) @asyncio.coroutine def update_domain_service(call): """Update the DuckDNS entry.""" yield from _update_duckdns(session, domain, token, txt=call.data[ATTR_TXT]) async_track_time_interval(hass, update_domain_interval, INTERVAL) hass.services.async_register( DOMAIN, SERVICE_SET_TXT, update_domain_service, schema=SERVICE_TXT_SCHEMA) return result
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the scenes stored in the LIFX Cloud.""" token = config.get(CONF_TOKEN) timeout = config.get(CONF_TIMEOUT) headers = { AUTHORIZATION: "Bearer {}".format(token), } url = LIFX_API_URL.format('scenes') try: httpsession = async_get_clientsession(hass) with async_timeout.timeout(timeout, loop=hass.loop): scenes_resp = yield from httpsession.get(url, headers=headers) except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.exception("Error on %s", url) return False status = scenes_resp.status if status == 200: data = yield from scenes_resp.json() devices = [] for scene in data: devices.append(LifxCloudScene(hass, headers, timeout, scene)) async_add_devices(devices) return True elif status == 401: _LOGGER.error("Unauthorized (bad token?) on %s", url) return False _LOGGER.error("HTTP error %d on %s", scenes_resp.status, url) return False
async def async_camera_image(self): """Return a still image response from the camera.""" # DigestAuth is not supported if self._authentication == HTTP_DIGEST_AUTHENTICATION or \ self._still_image_url is None: image = await self.hass.async_add_job( self.camera_image) return image websession = async_get_clientsession( self.hass, verify_ssl=self._verify_ssl ) try: with async_timeout.timeout(10, loop=self.hass.loop): response = await websession.get( self._still_image_url, auth=self._auth) image = await response.read() return image except asyncio.TimeoutError: _LOGGER.error("Timeout getting camera image") except aiohttp.ClientError as err: _LOGGER.error("Error getting new camera image: %s", err)
async def get_bridge(hass, host, username=None): """Create a bridge object and verify authentication.""" import aiohue bridge = aiohue.Bridge( host, username=username, websession=aiohttp_client.async_get_clientsession(hass) ) try: with async_timeout.timeout(5): # Create username if we don't have one if not username: await bridge.create_user('home-assistant') # Initialize bridge (and validate our username) await bridge.initialize() return bridge except (aiohue.LinkButtonNotPressed, aiohue.Unauthorized): LOGGER.warning("Connected to Hue at %s but not registered.", host) raise AuthenticationRequired except (asyncio.TimeoutError, aiohue.RequestError): LOGGER.error("Error connecting to the Hue bridge at %s", host) raise CannotConnect except aiohue.AiohueException: LOGGER.exception('Unknown Hue linking error occurred') raise AuthenticationRequired
async def handle_async_mjpeg_stream(self, request): """Return an MJPEG stream.""" # The snapshot implementation is handled by the parent class if self._stream_source == STREAM_SOURCE_LIST['snapshot']: return await super().handle_async_mjpeg_stream(request) if self._stream_source == STREAM_SOURCE_LIST['mjpeg']: # stream an MJPEG image stream directly from the camera websession = async_get_clientsession(self.hass) streaming_url = self._camera.mjpeg_url(typeno=self._resolution) stream_coro = websession.get( streaming_url, auth=self._token, timeout=TIMEOUT) return await async_aiohttp_proxy_web( self.hass, request, stream_coro) # streaming via ffmpeg from haffmpeg import CameraMjpeg streaming_url = self._camera.rtsp_url(typeno=self._resolution) stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) await stream.open_camera( streaming_url, extra_cmd=self._ffmpeg_arguments) try: return await async_aiohttp_proxy_stream( self.hass, request, stream, 'multipart/x-mixed-replace;boundary=ffserver') finally: await stream.close()
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the RESTful switch.""" name = config.get(CONF_NAME) resource = config.get(CONF_RESOURCE) body_on = config.get(CONF_BODY_ON) body_off = config.get(CONF_BODY_OFF) is_on_template = config.get(CONF_IS_ON_TEMPLATE) websession = async_get_clientsession(hass) if is_on_template is not None: is_on_template.hass = hass if body_on is not None: body_on.hass = hass if body_off is not None: body_off.hass = hass timeout = config.get(CONF_TIMEOUT) req = None try: with async_timeout.timeout(timeout, loop=hass.loop): req = yield from websession.get(resource) except (TypeError, ValueError): _LOGGER.error("Missing resource or schema in configuration. " "Add http:// or https:// to your URL") return False except (asyncio.TimeoutError, aiohttp.errors.ClientError): _LOGGER.error("No route to resource/endpoint: %s", resource) return False finally: if req is not None: yield from req.release() yield from async_add_devices( [RestSwitch(hass, name, resource, body_on, body_off, is_on_template, timeout)])
def async_update(self): """Get the latest data from REST API and update the state.""" websession = async_get_clientsession(self.hass) request = None try: with async_timeout.timeout(self._timeout, loop=self.hass.loop): request = yield from websession.get(self._resource) text = yield from request.text() except (asyncio.TimeoutError, aiohttp.errors.ClientError): _LOGGER.exception("Error while fetch data.") return finally: if request is not None: yield from request.release() if self._is_on_template is not None: text = self._is_on_template.async_render_with_possible_json_value( text, 'None') text = text.lower() if text == 'true': self._state = True elif text == 'false': self._state = False else: self._state = None else: if text == self._body_on.template: self._state = True elif text == self._body_off.template: self._state = False else: self._state = None
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the devices associated with the account.""" from foobot_async import FoobotClient token = config.get(CONF_TOKEN) username = config.get(CONF_USERNAME) client = FoobotClient(token, username, async_get_clientsession(hass), timeout=TIMEOUT) dev = [] try: devices = await client.get_devices() _LOGGER.debug("The following devices were found: %s", devices) for device in devices: foobot_data = FoobotData(client, device['uuid']) for sensor_type in SENSOR_TYPES: if sensor_type == 'time': continue foobot_sensor = FoobotSensor(foobot_data, device, sensor_type) dev.append(foobot_sensor) except (aiohttp.client_exceptions.ClientConnectorError, asyncio.TimeoutError, FoobotClient.TooManyRequests, FoobotClient.InternalError): _LOGGER.exception('Failed to connect to foobot servers.') raise PlatformNotReady except FoobotClient.ClientError: _LOGGER.error('Failed to fetch data from foobot servers.') return async_add_entities(dev, True)
async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): """Set up the Luftdaten sensor.""" from luftdaten import Luftdaten name = config.get(CONF_NAME) show_on_map = config.get(CONF_SHOW_ON_MAP) sensor_id = config.get(CONF_SENSORID) session = async_get_clientsession(hass) luftdaten = LuftdatenData(Luftdaten(sensor_id, hass.loop, session)) await luftdaten.async_update() if luftdaten.data is None: _LOGGER.error("Sensor is not available: %s", sensor_id) return devices = [] for variable in config[CONF_MONITORED_CONDITIONS]: if luftdaten.data.values[variable] is None: _LOGGER.warning("It might be that sensor %s is not providing " "measurements for %s", sensor_id, variable) devices.append( LuftdatenSensor(luftdaten, name, variable, sensor_id, show_on_map)) async_add_entities(devices)
def send_volumio_msg(self, method, params=None): """Send message.""" url = "http://{}:{}/api/v1/{}/".format(self.host, self.port, method) _LOGGER.debug("URL: %s params: %s", url, params) try: websession = async_get_clientsession(self.hass) response = yield from websession.get(url, params=params) if response.status == 200: data = yield from response.json() else: _LOGGER.error( "Query failed, response code: %s Full message: %s", response.status, response) return False except (asyncio.TimeoutError, aiohttp.ClientError) as error: _LOGGER.error("Failed communicating with Volumio: %s", type(error)) return False try: return data except AttributeError: _LOGGER.error("Received invalid response: %s", data) return False
def async_update(self): """Get the current state from The Things Network Data Storage.""" try: session = async_get_clientsession(self._hass) with async_timeout.timeout(DEFAULT_TIMEOUT, loop=self._hass.loop): req = yield from session.get(self._url, headers=self._headers) except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Error while accessing: %s", self._url) return False status = req.status if status == 204: _LOGGER.error("The device is not available: %s", self._device_id) return False if status == 401: _LOGGER.error( "Not authorized for Application ID: %s", self._app_id) return False if status == 404: _LOGGER.error("Application ID is not available: %s", self._app_id) return False data = yield from req.json() self.data = data[0] for value in self._values.items(): if value[0] not in self.data.keys(): _LOGGER.warning("Value not available: %s", value[0]) return req
def async_get_tts_audio(self, message, language, options=None): """Load TTS from yandex.""" websession = async_get_clientsession(self.hass) actual_language = language try: with async_timeout.timeout(10, loop=self.hass.loop): url_param = { 'text': message, 'lang': actual_language, 'key': self._key, 'speaker': self._speaker, 'format': self._codec, 'emotion': self._emotion, 'speed': self._speed } request = yield from websession.get( YANDEX_API_URL, params=url_param) if request.status != 200: _LOGGER.error("Error %d on load URL %s", request.status, request.url) return (None, None) data = yield from request.read() except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Timeout for yandex speech kit API") return (None, None) return (self._codec, data)
async def get_service(hass, config, discovery_info=None): """Get the Flock notification service.""" access_token = config.get(CONF_ACCESS_TOKEN) url = '{}{}'.format(_RESOURCE, access_token) session = async_get_clientsession(hass) return FlockNotificationService(url, session, hass.loop)
async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): """Set up the Glances sensors.""" from glances_api import Glances name = config[CONF_NAME] host = config[CONF_HOST] port = config[CONF_PORT] version = config[CONF_VERSION] var_conf = config[CONF_RESOURCES] username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) ssl = config[CONF_SSL] verify_ssl = config[CONF_VERIFY_SSL] session = async_get_clientsession(hass, verify_ssl) glances = GlancesData( Glances(hass.loop, session, host=host, port=port, version=version, username=username, password=password, ssl=ssl)) await glances.async_update() if glances.api.data is None: raise PlatformNotReady dev = [] for resource in var_conf: dev.append(GlancesSensor(glances, name, resource)) async_add_entities(dev, True)
def _setup_atv(hass, atv_config): """Setup an Apple TV.""" import pyatv name = atv_config.get(CONF_NAME) host = atv_config.get(CONF_HOST) login_id = atv_config.get(CONF_LOGIN_ID) start_off = atv_config.get(CONF_START_OFF) credentials = atv_config.get(CONF_CREDENTIALS) if host in hass.data[DATA_APPLE_TV]: return details = pyatv.AppleTVDevice(name, host, login_id) session = async_get_clientsession(hass) atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session) if credentials: yield from atv.airplay.load_credentials(credentials) power = AppleTVPowerManager(hass, atv, start_off) hass.data[DATA_APPLE_TV][host] = { ATTR_ATV: atv, ATTR_POWER: power } hass.async_add_job(discovery.async_load_platform( hass, 'media_player', DOMAIN, atv_config)) hass.async_add_job(discovery.async_load_platform( hass, 'remote', DOMAIN, atv_config))
def __init__(self, hass, host, port=None, name=None, init_callback=None): """Initialize the media player.""" self.host = host self._hass = hass self.port = port self._polling_session = async_get_clientsession(hass) self._polling_task = None # The actual polling task. self._name = name self._icon = None self._capture_items = [] self._services_items = [] self._preset_items = [] self._sync_status = {} self._status = None self._last_status_update = None self._is_online = False self._retry_remove = None self._lastvol = None self._master = None self._is_master = False self._group_name = None self._init_callback = init_callback if self.port is None: self.port = DEFAULT_PORT
def get_newest_version(hass, huuid, include_components): """Get the newest Home Assistant version.""" if huuid: info_object = yield from get_system_info(hass, include_components) info_object['huuid'] = huuid else: info_object = {} session = async_get_clientsession(hass) try: with async_timeout.timeout(5, loop=hass.loop): req = yield from session.post(UPDATER_URL, json=info_object) _LOGGER.info(("Submitted analytics to Home Assistant servers. " "Information submitted includes %s"), info_object) except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Could not contact Home Assistant Update to check " "for updates") return None try: res = yield from req.json() except ValueError: _LOGGER.error("Received invalid JSON from Home Assistant Update") return None try: res = RESPONSE_SCHEMA(res) return res['version'], res['release-notes'] except vol.Invalid: _LOGGER.error("Got unexpected response: %s", res) return None
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 async_setup_platform( hass, config, async_add_entities, discovery_info=None): """Set up the Netdata sensor.""" from netdata import Netdata name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) resources = config.get(CONF_RESOURCES) session = async_get_clientsession(hass) netdata = NetdataData(Netdata(host, hass.loop, session, port=port)) await netdata.async_update() if netdata.api.metrics is None: raise PlatformNotReady dev = [] for entry, data in resources.items(): icon = data[CONF_ICON] sensor = data[CONF_DATA_GROUP] element = data[CONF_ELEMENT] sensor_name = entry try: resource_data = netdata.api.metrics[sensor] unit = '%' if resource_data['units'] == 'percentage' else \ resource_data['units'] except KeyError: _LOGGER.error("Sensor is not available: %s", sensor) continue dev.append(NetdataSensor( netdata, name, sensor, sensor_name, element, icon, unit)) async_add_entities(dev, True)
async def get_device_state(self, hass): """Get the latest data from REST API and update the state.""" websession = async_get_clientsession(hass) with async_timeout.timeout(self._timeout, loop=hass.loop): req = await websession.get(self._resource, auth=self._auth, headers=self._headers) text = await req.text() if self._is_on_template is not None: text = self._is_on_template.async_render_with_possible_json_value( text, 'None') text = text.lower() if text == 'true': self._state = True elif text == 'false': self._state = False else: self._state = None else: if text == self._body_on.template: self._state = True elif text == self._body_off.template: self._state = False else: self._state = None return req
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Mill heater.""" from mill import Mill mill_data_connection = Mill(config[CONF_USERNAME], config[CONF_PASSWORD], websession=async_get_clientsession(hass)) if not await mill_data_connection.connect(): _LOGGER.error("Failed to connect to Mill") return await mill_data_connection.find_all_heaters() dev = [] for heater in mill_data_connection.heaters.values(): dev.append(MillHeater(heater, mill_data_connection)) async_add_entities(dev) async def set_room_temp(service): """Set room temp.""" room_name = service.data.get(ATTR_ROOM_NAME) sleep_temp = service.data.get(ATTR_SLEEP_TEMP) comfort_temp = service.data.get(ATTR_COMFORT_TEMP) away_temp = service.data.get(ATTR_AWAY_TEMP) await mill_data_connection.set_room_temperatures_by_name(room_name, sleep_temp, comfort_temp, away_temp) hass.services.async_register(DOMAIN, SERVICE_SET_ROOM_TEMP, set_room_temp, schema=SET_ROOM_TEMP_SCHEMA)
async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" from aioambient import Client from aioambient.errors import AmbientError if not user_input: return await self._show_form() if user_input[CONF_APP_KEY] in configured_instances(self.hass): return await self._show_form({CONF_APP_KEY: 'identifier_exists'}) session = aiohttp_client.async_get_clientsession(self.hass) client = Client( user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session) try: devices = await client.api.get_devices() except AmbientError: return await self._show_form({'base': 'invalid_key'}) if not devices: return await self._show_form({'base': 'no_devices'}) # The Application Key (which identifies each config entry) is too long # to show nicely in the UI, so we take the first 12 characters (similar # to how GitHub does it): return self.async_create_entry( title=user_input[CONF_APP_KEY][:12], data=user_input)
def async_get_tts_audio(self, message): """Load TTS from voicerss.""" websession = async_get_clientsession(self.hass) form_data = self.form_data.copy() form_data['src'] = message request = None try: with async_timeout.timeout(10, loop=self.hass.loop): request = yield from websession.post( VOICERSS_API_URL, data=form_data ) if request.status != 200: _LOGGER.error("Error %d on load url %s.", request.status, request.url) return (None, None) data = yield from request.read() if data in ERROR_MSG: _LOGGER.error( "Error receive %s from voicerss.", str(data, 'utf-8')) return (None, None) except (asyncio.TimeoutError, aiohttp.errors.ClientError): _LOGGER.error("Timeout for voicerss api.") return (None, None) finally: if request is not None: yield from request.release() return (self.extension, data)
async def handle_async_mjpeg_stream(self, request): """Return an MJPEG stream.""" # The snapshot implementation is handled by the parent class if self._stream_source == 'snapshot': return await super().handle_async_mjpeg_stream(request) if self._stream_source == 'mjpeg': # stream an MJPEG image stream directly from the camera websession = async_get_clientsession(self.hass) streaming_url = self._api.mjpeg_url(typeno=self._resolution) stream_coro = websession.get( streaming_url, auth=self._token, timeout=CAMERA_WEB_SESSION_TIMEOUT) return await async_aiohttp_proxy_web( self.hass, request, stream_coro) # streaming via ffmpeg from haffmpeg.camera import CameraMjpeg streaming_url = self._api.rtsp_url(typeno=self._resolution) stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) await stream.open_camera( streaming_url, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( self.hass, request, stream_reader, self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close()
async def async_setup_entry(hass, config_entry): """Set up the Ambient PWS as config entry.""" from aioambient import Client from aioambient.errors import WebsocketError session = aiohttp_client.async_get_clientsession(hass) try: ambient = AmbientStation( hass, config_entry, Client( config_entry.data[CONF_API_KEY], config_entry.data[CONF_APP_KEY], session), hass.data[DOMAIN].get(DATA_CONFIG, {}).get( CONF_MONITORED_CONDITIONS, [])) hass.loop.create_task(ambient.ws_connect()) hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient except WebsocketError as err: _LOGGER.error('Config entry failed: %s', err) raise ConfigEntryNotReady hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, ambient.client.websocket.disconnect()) return True
def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up home assistant scene entries.""" # from aiopvapi.hub import Hub from aiopvapi.scenes import Scenes from aiopvapi.rooms import Rooms from aiopvapi.resources.scene import Scene as PvScene hub_address = config.get(HUB_ADDRESS) websession = async_get_clientsession(hass) _scenes = yield from Scenes( hub_address, hass.loop, websession).get_resources() _rooms = yield from Rooms( hub_address, hass.loop, websession).get_resources() if not _scenes or not _rooms: _LOGGER.error( "Unable to initialize PowerView hub: %s", hub_address) return pvscenes = (PowerViewScene(hass, PvScene(_raw_scene, hub_address, hass.loop, websession), _rooms) for _raw_scene in _scenes[SCENE_DATA]) async_add_entities(pvscenes)
async def async_setup_platform( hass, config, async_add_devices, discovery_info=None): """Set up the Pi-hole sensor.""" from hole import Hole name = config.get(CONF_NAME) host = config.get(CONF_HOST) use_tls = config.get(CONF_SSL) location = config.get(CONF_LOCATION) verify_tls = config.get(CONF_VERIFY_SSL) session = async_get_clientsession(hass) pi_hole = PiHoleData(Hole( host, hass.loop, session, location=location, tls=use_tls, verify_tls=verify_tls)) await pi_hole.async_update() if pi_hole.api.data is None: raise PlatformNotReady sensors = [PiHoleSensor(pi_hole, name, condition) for condition in config[CONF_MONITORED_CONDITIONS]] async_add_devices(sensors, True)
async def get_data(self, url): """Load data from specified url.""" from buienradar.buienradar import (CONTENT, MESSAGE, STATUS_CODE, SUCCESS) _LOGGER.debug("Calling url: %s...", url) result = {SUCCESS: False, MESSAGE: None} resp = None try: websession = async_get_clientsession(self.hass) with async_timeout.timeout(10, loop=self.hass.loop): resp = await websession.get(url) result[STATUS_CODE] = resp.status result[CONTENT] = await resp.text() if resp.status == 200: result[SUCCESS] = True else: result[MESSAGE] = "Got http statuscode: %d" % (resp.status) return result except (asyncio.TimeoutError, aiohttp.ClientError) as err: result[MESSAGE] = "%s" % err return result finally: if resp is not None: await resp.release()
def __init__(self, hass, name, host, port, tcp_port, encryption=False, username=None, password=None, turn_off_action=None, websocket=True): """Initialize the Kodi device.""" import jsonrpc_async import jsonrpc_websocket self.hass = hass self._name = name kwargs = { 'timeout': DEFAULT_TIMEOUT, 'session': async_get_clientsession(hass), } if username is not None: kwargs['auth'] = aiohttp.BasicAuth(username, password) image_auth_string = "{}:{}@".format(username, password) else: image_auth_string = "" http_protocol = 'https' if encryption else 'http' ws_protocol = 'wss' if encryption else 'ws' self._http_url = '{}://{}:{}/jsonrpc'.format(http_protocol, host, port) self._image_url = '{}://{}{}:{}/image'.format( http_protocol, image_auth_string, host, port) self._ws_url = '{}://{}:{}/jsonrpc'.format(ws_protocol, host, tcp_port) self._http_server = jsonrpc_async.Server(self._http_url, **kwargs) if websocket: # Setup websocket connection self._ws_server = jsonrpc_websocket.Server(self._ws_url, **kwargs) # Register notification listeners self._ws_server.Player.OnPause = self.async_on_speed_event self._ws_server.Player.OnPlay = self.async_on_speed_event self._ws_server.Player.OnSpeedChanged = self.async_on_speed_event self._ws_server.Player.OnStop = self.async_on_stop self._ws_server.Application.OnVolumeChanged = \ self.async_on_volume_changed self._ws_server.System.OnQuit = self.async_on_quit self._ws_server.System.OnRestart = self.async_on_quit self._ws_server.System.OnSleep = self.async_on_quit def on_hass_stop(event): """Close websocket connection when hass stops.""" self.hass.async_add_job(self._ws_server.close()) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, on_hass_stop) else: self._ws_server = None self._turn_off_action = turn_off_action self._enable_websocket = websocket self._players = list() self._properties = {} self._item = {} self._app_properties = {} self._ws_connected = False
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup the Apple TV platform.""" import pyatv if discovery_info is not None: name = discovery_info['name'] host = discovery_info['host'] login_id = discovery_info['properties']['hG'] start_off = False else: name = config.get(CONF_NAME) host = config.get(CONF_HOST) login_id = config.get(CONF_LOGIN_ID) start_off = config.get(CONF_START_OFF) if DATA_APPLE_TV not in hass.data: hass.data[DATA_APPLE_TV] = [] if host in hass.data[DATA_APPLE_TV]: return False hass.data[DATA_APPLE_TV].append(host) details = pyatv.AppleTVDevice(name, host, login_id) session = async_get_clientsession(hass) atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session) entity = AppleTvDevice(atv, name, start_off) @callback def on_hass_stop(event): """Stop push updates when hass stops.""" atv.push_updater.stop() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) async_add_devices([entity])
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Plugwise Smiles from a config entry.""" websession = async_get_clientsession(hass, verify_ssl=False) api = Smile( host=entry.data["host"], password=entry.data["password"], websession=websession ) try: connected = await api.connect() if not connected: _LOGGER.error("Unable to connect to Smile") raise ConfigEntryNotReady except Smile.InvalidAuthentication: _LOGGER.error("Invalid Smile ID") return False except Smile.PlugwiseError: _LOGGER.error("Error while communicating to device") raise ConfigEntryNotReady except asyncio.TimeoutError: _LOGGER.error("Timeout while connecting to Smile") raise ConfigEntryNotReady if api.smile_type == "power": update_interval = timedelta(seconds=10) else: update_interval = timedelta(seconds=60) async def async_update_data(): """Update data via API endpoint.""" try: async with async_timeout.timeout(10): await api.full_update_device() return True except Smile.XMLDataMissingError: raise UpdateFailed("Smile update failed") coordinator = DataUpdateCoordinator( hass, _LOGGER, name="Smile", update_method=async_update_data, update_interval=update_interval, ) await coordinator.async_refresh() if not coordinator.last_update_success: raise ConfigEntryNotReady api.get_all_devices() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { "api": api, "coordinator": coordinator, } device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, api.gateway_id)}, manufacturer="Plugwise", name=entry.title, model=f"Smile {api.smile_name}", sw_version=api.smile_version[0], ) platforms = ALL_PLATFORMS single_master_thermostat = api.single_master_thermostat() if single_master_thermostat is None: platforms = SENSOR_PLATFORMS for component in platforms: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) return True
def async_setup_scanner(hass, config, async_see, discovery_info=None): """Validate the configuration and return an Automatic scanner.""" import aioautomatic hass.http.register_view(AutomaticAuthCallbackView()) scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE client = aioautomatic.Client( client_id=config[CONF_CLIENT_ID], client_secret=config[CONF_SECRET], client_session=async_get_clientsession(hass), request_kwargs={"timeout": DEFAULT_TIMEOUT}, ) filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID]) refresh_token = yield from hass.async_add_job(_get_refresh_token_from_file, hass, filename) @asyncio.coroutine def initialize_data(session): """Initialize the AutomaticData object from the created session.""" hass.async_add_job(_write_refresh_token_to_file, hass, filename, session.refresh_token) data = AutomaticData(hass, client, session, config.get(CONF_DEVICES), async_see) # Load the initial vehicle data vehicles = yield from session.get_vehicles() for vehicle in vehicles: hass.async_create_task(data.load_vehicle(vehicle)) # Create a task instead of adding a tracking job, since this task will # run until the websocket connection is closed. hass.loop.create_task(data.ws_connect()) if refresh_token is not None: try: session = yield from client.create_session_from_refresh_token( refresh_token) yield from initialize_data(session) return True except aioautomatic.exceptions.AutomaticError as err: _LOGGER.error(str(err)) configurator = hass.components.configurator request_id = configurator.async_request_config( "Automatic", description=("Authorization required for Automatic device tracker."), link_name="Click here to authorize Home Assistant.", link_url=client.generate_oauth_url(scope), entity_picture="/static/images/logo_automatic.png", ) @asyncio.coroutine def initialize_callback(code, state): """Call after OAuth2 response is returned.""" try: session = yield from client.create_session_from_oauth_code( code, state) yield from initialize_data(session) configurator.async_request_done(request_id) except aioautomatic.exceptions.AutomaticError as err: _LOGGER.error(str(err)) configurator.async_notify_errors(request_id, str(err)) return False if DATA_CONFIGURING not in hass.data: hass.data[DATA_CONFIGURING] = {} hass.data[DATA_CONFIGURING][client.state] = initialize_callback return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up AirVisual as config entry.""" if CONF_API_KEY in entry.data: _standardize_geography_config_entry(hass, entry) websession = aiohttp_client.async_get_clientsession(hass) cloud_api = CloudAPI(entry.data[CONF_API_KEY], session=websession) async def async_update_data() -> dict[str, Any]: """Get new data from the API.""" if CONF_CITY in entry.data: api_coro = cloud_api.air_quality.city( entry.data[CONF_CITY], entry.data[CONF_STATE], entry.data[CONF_COUNTRY], ) else: api_coro = cloud_api.air_quality.nearest_city( entry.data[CONF_LATITUDE], entry.data[CONF_LONGITUDE], ) try: data = await api_coro return cast(Dict[str, Any], data) except (InvalidKeyError, KeyExpiredError) as ex: raise ConfigEntryAuthFailed from ex except AirVisualError as err: raise UpdateFailed( f"Error while retrieving data: {err}") from err coordinator = DataUpdateCoordinator( hass, LOGGER, name=async_get_geography_id(entry.data), # We give a placeholder update interval in order to create the coordinator; # then, below, we use the coordinator's presence (along with any other # coordinators using the same API key) to calculate an actual, leveled # update interval: update_interval=timedelta(minutes=5), update_method=async_update_data, ) # Only geography-based entries have options: entry.async_on_unload(entry.add_update_listener(async_reload_entry)) else: # Remove outdated air_quality entities from the entity registry if they exist: ent_reg = entity_registry.async_get(hass) for entity_entry in [ e for e in ent_reg.entities.values() if e.config_entry_id == entry.entry_id and e.entity_id.startswith("air_quality") ]: LOGGER.debug('Removing deprecated air_quality entity: "%s"', entity_entry.entity_id) ent_reg.async_remove(entity_entry.entity_id) _standardize_node_pro_config_entry(hass, entry) async def async_update_data() -> dict[str, Any]: """Get new data from the API.""" try: async with NodeSamba(entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD]) as node: data = await node.async_get_latest_measurements() return cast(Dict[str, Any], data) except NodeProError as err: raise UpdateFailed( f"Error while retrieving data: {err}") from err coordinator = DataUpdateCoordinator( hass, LOGGER, name="Node/Pro data", update_interval=DEFAULT_NODE_PRO_UPDATE_INTERVAL, update_method=async_update_data, ) await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = {DATA_COORDINATOR: coordinator} # Reassess the interval between 2 server requests if CONF_API_KEY in entry.data: async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY]) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Plugwise Smiles from a config entry.""" websession = async_get_clientsession(hass, verify_ssl=False) api = Smile( host=entry.data[CONF_HOST], username=entry.data.get(CONF_USERNAME, DEFAULT_USERNAME), password=entry.data[CONF_PASSWORD], port=entry.data.get(CONF_PORT, DEFAULT_PORT), timeout=30, websession=websession, ) try: connected = await api.connect() if not connected: _LOGGER.error("Unable to connect to Smile") raise ConfigEntryNotReady except InvalidAuthentication: _LOGGER.error("Invalid username or Smile ID") return False except PlugwiseException as err: _LOGGER.error("Error while communicating to device %s", api.smile_name) raise ConfigEntryNotReady from err except asyncio.TimeoutError as err: _LOGGER.error("Timeout while connecting to Smile %s", api.smile_name) raise ConfigEntryNotReady from err update_interval = timedelta(seconds=entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL[api.smile_type])) async def async_update_data(): """Update data via API endpoint.""" try: async with async_timeout.timeout(DEFAULT_TIMEOUT): await api.full_update_device() return True except XMLDataMissingError as err: raise UpdateFailed("Smile update failed") from err coordinator = DataUpdateCoordinator( hass, _LOGGER, name=f"Smile {api.smile_name}", update_method=async_update_data, update_interval=update_interval, ) await coordinator.async_refresh() if not coordinator.last_update_success: raise ConfigEntryNotReady api.get_all_devices() if entry.unique_id is None: if api.smile_version[0] != "1.8.0": hass.config_entries.async_update_entry( entry, unique_id=api.smile_hostname) undo_listener = entry.add_update_listener(_update_listener) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { "api": api, COORDINATOR: coordinator, PW_TYPE: GATEWAY, UNDO_UPDATE_LISTENER: undo_listener, } device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, api.gateway_id)}, manufacturer="Plugwise", name=entry.title, model=f"Smile {api.smile_name}", sw_version=api.smile_version[0], ) single_master_thermostat = api.single_master_thermostat() platforms = PLATFORMS_GATEWAY if single_master_thermostat is None: platforms = SENSOR_PLATFORMS for component in platforms: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component)) return True
async def async_setup_entry(hass, entry): """Set up Plex from a config entry.""" server_config = entry.data[PLEX_SERVER_CONFIG] if entry.unique_id is None: hass.config_entries.async_update_entry( entry, unique_id=entry.data[CONF_SERVER_IDENTIFIER]) if MP_DOMAIN not in entry.options: options = dict(entry.options) options.setdefault(MP_DOMAIN, {}) hass.config_entries.async_update_entry(entry, options=options) plex_server = PlexServer( hass, server_config, entry.data[CONF_SERVER_IDENTIFIER], entry.options, entry.entry_id, ) try: await hass.async_add_executor_job(plex_server.connect) except ShouldUpdateConfigEntry: new_server_data = { **entry.data[PLEX_SERVER_CONFIG], CONF_URL: plex_server.url_in_use, CONF_SERVER: plex_server.friendly_name, } hass.config_entries.async_update_entry( entry, data={ **entry.data, PLEX_SERVER_CONFIG: new_server_data }) except requests.exceptions.ConnectionError as error: if entry.state != ENTRY_STATE_SETUP_RETRY: _LOGGER.error( "Plex server (%s) could not be reached: [%s]", server_config[CONF_URL], error, ) raise ConfigEntryNotReady from error except plexapi.exceptions.Unauthorized: hass.async_create_task( hass.config_entries.flow.async_init( PLEX_DOMAIN, context={CONF_SOURCE: SOURCE_REAUTH}, data=entry.data, )) _LOGGER.error( "Token not accepted, please reauthenticate Plex server '%s'", entry.data[CONF_SERVER], ) return False except ( plexapi.exceptions.BadRequest, plexapi.exceptions.NotFound, ) as error: _LOGGER.error( "Login to %s failed, verify token and SSL settings: [%s]", entry.data[CONF_SERVER], error, ) return False _LOGGER.debug("Connected to: %s (%s)", plex_server.friendly_name, plex_server.url_in_use) server_id = plex_server.machine_identifier hass.data[PLEX_DOMAIN][SERVERS][server_id] = plex_server hass.data[PLEX_DOMAIN][PLATFORMS_COMPLETED][server_id] = set() entry.add_update_listener(async_options_updated) unsub = async_dispatcher_connect( hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id), plex_server.async_update_platforms, ) hass.data[PLEX_DOMAIN][DISPATCHERS].setdefault(server_id, []) hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) @callback def plex_websocket_callback(msgtype, data, error): """Handle callbacks from plexwebsocket library.""" if msgtype == SIGNAL_CONNECTION_STATE: if data == STATE_CONNECTED: _LOGGER.debug("Websocket to %s successful", entry.data[CONF_SERVER]) hass.async_create_task(plex_server.async_update_platforms()) elif data == STATE_DISCONNECTED: _LOGGER.debug("Websocket to %s disconnected, retrying", entry.data[CONF_SERVER]) # Stopped websockets without errors are expected during shutdown and ignored elif data == STATE_STOPPED and error: _LOGGER.error( "Websocket to %s failed, aborting [Error: %s]", entry.data[CONF_SERVER], error, ) hass.async_create_task( hass.config_entries.async_reload(entry.entry_id)) elif msgtype == "playing": hass.async_create_task(plex_server.async_update_session(data)) elif msgtype == "status": if data["StatusNotification"][0][ "title"] == "Library scan complete": async_dispatcher_send( hass, PLEX_UPDATE_LIBRARY_SIGNAL.format(server_id), ) session = async_get_clientsession(hass) subscriptions = ["playing", "status"] verify_ssl = server_config.get(CONF_VERIFY_SSL) websocket = PlexWebsocket( plex_server.plex_server, plex_websocket_callback, subscriptions=subscriptions, session=session, verify_ssl=verify_ssl, ) hass.data[PLEX_DOMAIN][WEBSOCKETS][server_id] = websocket def start_websocket_session(platform, _): hass.data[PLEX_DOMAIN][PLATFORMS_COMPLETED][server_id].add(platform) if hass.data[PLEX_DOMAIN][PLATFORMS_COMPLETED][server_id] == PLATFORMS: hass.loop.create_task(websocket.listen()) def close_websocket_session(_): websocket.close() unsub = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_websocket_session) hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) for platform in PLATFORMS: task = hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform)) task.add_done_callback(partial(start_websocket_session, platform)) async_cleanup_plex_devices(hass, entry) def get_plex_account(plex_server): try: return plex_server.account except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): return None await hass.async_add_executor_job(get_plex_account, plex_server) return True
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Initialize config entry which represents an installed SmartApp.""" from pysmartthings import SmartThings if not hass.config.api.base_url.lower().startswith('https://'): _LOGGER.warning("The 'base_url' of the 'http' component must be " "configured and start with 'https://'") return False api = SmartThings(async_get_clientsession(hass), entry.data[CONF_ACCESS_TOKEN]) remove_entry = False try: # See if the app is already setup. This occurs when there are # installs in multiple SmartThings locations (valid use-case) manager = hass.data[DOMAIN][DATA_MANAGER] smart_app = manager.smartapps.get(entry.data[CONF_APP_ID]) if not smart_app: # Validate and setup the app. app = await api.app(entry.data[CONF_APP_ID]) smart_app = setup_smartapp(hass, app) # Validate and retrieve the installed app. installed_app = await validate_installed_app( api, entry.data[CONF_INSTALLED_APP_ID]) # Get devices and their current status devices = await api.devices( location_ids=[installed_app.location_id]) async def retrieve_device_status(device): try: await device.status.refresh() except ClientResponseError: _LOGGER.debug("Unable to update status for device: %s (%s), " "the device will be ignored", device.label, device.device_id, exc_info=True) devices.remove(device) await asyncio.gather(*[retrieve_device_status(d) for d in devices.copy()]) # Setup device broker broker = DeviceBroker(hass, devices, installed_app.installed_app_id) broker.event_handler_disconnect = \ smart_app.connect_event(broker.event_handler) hass.data[DOMAIN][DATA_BROKERS][entry.entry_id] = broker except ClientResponseError as ex: if ex.status in (401, 403): _LOGGER.exception("Unable to setup config entry '%s' - please " "reconfigure the integration", entry.title) remove_entry = True else: _LOGGER.debug(ex, exc_info=True) raise ConfigEntryNotReady except (ClientConnectionError, RuntimeWarning) as ex: _LOGGER.debug(ex, exc_info=True) raise ConfigEntryNotReady if remove_entry: hass.async_create_task( hass.config_entries.async_remove(entry.entry_id)) # only create new flow if there isn't a pending one for SmartThings. flows = hass.config_entries.flow.async_progress() if not [flow for flow in flows if flow['handler'] == DOMAIN]: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={'source': 'import'})) return False for component in SUPPORTED_PLATFORMS: hass.async_create_task(hass.config_entries.async_forward_entry_setup( entry, component)) return True
async def async_connect(self): """Connect to remote home-assistant websocket...""" async def _async_stop_handler(event): """Stop when Home Assistant is shutting down.""" await self.async_stop() async def _async_instance_get_info(): """Fetch discovery info from remote instance.""" try: return await async_get_discovery_info( self._hass, self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._secure, self._access_token, self._verify_ssl, ) except OSError: _LOGGER.exception("failed to connect") except UnsupportedVersion: _LOGGER.error( "Unsupported version, at least 0.111 is required.") except Exception: _LOGGER.exception("failed to fetch instance info") return None @callback def _async_instance_id_match(info): """Verify if remote instance id matches the expected id.""" if not info: return False if info and info["uuid"] != self._entry.unique_id: _LOGGER.error( "instance id not matching: %s != %s", info["uuid"], self._entry.unique_id, ) return False return True url = self._get_url() session = async_get_clientsession(self._hass, self._verify_ssl) self.set_connection_state(STATE_CONNECTING) while True: info = await _async_instance_get_info() # Verify we are talking to correct instance if not _async_instance_id_match(info): self.set_connection_state(STATE_RECONNECTING) await asyncio.sleep(10) continue try: _LOGGER.info("Connecting to %s", url) self._connection = await session.ws_connect(url) except aiohttp.client_exceptions.ClientError: _LOGGER.error( "Could not connect to %s, retry in 10 seconds...", url) self.set_connection_state(STATE_RECONNECTING) await asyncio.sleep(10) else: _LOGGER.info("Connected to home-assistant websocket at %s", url) break self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_handler) device_registry = await dr.async_get_registry(self._hass) device_registry.async_get_or_create( config_entry_id=self._entry.entry_id, identifiers={(DOMAIN, f"remote_{self._entry.unique_id}")}, name=info.get("location_name"), manufacturer="Home Assistant", model=info.get("installation_type"), sw_version=info.get("version"), ) asyncio.ensure_future(self._recv()) self._heartbeat_task = self._hass.loop.create_task( self._heartbeat_loop())
async def async_step_user(self, user_input=None): """Get access token and validate it.""" errors = {} if user_input is None or CONF_ACCESS_TOKEN not in user_input: return self._show_step_user(errors) self.access_token = user_input.get(CONF_ACCESS_TOKEN, "") self.api = SmartThings(async_get_clientsession(self.hass), self.access_token) # Ensure token is a UUID if not VAL_UID_MATCHER.match(self.access_token): errors[CONF_ACCESS_TOKEN] = "token_invalid_format" return self._show_step_user(errors) # Check not already setup in another entry if any( entry.data.get(CONF_ACCESS_TOKEN) == self.access_token for entry in self.hass.config_entries.async_entries(DOMAIN) ): errors[CONF_ACCESS_TOKEN] = "token_already_setup" return self._show_step_user(errors) # Setup end-point await setup_smartapp_endpoint(self.hass) if not validate_webhook_requirements(self.hass): errors["base"] = "base_url_not_https" return self._show_step_user(errors) try: app = await find_app(self.hass, self.api) if app: await app.refresh() # load all attributes await update_app(self.hass, app) # Get oauth client id/secret by regenerating it app_oauth = AppOAuth(app.app_id) app_oauth.client_name = APP_OAUTH_CLIENT_NAME app_oauth.scope.extend(APP_OAUTH_SCOPES) client = await self.api.generate_app_oauth(app_oauth) else: app, client = await create_app(self.hass, self.api) setup_smartapp(self.hass, app) self.app_id = app.app_id self.oauth_client_secret = client.client_secret self.oauth_client_id = client.client_id except APIResponseError as ex: if ex.is_target_error(): errors["base"] = "webhook_error" else: errors["base"] = "app_setup_error" _LOGGER.exception( "API error setting up the SmartApp: %s", ex.raw_error_response ) return self._show_step_user(errors) except ClientResponseError as ex: if ex.status == 401: errors[CONF_ACCESS_TOKEN] = "token_unauthorized" elif ex.status == 403: errors[CONF_ACCESS_TOKEN] = "token_forbidden" else: errors["base"] = "app_setup_error" _LOGGER.exception("Unexpected error setting up the SmartApp") return self._show_step_user(errors) except Exception: # pylint:disable=broad-except errors["base"] = "app_setup_error" _LOGGER.exception("Unexpected error setting up the SmartApp") return self._show_step_user(errors) return await self.async_step_wait_install()
async def mock_responses( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, token: str = TOKEN, error: bool = False, ): """Mock responses from Efergy.""" base_url = "https://engage.efergy.com/mobile_proxy/" api = Efergy(token, session=async_get_clientsession(hass), utc_offset="America/New_York") assert api._utc_offset == 300 if error: aioclient_mock.get( f"{base_url}getInstant?token={token}", exc=exceptions.ConnectError, ) return aioclient_mock.get( f"{base_url}getStatus?token={token}", text=load_fixture("efergy/status.json"), ) aioclient_mock.get( f"{base_url}getInstant?token={token}", text=load_fixture("efergy/instant.json"), ) aioclient_mock.get( f"{base_url}getEnergy?period=day", text=load_fixture("efergy/daily_energy.json"), ) aioclient_mock.get( f"{base_url}getEnergy?period=week", text=load_fixture("efergy/weekly_energy.json"), ) aioclient_mock.get( f"{base_url}getEnergy?period=month", text=load_fixture("efergy/monthly_energy.json"), ) aioclient_mock.get( f"{base_url}getEnergy?period=year", text=load_fixture("efergy/yearly_energy.json"), ) aioclient_mock.get( f"{base_url}getBudget?token={token}", text=load_fixture("efergy/budget.json"), ) aioclient_mock.get( f"{base_url}getCost?period=day", text=load_fixture("efergy/daily_cost.json"), ) aioclient_mock.get( f"{base_url}getCost?period=week", text=load_fixture("efergy/weekly_cost.json"), ) aioclient_mock.get( f"{base_url}getCost?period=month", text=load_fixture("efergy/monthly_cost.json"), ) aioclient_mock.get( f"{base_url}getCost?period=year", text=load_fixture("efergy/yearly_cost.json"), ) if token == TOKEN: aioclient_mock.get( f"{base_url}getCurrentValuesSummary?token={token}", text=load_fixture("efergy/current_values_single.json"), ) else: aioclient_mock.get( f"{base_url}getCurrentValuesSummary?token={token}", text=load_fixture("efergy/current_values_multi.json"), )
async def async_get_service(hass, config, discovery_info=None): """Get the mobile_app notification service.""" session = async_get_clientsession(hass) return MobileAppNotificationService(session)
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Set up AdGuard Home from a config entry.""" session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]) adguard = AdGuardHome( entry.data[CONF_HOST], port=entry.data[CONF_PORT], username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], tls=entry.data[CONF_SSL], verify_ssl=entry.data[CONF_VERIFY_SSL], session=session, ) hass.data.setdefault(DOMAIN, {})[DATA_ADGUARD_CLIENT] = adguard try: version = await adguard.version() except AdGuardHomeConnectionError as exception: raise ConfigEntryNotReady from exception if LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version): _LOGGER.error( "This integration requires AdGuard Home v0.99.0 or higher to work correctly" ) raise ConfigEntryNotReady for component in "sensor", "switch": hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component)) async def add_url(call) -> None: """Service call to add a new filter subscription to AdGuard Home.""" await adguard.filtering.add_url(call.data.get(CONF_NAME), call.data.get(CONF_URL)) async def remove_url(call) -> None: """Service call to remove a filter subscription from AdGuard Home.""" await adguard.filtering.remove_url(call.data.get(CONF_URL)) async def enable_url(call) -> None: """Service call to enable a filter subscription in AdGuard Home.""" await adguard.filtering.enable_url(call.data.get(CONF_URL)) async def disable_url(call) -> None: """Service call to disable a filter subscription in AdGuard Home.""" await adguard.filtering.disable_url(call.data.get(CONF_URL)) async def refresh(call) -> None: """Service call to refresh the filter subscriptions in AdGuard Home.""" await adguard.filtering.refresh(call.data.get(CONF_FORCE)) hass.services.async_register(DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_REMOVE_URL, remove_url, schema=SERVICE_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_ENABLE_URL, enable_url, schema=SERVICE_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_DISABLE_URL, disable_url, schema=SERVICE_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_REFRESH, refresh, schema=SERVICE_REFRESH_SCHEMA) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up AdGuard Home from a config entry.""" session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]) adguard = AdGuardHome( entry.data[CONF_HOST], port=entry.data[CONF_PORT], username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], tls=entry.data[CONF_SSL], verify_ssl=entry.data[CONF_VERIFY_SSL], session=session, ) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { DATA_ADGUARD_CLIENT: adguard } try: await adguard.version() except AdGuardHomeConnectionError as exception: raise ConfigEntryNotReady from exception hass.config_entries.async_setup_platforms(entry, PLATFORMS) async def add_url(call: ServiceCall) -> None: """Service call to add a new filter subscription to AdGuard Home.""" await adguard.filtering.add_url(allowlist=False, name=call.data[CONF_NAME], url=call.data[CONF_URL]) async def remove_url(call: ServiceCall) -> None: """Service call to remove a filter subscription from AdGuard Home.""" await adguard.filtering.remove_url(allowlist=False, url=call.data[CONF_URL]) async def enable_url(call: ServiceCall) -> None: """Service call to enable a filter subscription in AdGuard Home.""" await adguard.filtering.enable_url(allowlist=False, url=call.data[CONF_URL]) async def disable_url(call: ServiceCall) -> None: """Service call to disable a filter subscription in AdGuard Home.""" await adguard.filtering.disable_url(allowlist=False, url=call.data[CONF_URL]) async def refresh(call: ServiceCall) -> None: """Service call to refresh the filter subscriptions in AdGuard Home.""" await adguard.filtering.refresh(allowlist=False, force=call.data[CONF_FORCE]) hass.services.async_register(DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_REMOVE_URL, remove_url, schema=SERVICE_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_ENABLE_URL, enable_url, schema=SERVICE_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_DISABLE_URL, disable_url, schema=SERVICE_URL_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_REFRESH, refresh, schema=SERVICE_REFRESH_SCHEMA) return True
if tokens.get(ACCESS_TOKEN_EXPIRES) is not None and ( expires := dt_util.parse_datetime( tokens[ACCESS_TOKEN_EXPIRES])): tokens[ACCESS_TOKEN_EXPIRES] = _dt_aware_to_naive(expires) user_data = tokens.pop(USER_DATA, None) return (tokens, user_data) store = Store[dict[str, Any]](hass, STORAGE_VER, STORAGE_KEY) tokens, user_data = await load_auth_tokens(store) client_v2 = evohomeasync2.EvohomeClient( config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD], **tokens, session=async_get_clientsession(hass), ) try: await client_v2.login() except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err: _handle_exception(err) return False finally: config[DOMAIN][CONF_PASSWORD] = "REDACTED" loc_idx = config[DOMAIN][CONF_LOCATION_IDX] try: loc_config = client_v2.installation_info[loc_idx] except IndexError: _LOGGER.error(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tomorrow.io API from a config entry.""" hass.data.setdefault(DOMAIN, {}) # Let's precreate the device so that if this is a first time setup for a config # entry imported from a ClimaCell entry, we can apply customizations from the old # device. dev_reg = dr.async_get(hass) device = dev_reg.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, entry.data[CONF_API_KEY])}, name=INTEGRATION_NAME, manufacturer=INTEGRATION_NAME, sw_version="v4", entry_type=dr.DeviceEntryType.SERVICE, ) # If this is an import and we still have the old config entry ID in the entry data, # it means we are setting this entry up for the first time after a migration from # ClimaCell to Tomorrow.io. In order to preserve any customizations on the ClimaCell # entities, we need to remove each old entity, creating a new entity in its place # but attached to this entry. if entry.source == SOURCE_IMPORT and "old_config_entry_id" in entry.data: # Remove the old config entry ID from the entry data so we don't try this again # on the next setup data = entry.data.copy() old_config_entry_id = data.pop("old_config_entry_id") hass.config_entries.async_update_entry(entry, data=data) _LOGGER.debug( ("Setting up imported climacell entry %s for the first time as " "tomorrowio entry %s"), old_config_entry_id, entry.entry_id, ) ent_reg = er.async_get(hass) for entity_entry in er.async_entries_for_config_entry( ent_reg, old_config_entry_id): _LOGGER.debug("Removing %s", entity_entry.entity_id) ent_reg.async_remove(entity_entry.entity_id) # In case the API key has changed due to a V3 -> V4 change, we need to # generate the new entity's unique ID new_unique_id = ( f"{entry.data[CONF_API_KEY]}_" f"{'_'.join(entity_entry.unique_id.split('_')[1:])}") _LOGGER.debug("Re-creating %s for the new config entry", entity_entry.entity_id) # We will precreate the entity so that any customizations can be preserved new_entity_entry = ent_reg.async_get_or_create( entity_entry.domain, DOMAIN, new_unique_id, suggested_object_id=entity_entry.entity_id.split(".")[1], disabled_by=entity_entry.disabled_by, config_entry=entry, original_name=entity_entry.original_name, original_icon=entity_entry.original_icon, ) _LOGGER.debug("Re-created %s", new_entity_entry.entity_id) # If there are customizations on the old entity, apply them to the new one if entity_entry.name or entity_entry.icon: ent_reg.async_update_entity( new_entity_entry.entity_id, name=entity_entry.name, icon=entity_entry.icon, ) # We only have one device in the registry but we will do a loop just in case for old_device in dr.async_entries_for_config_entry( dev_reg, old_config_entry_id): if old_device.name_by_user: dev_reg.async_update_device( device.id, name_by_user=old_device.name_by_user) # Remove the old config entry and now the entry is fully migrated hass.async_create_task( hass.config_entries.async_remove(old_config_entry_id)) api = TomorrowioV4( entry.data[CONF_API_KEY], entry.data[CONF_LATITUDE], entry.data[CONF_LONGITUDE], session=async_get_clientsession(hass), ) coordinator = TomorrowioDataUpdateCoordinator( hass, entry, api, _set_update_interval(hass, entry), ) await coordinator.async_config_entry_first_refresh() hass.data[DOMAIN][entry.entry_id] = coordinator hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Weatherbit forecast as config entry.""" if not entry.options: hass.config_entries.async_update_entry( entry, options={ "fcs_update_interval": entry.data[CONF_FCS_UPDATE_INTERVAL], "cur_update_interval": entry.data[CONF_CUR_UPDATE_INTERVAL], "fcst_language": entry.data[CONF_FORECAST_LANGUAGE], "wind_unit": entry.data.get(CONF_WIND_UNITS, UNIT_WIND_MS), }, ) session = aiohttp_client.async_get_clientsession(hass) weatherbit = Weatherbit( entry.data[CONF_API_KEY], entry.data[CONF_LATITUDE], entry.data[CONF_LONGITUDE], entry.options.get(CONF_FORECAST_LANGUAGE, DEFAULT_FORECAST_LANGUAGE), "M", session, ) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = weatherbit _LOGGER.debug("Connected to Weatherbit") if entry.options.get(CONF_FCS_UPDATE_INTERVAL): fcst_scan_interval = timedelta( minutes=entry.options[CONF_FCS_UPDATE_INTERVAL]) else: fcst_scan_interval = SCAN_INTERVAL fcst_coordinator = DataUpdateCoordinator( hass, _LOGGER, name=DOMAIN, update_method=weatherbit.async_get_forecast_daily, update_interval=fcst_scan_interval, ) if entry.data.get(CONF_ADD_ALERTS): alert_coordinator = DataUpdateCoordinator( hass, _LOGGER, name=DOMAIN, update_method=weatherbit.async_get_weather_alerts, update_interval=fcst_scan_interval, ) else: alert_coordinator = None if entry.options.get(CONF_CUR_UPDATE_INTERVAL): current_scan_interval = timedelta( minutes=entry.options[CONF_CUR_UPDATE_INTERVAL]) else: current_scan_interval = SCAN_INTERVAL cur_coordinator = DataUpdateCoordinator( hass, _LOGGER, name=DOMAIN, update_method=weatherbit.async_get_current_data, update_interval=current_scan_interval, ) try: await weatherbit.async_get_city_name() except InvalidApiKey: _LOGGER.error( "Your API Key is invalid or does not support this operation") return False except (RequestError, ServerDisconnectedError) as err: _LOGGER.warning(str(err)) raise ConfigEntryNotReady await fcst_coordinator.async_refresh() await cur_coordinator.async_refresh() if entry.data.get(CONF_ADD_ALERTS): await alert_coordinator.async_refresh() hass.data[DOMAIN][entry.entry_id] = { "fcst_coordinator": fcst_coordinator, "cur_coordinator": cur_coordinator, "alert_coordinator": alert_coordinator, "weatherbit": weatherbit, "wind_unit_metric": entry.options.get(CONF_WIND_UNITS, UNIT_WIND_MS), } await _async_get_or_create_weatherbit_device_in_registry(hass, entry) for platform in WEATHERBIT_PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform)) if not entry.update_listeners: entry.add_update_listener(async_update_options) return True
async def validate_input(self, api_key: str, ferry_from: str, ferry_to: str) -> None: """Validate input from user input.""" web_session = async_get_clientsession(self.hass) ferry_api = TrafikverketFerry(web_session, api_key) await ferry_api.async_get_next_ferry_stop(ferry_from, ferry_to)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Control4 from a config entry.""" entry_data = hass.data[DOMAIN].setdefault(entry.entry_id, {}) account_session = aiohttp_client.async_get_clientsession(hass) config = entry.data account = C4Account(config[CONF_USERNAME], config[CONF_PASSWORD], account_session) try: await account.getAccountBearerToken() except client_exceptions.ClientError as exception: _LOGGER.error("Error connecting to Control4 account API: %s", exception) raise ConfigEntryNotReady from exception except BadCredentials as exception: _LOGGER.error( "Error authenticating with Control4 account API, incorrect username or password: %s", exception, ) return False entry_data[CONF_ACCOUNT] = account controller_unique_id = config[CONF_CONTROLLER_UNIQUE_ID] entry_data[CONF_CONTROLLER_UNIQUE_ID] = controller_unique_id director_token_dict = await account.getDirectorBearerToken(controller_unique_id) director_session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False) director = C4Director( config[CONF_HOST], director_token_dict[CONF_TOKEN], director_session ) entry_data[CONF_DIRECTOR] = director entry_data[CONF_DIRECTOR_TOKEN_EXPIRATION] = director_token_dict["token_expiration"] # Add Control4 controller to device registry controller_href = (await account.getAccountControllers())["href"] entry_data[CONF_DIRECTOR_SW_VERSION] = await account.getControllerOSVersion( controller_href ) _, model, mac_address = controller_unique_id.split("_", 3) entry_data[CONF_DIRECTOR_MODEL] = model.upper() device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, controller_unique_id)}, connections={(dr.CONNECTION_NETWORK_MAC, mac_address)}, manufacturer="Control4", name=controller_unique_id, model=entry_data[CONF_DIRECTOR_MODEL], sw_version=entry_data[CONF_DIRECTOR_SW_VERSION], ) # Store all items found on controller for platforms to use director_all_items = await director.getAllItemInfo() director_all_items = json.loads(director_all_items) entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items # Load options from config entry entry_data[CONF_SCAN_INTERVAL] = entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL ) entry_data[CONF_CONFIG_LISTENER] = entry.add_update_listener(update_listener) for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) return True
async def async_setup_entry(hass, entry): """Set up Plex from a config entry.""" server_config = entry.data[PLEX_SERVER_CONFIG] if MP_DOMAIN not in entry.options: options = dict(entry.options) options.setdefault( MP_DOMAIN, hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS) or MEDIA_PLAYER_SCHEMA({}), ) hass.config_entries.async_update_entry(entry, options=options) plex_server = PlexServer(hass, server_config, entry.options) try: await hass.async_add_executor_job(plex_server.connect) except requests.exceptions.ConnectionError as error: _LOGGER.error( "Plex server (%s) could not be reached: [%s]", server_config[CONF_URL], error, ) return False except ( plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized, plexapi.exceptions.NotFound, ) as error: _LOGGER.error( "Login to %s failed, verify token and SSL settings: [%s]", entry.data[CONF_SERVER], error, ) return False _LOGGER.debug("Connected to: %s (%s)", plex_server.friendly_name, plex_server.url_in_use) server_id = plex_server.machine_identifier hass.data[PLEX_DOMAIN][SERVERS][server_id] = plex_server for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform)) entry.add_update_listener(async_options_updated) unsub = async_dispatcher_connect( hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id), plex_server.update_platforms, ) hass.data[PLEX_DOMAIN][DISPATCHERS].setdefault(server_id, []) hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) def update_plex(): async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) session = async_get_clientsession(hass) verify_ssl = server_config.get(CONF_VERIFY_SSL) websocket = PlexWebsocket(plex_server.plex_server, update_plex, session=session, verify_ssl=verify_ssl) hass.data[PLEX_DOMAIN][WEBSOCKETS][server_id] = websocket async def async_start_websocket_session(_): await websocket.listen() def close_websocket_session(_): websocket.close() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start_websocket_session) unsub = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_websocket_session) hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up sma from a config entry.""" # Init the SMA interface protocol = "https" if entry.data[CONF_SSL] else "http" url = f"{protocol}://{entry.data[CONF_HOST]}" verify_ssl = entry.data[CONF_VERIFY_SSL] group = entry.data[CONF_GROUP] password = entry.data[CONF_PASSWORD] session = async_get_clientsession(hass, verify_ssl=verify_ssl) sma = pysma.SMA(session, url, password, group) try: # Get updated device info sma_device_info = await sma.device_info() # Get all device sensors sensor_def = await sma.get_sensors() except ( pysma.exceptions.SmaReadException, pysma.exceptions.SmaConnectionException, ) as exc: raise ConfigEntryNotReady from exc if TYPE_CHECKING: assert entry.unique_id # Create DeviceInfo object from sma_device_info device_info = DeviceInfo( configuration_url=url, identifiers={(DOMAIN, entry.unique_id)}, manufacturer=sma_device_info["manufacturer"], model=sma_device_info["type"], name=sma_device_info["name"], sw_version=sma_device_info["sw_version"], ) # Define the coordinator async def async_update_data(): """Update the used SMA sensors.""" try: await sma.read(sensor_def) except ( pysma.exceptions.SmaReadException, pysma.exceptions.SmaConnectionException, ) as exc: raise UpdateFailed(exc) from exc interval = timedelta( seconds=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)) coordinator = DataUpdateCoordinator( hass, _LOGGER, name="sma", update_method=async_update_data, update_interval=interval, ) try: await coordinator.async_config_entry_first_refresh() except ConfigEntryNotReady: await sma.close_session() raise # Ensure we logout on shutdown async def async_close_session(event): """Close the session.""" await sma.close_session() remove_stop_listener = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close_session) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { PYSMA_OBJECT: sma, PYSMA_COORDINATOR: coordinator, PYSMA_SENSORS: sensor_def, PYSMA_REMOVE_LISTENER: remove_stop_listener, PYSMA_DEVICE_INFO: device_info, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a Synology IP Camera.""" verify_ssl = config.get(CONF_VERIFY_SSL) websession_init = async_get_clientsession(hass, verify_ssl) # Determine API to use for authentication syno_api_url = SYNO_API_URL.format(config.get(CONF_URL), WEBAPI_PATH, QUERY_CGI) query_payload = { 'api': QUERY_API, 'method': 'Query', 'version': '1', 'query': 'SYNO.' } query_req = None try: with async_timeout.timeout(TIMEOUT, loop=hass.loop): query_req = yield from websession_init.get(syno_api_url, params=query_payload) query_resp = yield from query_req.json() auth_path = query_resp['data'][AUTH_API]['path'] camera_api = query_resp['data'][CAMERA_API]['path'] camera_path = query_resp['data'][CAMERA_API]['path'] streaming_path = query_resp['data'][STREAMING_API]['path'] except (asyncio.TimeoutError, aiohttp.errors.ClientError): _LOGGER.exception("Error on %s", syno_api_url) return False finally: if query_req is not None: yield from query_req.release() # Authticate to NAS to get a session id syno_auth_url = SYNO_API_URL.format(config.get(CONF_URL), WEBAPI_PATH, auth_path) session_id = yield from get_session_id(hass, websession_init, config.get(CONF_USERNAME), config.get(CONF_PASSWORD), syno_auth_url) # init websession websession = async_create_clientsession(hass, verify_ssl, cookies={'id': session_id}) # Use SessionID to get cameras in system syno_camera_url = SYNO_API_URL.format(config.get(CONF_URL), WEBAPI_PATH, camera_api) camera_payload = {'api': CAMERA_API, 'method': 'List', 'version': '1'} try: with async_timeout.timeout(TIMEOUT, loop=hass.loop): camera_req = yield from websession.get(syno_camera_url, params=camera_payload) except (asyncio.TimeoutError, aiohttp.errors.ClientError): _LOGGER.exception("Error on %s", syno_camera_url) return False camera_resp = yield from camera_req.json() cameras = camera_resp['data']['cameras'] yield from camera_req.release() # add cameras devices = [] for camera in cameras: if not config.get(CONF_WHITELIST): camera_id = camera['id'] snapshot_path = camera['snapshot_path'] device = SynologyCamera(hass, websession, config, camera_id, camera['name'], snapshot_path, streaming_path, camera_path, auth_path) devices.append(device) yield from async_add_devices(devices)
async def _async_send_message(self, message, targets, data): conversations = [] for target in targets: conversation = None if CONF_CONVERSATION_ID in target: conversation = self._conversation_list.get(target[CONF_CONVERSATION_ID]) elif CONF_CONVERSATION_NAME in target: conversation = self._resolve_conversation_name( target[CONF_CONVERSATION_NAME] ) if conversation is not None: conversations.append(conversation) if not conversations: return False from hangups import ChatMessageSegment, hangouts_pb2 messages = [] for segment in message: if messages: messages.append( ChatMessageSegment( "", segment_type=hangouts_pb2.SEGMENT_TYPE_LINE_BREAK ) ) if "parse_str" in segment and segment["parse_str"]: messages.extend(ChatMessageSegment.from_str(segment["text"])) else: if "parse_str" in segment: del segment["parse_str"] messages.append(ChatMessageSegment(**segment)) image_file = None if data: if data.get("image_url"): uri = data.get("image_url") try: websession = async_get_clientsession(self.hass) async with websession.get(uri, timeout=5) as response: if response.status != 200: _LOGGER.error( "Fetch image failed, %s, %s", response.status, response ) image_file = None else: image_data = await response.read() image_file = io.BytesIO(image_data) image_file.name = "image.png" except (asyncio.TimeoutError, aiohttp.ClientError) as error: _LOGGER.error("Failed to fetch image, %s", type(error)) image_file = None elif data.get("image_file"): uri = data.get("image_file") if self.hass.config.is_allowed_path(uri): try: image_file = open(uri, "rb") except IOError as error: _LOGGER.error( "Image file I/O error(%s): %s", error.errno, error.strerror ) else: _LOGGER.error('Path "%s" not allowed', uri) if not messages: return False for conv in conversations: await conv.send_message(messages, image_file)
async def async_step_user(self, user_input: dict[str, Any] = None) -> FlowResult: """Handle the initial step.""" errors = {} if user_input is not None: # Grab the API key and add it to the rest of the config before continuing if self._import_config: self._import_config[CONF_API_KEY] = user_input[CONF_API_KEY] self._import_config[CONF_LOCATION] = { CONF_LATITUDE: self._import_config.pop( CONF_LATITUDE, self.hass.config.latitude ), CONF_LONGITUDE: self._import_config.pop( CONF_LONGITUDE, self.hass.config.longitude ), } user_input = self._import_config.copy() await self.async_set_unique_id( unique_id=_get_unique_id(self.hass, user_input) ) self._abort_if_unique_id_configured() location = user_input[CONF_LOCATION] latitude = location[CONF_LATITUDE] longitude = location[CONF_LONGITUDE] if CONF_NAME not in user_input: user_input[CONF_NAME] = DEFAULT_NAME # Append zone name if it exists and we are using the default name if zone_state := async_active_zone(self.hass, latitude, longitude): zone_name = zone_state.attributes[CONF_FRIENDLY_NAME] user_input[CONF_NAME] += f" - {zone_name}" try: await TomorrowioV4( user_input[CONF_API_KEY], str(latitude), str(longitude), session=async_get_clientsession(self.hass), ).realtime([TMRW_ATTR_TEMPERATURE]) except CantConnectException: errors["base"] = "cannot_connect" except InvalidAPIKeyException: errors[CONF_API_KEY] = "invalid_api_key" except RateLimitedException: errors[CONF_API_KEY] = "rate_limited" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" if not errors: options: Mapping[str, Any] = {CONF_TIMESTEP: DEFAULT_TIMESTEP} # Store the old config entry ID and retrieve options to recreate the entry if self.source == config_entries.SOURCE_IMPORT: old_config_entry_id = self.context["old_config_entry_id"] old_config_entry = self.hass.config_entries.async_get_entry( old_config_entry_id ) assert old_config_entry options = dict(old_config_entry.options) user_input["old_config_entry_id"] = old_config_entry_id return self.async_create_entry( title=user_input[CONF_NAME], data=user_input, options=options, )
def async_turn_off(self): """Turn the device off.""" websession = async_get_clientsession(self.hass) with async_timeout.timeout(self._timeout, loop=self.hass.loop): req = yield from websession.post(self._res_off) text = yield from req.text()
def __init__(self, hass, name, host, port, tcp_port, encryption=False, username=None, password=None, turn_on_action=None, turn_off_action=None, timeout=DEFAULT_TIMEOUT, websocket=True, unique_id=None): """Initialize the Kodi device.""" import jsonrpc_async import jsonrpc_websocket self.hass = hass self._name = name self._unique_id = unique_id self._media_position_updated_at = None self._media_position = None kwargs = { 'timeout': timeout, 'session': async_get_clientsession(hass), } if username is not None: kwargs['auth'] = aiohttp.BasicAuth(username, password) image_auth_string = "{}:{}@".format(username, password) else: image_auth_string = "" http_protocol = 'https' if encryption else 'http' ws_protocol = 'wss' if encryption else 'ws' self._http_url = '{}://{}:{}/jsonrpc'.format(http_protocol, host, port) self._image_url = '{}://{}{}:{}/image'.format( http_protocol, image_auth_string, host, port) self._ws_url = '{}://{}:{}/jsonrpc'.format(ws_protocol, host, tcp_port) self._http_server = jsonrpc_async.Server(self._http_url, **kwargs) if websocket: # Setup websocket connection self._ws_server = jsonrpc_websocket.Server(self._ws_url, **kwargs) # Register notification listeners self._ws_server.Player.OnPause = self.async_on_speed_event self._ws_server.Player.OnPlay = self.async_on_speed_event self._ws_server.Player.OnAVStart = self.async_on_speed_event self._ws_server.Player.OnAVChange = self.async_on_speed_event self._ws_server.Player.OnResume = self.async_on_speed_event self._ws_server.Player.OnSpeedChanged = self.async_on_speed_event self._ws_server.Player.OnSeek = self.async_on_speed_event self._ws_server.Player.OnStop = self.async_on_stop self._ws_server.Application.OnVolumeChanged = \ self.async_on_volume_changed self._ws_server.System.OnQuit = self.async_on_quit self._ws_server.System.OnRestart = self.async_on_quit self._ws_server.System.OnSleep = self.async_on_quit def on_hass_stop(event): """Close websocket connection when hass stops.""" self.hass.async_create_task(self._ws_server.close()) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, on_hass_stop) else: self._ws_server = None # Script creation for the turn on/off config options if turn_on_action is not None: turn_on_action = script.Script( self.hass, turn_on_action, "{} turn ON script".format(self.name), self.async_update_ha_state(True)) if turn_off_action is not None: turn_off_action = script.Script( self.hass, _check_deprecated_turn_off(hass, turn_off_action), "{} turn OFF script".format(self.name)) self._turn_on_action = turn_on_action self._turn_off_action = turn_off_action self._enable_websocket = websocket self._players = list() self._properties = {} self._item = {} self._app_properties = {}
async def update(self): """Update function for updating api information.""" try: url = BSE_URL.format(self.area) hdr = { "User-Agent": ("mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/78.0.3904.70 safari/537.36" ) } session = async_get_clientsession(self.hass) # response = requests.get(url, headers=hdr, timeout=10) response = await session.get(url, headers=hdr, timeout=30) response.raise_for_status() soup = BeautifulSoup(await response.text(), "html.parser") NowTemp = "" CheckDust = [] # 지역 LocationInfo = soup.find("span", {"class": "btn_select"}).text # 현재 온도 NowTemp = soup.find("span", {"class": "todaytemp"}).text # 날씨 캐스트 WeatherCast = soup.find("p", {"class": "cast_txt"}).text # 오늘 오전온도, 오후온도, 체감온도 TodayMinTemp = (soup.find("span", { "class": "min" }).select("span.num")[0].text) TodayMaxTemp = (soup.find("span", { "class": "max" }).select("span.num")[0].text) TodayFeelTemp = (soup.find("span", { "class": "sensible" }).select("em > span.num")[0].text) # 시간당 강수량 TodayRainfall = soup.find("span", {"class": "rainfall"}) Rainfall = "-" if TodayRainfall is not None: TodayRainfallSelect = TodayRainfall.select("em > span.num") for rain in TodayRainfallSelect: Rainfall = rain.text # 자외선 지수 TodayUVSelect = soup.find("span", { "class": "indicator" }).select("span > span.num") TodayUV = "-" for uv in TodayUVSelect: TodayUV = uv.text # 미세먼지, 초미세먼지, 오존 지수 CheckDust1 = soup.find("div", {"class": "sub_info"}) CheckDust2 = CheckDust1.find("div", {"class": "detail_box"}) for i in CheckDust2.select("dd"): CheckDust.append(i.text) FineDust = CheckDust[0].split("㎍/㎥")[0] FineDustGrade = CheckDust[0].split("㎍/㎥")[1] UltraFineDust = CheckDust[1].split("㎍/㎥")[0] UltraFineDustGrade = CheckDust[1].split("㎍/㎥")[1] # 오존 Ozon = CheckDust[2].split("ppm")[0] OzonGrade = CheckDust[2].split("ppm")[1] # condition today_area = soup.find("div", {"class": "today_area _mainTabContent"}) condition_main = today_area.select( "div.main_info > span.ico_state")[0]["class"][1] condition = CONDITIONS[condition_main][0] # 현재 습도 humi_tab = soup.find( "div", {"class": "info_list humidity _tabContent _center"}) Humidity = humi_tab.select( "ul > li.on.now > dl > dd.weather_item._dotWrapper > span" )[0].text # 현재풍속 wind_tab = soup.find( "div", {"class": "info_list wind _tabContent _center"}) WindSpeed = wind_tab.select( "ul > li.on.now > dl > dd.weather_item._dotWrapper > span" )[0].text WindState = wind_tab.select( "ul > li.on.now > dl > dd.item_condition > span.wt_status" )[0].text # 내일 오전, 오후 온도 및 상태 체크 tomorrowArea = soup.find("div", {"class": "tomorrow_area"}) tomorrowCheck = tomorrowArea.find_all( "div", {"class": "main_info morning_box"}) # 내일 오전온도 tomorrowMTemp = tomorrowCheck[0].find("span", { "class": "todaytemp" }).text # 내일 오전상태 tomorrowMState1 = tomorrowCheck[0].find("div", {"class": "info_data"}) tomorrowMState2 = tomorrowMState1.find("ul", {"class": "info_list"}) tomorrowMState = tomorrowMState2.find("p", { "class": "cast_txt" }).text # 내일 오후온도 tomorrowATemp1 = tomorrowCheck[1].find( "p", {"class": "info_temperature"}) tomorrowATemp = tomorrowATemp1.find("span", { "class": "todaytemp" }).text # 내일 오후상태 tomorrowAState1 = tomorrowCheck[1].find("div", {"class": "info_data"}) tomorrowAState2 = tomorrowAState1.find("ul", {"class": "info_list"}) tomorrowAState = tomorrowAState2.find("p", { "class": "cast_txt" }).text # 주간날씨 weekly = soup.find("div", {"class": "table_info weekly _weeklyWeather"}) date_info = weekly.find_all("li", {"class": "date_info today"}) # 비시작시간 rain_tab = soup.find( "div", {"class": "info_list weather_condition _tabContent"}) rainyStart = "-" rainyStartTmr = "-" if rain_tab is not None: # 오늘 rainyStart = "비안옴" for rain_li in rain_tab.select("ul > li"): if (rain_li.select("dl > dd.item_time")[0].find( "span", {"class": "tomorrow"}) is not None): break if rain_li.select( "dl > dd.item_condition > span")[0].text == "비": rainyStart = ( rain_li.select("dl > dd.item_time")[0].find( "span", { "class": None }).text) break # 오늘 ~ 내일 rainyStartTmr = "비안옴" for rain_li in rain_tab.select("ul > li"): if rain_li.select( "dl > dd.item_condition > span")[0].text == "비": rainyStartTmr = ( rain_li.select("dl > dd.item_time")[0].find( "span", { "class": None }).text) break forecast = [] reftime = datetime.now() for di in date_info: data = {} # day day = di.select("span.day_info") dayInfo = "" for t in day: dayInfo = t.text.strip() # data['datetime'] = dayInfo data["datetime"] = reftime # temp temp = di.select("dl > dd > span") temptext = "" for t in temp: temptext += t.text arrTemp = temptext.split("/") data["templow"] = float(arrTemp[0]) data["temperature"] = float(arrTemp[1]) # condition condition_am = di.select( "span.point_time.morning > span.ico_state2")[0] condition_pm = di.select( "span.point_time.afternoon > span.ico_state2")[0] data["condition"] = CONDITIONS[condition_pm["class"][1]][0] data["condition_am"] = CONDITIONS[condition_am["class"][1]][2] data["condition_pm"] = CONDITIONS[condition_pm["class"][1]][2] # rain_rate rain_m = di.select( "span.point_time.morning > span.rain_rate > span.num") for t in rain_m: data["rain_rate_am"] = int(t.text) rain_a = di.select( "span.point_time.afternoon > span.rain_rate > span.num") for t in rain_a: data["rain_rate_pm"] = int(t.text) if dayInfo.split(" ")[1] != "오늘": forecast.append(data) reftime = reftime + timedelta(days=1) self.forecast = forecast self.result = { LOCATION[0]: LocationInfo, NOW_CAST[0]: WeatherCast, NOW_TEMP[0]: NowTemp, NOW_HUMI[0]: Humidity, CONDITION[0]: condition, WIND_SPEED[0]: WindSpeed, WIND_DIR[0]: WindState, MIN_TEMP[0]: TodayMinTemp, MAX_TEMP[0]: TodayMaxTemp, FEEL_TEMP[0]: TodayFeelTemp, RAINFALL[0]: Rainfall, UV[0]: TodayUV, NDUST[0]: FineDust, NDUST_GRADE[0]: FineDustGrade, UDUST[0]: UltraFineDust, UDUST_GRADE[0]: UltraFineDustGrade, OZON[0]: Ozon, OZON_GRADE[0]: OzonGrade, TOMORROW_AM[0]: tomorrowMState, TOMORROW_MIN[0]: tomorrowMTemp, TOMORROW_PM[0]: tomorrowAState, TOMORROW_MAX[0]: tomorrowATemp, RAINY_START[0]: rainyStart, RAINY_START_TMR[0]: rainyStartTmr, } _LOGGER.info( f"[{BRAND}] Update weather information -> {self.result}") for id in WEATHER_INFO.keys(): try: self.device_update(id) except Exception as ex: _LOGGER.info(f"[{BRAND}] Update weather fail -> {ex}") except Exception as ex: _LOGGER.error("Failed to update NWeather API status Error: %s", ex) raise
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the IP Webcam component.""" webcams = hass.data[DATA_IP_WEBCAM] = {} websession = async_get_clientsession(hass) async def async_setup_ipcamera(cam_config): """Set up an IP camera.""" host = cam_config[CONF_HOST] username: str | None = cam_config.get(CONF_USERNAME) password: str | None = cam_config.get(CONF_PASSWORD) name: str = cam_config[CONF_NAME] interval = cam_config[CONF_SCAN_INTERVAL] switches = cam_config.get(CONF_SWITCHES) sensors = cam_config.get(CONF_SENSORS) motion = cam_config.get(CONF_MOTION_SENSOR) # Init ip webcam cam = PyDroidIPCam( websession, host, cam_config[CONF_PORT], username=username, password=password, timeout=cam_config[CONF_TIMEOUT], ssl=False, ) if switches is None: switches = [ setting for setting in cam.enabled_settings if setting in SWITCHES ] if sensors is None: sensors = [ sensor for sensor in cam.enabled_sensors if sensor in SENSORS ] sensors.extend(["audio_connections", "video_connections"]) if motion is None: motion = "motion_active" in cam.enabled_sensors async def async_update_data(now): """Update data from IP camera in SCAN_INTERVAL.""" await cam.update() async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host) async_track_point_in_utc_time(hass, async_update_data, utcnow() + interval) await async_update_data(None) # Load platforms webcams[host] = cam mjpeg_camera = { CONF_MJPEG_URL: cam.mjpeg_url, CONF_STILL_IMAGE_URL: cam.image_url, } if username and password: mjpeg_camera.update({ CONF_USERNAME: username, CONF_PASSWORD: password }) # Remove incorrect config entry setup via mjpeg platform discovery. mjpeg_config_entry = next( (config_entry for config_entry in hass.config_entries.async_entries("mjpeg") if all( config_entry.options.get(key) == val for key, val in mjpeg_camera.items())), None, ) if mjpeg_config_entry: await hass.config_entries.async_remove(mjpeg_config_entry.entry_id) mjpeg_camera[CONF_NAME] = name hass.async_create_task( discovery.async_load_platform(hass, Platform.CAMERA, DOMAIN, mjpeg_camera, config)) if sensors: hass.async_create_task( discovery.async_load_platform( hass, Platform.SENSOR, DOMAIN, { CONF_NAME: name, CONF_HOST: host, CONF_SENSORS: sensors }, config, )) if switches: hass.async_create_task( discovery.async_load_platform( hass, Platform.SWITCH, DOMAIN, { CONF_NAME: name, CONF_HOST: host, CONF_SWITCHES: switches }, config, )) if motion: hass.async_create_task( discovery.async_load_platform( hass, Platform.BINARY_SENSOR, DOMAIN, { CONF_HOST: host, CONF_NAME: name }, config, )) tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]] if tasks: await asyncio.wait(tasks) return True
async def create_vehicle_proxy( hass: HomeAssistant, vehicle_type: str ) -> RenaultVehicleProxy: """Create a vehicle proxy for testing.""" mock_vehicle = MOCK_VEHICLES[vehicle_type] mock_get_cockpit = schemas.KamereonVehicleDataResponseSchema.loads( load_fixture(mock_vehicle["endpoints"]["cockpit"]) if "cockpit" in mock_vehicle["endpoints"] else "{}" ).get_attributes(schemas.KamereonVehicleCockpitDataSchema) mock_get_hvac_status = schemas.KamereonVehicleDataResponseSchema.loads( load_fixture(mock_vehicle["endpoints"]["hvac_status"]) if "hvac_status" in mock_vehicle["endpoints"] else "{}" ).get_attributes(schemas.KamereonVehicleHvacStatusDataSchema) mock_get_battery_status = schemas.KamereonVehicleDataResponseSchema.loads( load_fixture(mock_vehicle["endpoints"]["battery_status"]) if "battery_status" in mock_vehicle["endpoints"] else "{}" ).get_attributes(schemas.KamereonVehicleBatteryStatusDataSchema) mock_get_charge_mode = schemas.KamereonVehicleDataResponseSchema.loads( load_fixture(mock_vehicle["endpoints"]["charge_mode"]) if "charge_mode" in mock_vehicle["endpoints"] else "{}" ).get_attributes(schemas.KamereonVehicleChargeModeDataSchema) mock_get_location = schemas.KamereonVehicleDataResponseSchema.loads( load_fixture(mock_vehicle["endpoints"]["location"]) if "location" in mock_vehicle["endpoints"] else "{}" ).get_attributes(schemas.KamereonVehicleLocationDataSchema) vehicles_response: models.KamereonVehiclesResponse = ( schemas.KamereonVehiclesResponseSchema.loads( load_fixture(f"vehicle_{vehicle_type}.json") ) ) vehicle_details = vehicles_response.vehicleLinks[0].vehicleDetails vehicle = RenaultVehicle( vehicles_response.accountId, vehicle_details.vin, websession=aiohttp_client.async_get_clientsession(hass), ) vehicle_proxy = RenaultVehicleProxy( hass, vehicle, vehicle_details, timedelta(seconds=300), False ) with patch( "custom_components.renault.renault_vehicle.RenaultVehicleProxy.endpoint_available", side_effect=mock_vehicle["endpoints_available"], ), patch( "custom_components.renault.renault_vehicle.RenaultVehicleProxy.get_cockpit", return_value=mock_get_cockpit, ), patch( "custom_components.renault.renault_vehicle.RenaultVehicleProxy.get_hvac_status", return_value=mock_get_hvac_status, ), patch( "custom_components.renault.renault_vehicle.RenaultVehicleProxy.get_battery_status", return_value=mock_get_battery_status, ), patch( "custom_components.renault.renault_vehicle.RenaultVehicleProxy.get_charge_mode", return_value=mock_get_charge_mode, ), patch( "custom_components.renault.renault_vehicle.RenaultVehicleProxy.get_location", return_value=mock_get_location, ): await vehicle_proxy.async_initialise() return vehicle_proxy
async def async_setup_entry(hass, config_entry): """Set up SimpliSafe as config entry.""" _verify_domain_control = verify_domain_control(hass, DOMAIN) websession = aiohttp_client.async_get_clientsession(hass) try: api = await API.login_via_token(config_entry.data[CONF_TOKEN], websession) except InvalidCredentialsError: _LOGGER.error("Invalid credentials provided") return False except SimplipyError as err: _LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady _async_save_refresh_token(hass, config_entry, api.refresh_token) simplisafe = SimpliSafe(hass, api, config_entry) await simplisafe.async_init() hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = simplisafe for component in ("alarm_control_panel", "lock"): hass.async_create_task( hass.config_entries.async_forward_entry_setup( config_entry, component)) @callback def verify_system_exists(coro): """Log an error if a service call uses an invalid system ID.""" async def decorator(call): """Decorate.""" system_id = int(call.data[ATTR_SYSTEM_ID]) if system_id not in simplisafe.systems: _LOGGER.error("Unknown system ID in service call: %s", system_id) return await coro(call) return decorator @callback def v3_only(coro): """Log an error if the decorated coroutine is called with a v2 system.""" async def decorator(call): """Decorate.""" system = simplisafe.systems[int(call.data[ATTR_SYSTEM_ID])] if system.version != 3: _LOGGER.error("Service only available on V3 systems") return await coro(call) return decorator @verify_system_exists @_verify_domain_control async def remove_pin(call): """Remove a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.remove_pin(call.data[ATTR_PIN_LABEL_OR_VALUE]) except SimplipyError as err: _LOGGER.error("Error during service call: %s", err) return @verify_system_exists @_verify_domain_control async def set_pin(call): """Set a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.set_pin(call.data[ATTR_PIN_LABEL], call.data[ATTR_PIN_VALUE]) except SimplipyError as err: _LOGGER.error("Error during service call: %s", err) return @verify_system_exists @v3_only @_verify_domain_control async def set_system_properties(call): """Set one or more system parameters.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.set_properties({ prop: value for prop, value in call.data.items() if prop != ATTR_SYSTEM_ID }) except SimplipyError as err: _LOGGER.error("Error during service call: %s", err) return for service, method, schema in [ ("remove_pin", remove_pin, SERVICE_REMOVE_PIN_SCHEMA), ("set_pin", set_pin, SERVICE_SET_PIN_SCHEMA), ( "set_system_properties", set_system_properties, SERVICE_SET_SYSTEM_PROPERTIES_SCHEMA, ), ]: async_register_admin_service(hass, DOMAIN, service, method, schema=schema) return True
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the SyncThru component.""" from pysyncthru import SyncThru if discovery_info is not None: _LOGGER.info("Discovered a new Samsung Printer at %s", discovery_info.get(CONF_HOST)) host = discovery_info.get(CONF_HOST) name = discovery_info.get(CONF_NAME, DEFAULT_NAME) # Main device, always added else: host = config.get(CONF_RESOURCE) name = config.get(CONF_NAME) # always pass through all of the obtained information monitored = DEFAULT_MONITORED_CONDITIONS session = aiohttp_client.async_get_clientsession(hass) printer = SyncThru(host, session) # Test if the discovered device actually is a syncthru printer # and fetch the available toner/drum/etc try: # No error is thrown when the device is off # (only after user added it manually) # therefore additional catches are inside the Sensor below await printer.update() supp_toner = printer.toner_status(filter_supported=True) supp_drum = printer.drum_status(filter_supported=True) supp_tray = printer.input_tray_status(filter_supported=True) supp_output_tray = printer.output_tray_status() except ValueError: # if an exception is thrown, printer does not support syncthru # and should not be set up # If the printer was discovered automatically, no warning or error # should be issued and printer should not be set up if discovery_info is not None: _LOGGER.info("Samsung printer at %s does not support SyncThru", host) return # Otherwise, emulate printer that supports everything supp_toner = TONER_COLORS supp_drum = DRUM_COLORS supp_tray = TRAYS supp_output_tray = OUTPUT_TRAYS devices = [SyncThruMainSensor(printer, name)] for key in supp_toner: if "toner_{}".format(key) in monitored: devices.append(SyncThruTonerSensor(printer, name, key)) for key in supp_drum: if "drum_{}".format(key) in monitored: devices.append(SyncThruDrumSensor(printer, name, key)) for key in supp_tray: if "tray_{}".format(key) in monitored: devices.append(SyncThruInputTraySensor(printer, name, key)) for key in supp_output_tray: if "output_tray_{}".format(key) in monitored: devices.append(SyncThruOutputTraySensor(printer, name, key)) async_add_entities(devices, True)
async def get_controller(hass, host, username, password, port, site, verify_ssl, async_callback=None): """Create a controller object and verify authentication.""" sslcontext = None if verify_ssl: session = aiohttp_client.async_get_clientsession(hass) if isinstance(verify_ssl, str): sslcontext = ssl.create_default_context(cafile=verify_ssl) else: session = aiohttp_client.async_create_clientsession( hass, verify_ssl=verify_ssl, cookie_jar=CookieJar(unsafe=True)) controller = aiounifi.Controller( host, username=username, password=password, port=port, site=site, websession=session, sslcontext=sslcontext, callback=async_callback, ) try: async with async_timeout.timeout(10): await controller.check_unifi_os() await controller.login() return controller except aiounifi.Unauthorized as err: LOGGER.warning( "Connected to UniFi Network at %s but not registered: %s", host, err, ) raise AuthenticationRequired from err except ( asyncio.TimeoutError, aiounifi.BadGateway, aiounifi.ServiceUnavailable, aiounifi.RequestError, aiounifi.ResponseError, ) as err: LOGGER.error("Error connecting to the UniFi Network at %s: %s", host, err) raise CannotConnect from err except aiounifi.LoginRequired as err: LOGGER.warning( "Connected to UniFi Network at %s but login required: %s", host, err, ) raise AuthenticationRequired from err except aiounifi.AiounifiException as err: LOGGER.exception( "Unknown UniFi Network communication error occurred: %s", err) raise AuthenticationRequired from err