async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Set up SmartThinQ integration from a config entry.""" if not is_valid_ha_version(): msg = "This integration require at least HomeAssistant version " \ f" {__min_ha_version__}, you are running version {__version__}." \ " Please upgrade HomeAssistant to continue use this integration." _notify_error(hass, "inv_ha_version", "SmartThinQ Sensors", msg) _LOGGER.warning(msg) return False refresh_token = config_entry.data.get(CONF_TOKEN) region = config_entry.data.get(CONF_REGION) language = config_entry.data.get(CONF_LANGUAGE) use_api_v2 = config_entry.data.get(CONF_USE_API_V2, False) oauth_url = config_entry.data.get(CONF_OAUTH_URL) # oauth_user_num = config_entry.data.get(CONF_OAUTH_USER_NUM) use_tls_v1 = config_entry.data.get(CONF_USE_TLS_V1, False) exclude_dh = config_entry.data.get(CONF_EXCLUDE_DH, False) _LOGGER.info(STARTUP) _LOGGER.info( "Initializing ThinQ platform with region: %s - language: %s", region, language, ) # if network is not connected we can have some error # raising ConfigEntryNotReady platform setup will be retried lge_auth = LGEAuthentication(region, language, use_api_v2) lge_auth.init_http_adapter(use_tls_v1, exclude_dh) try: client = await hass.async_add_executor_job( lge_auth.create_client_from_token, refresh_token, oauth_url) except InvalidCredentialError: msg = "Invalid ThinQ credential error, integration setup aborted." \ " Please use the LG App on your mobile device to ensure your" \ " credentials are correct, then restart HomeAssistant." \ " If your credential changed, you must reconfigure integration" _notify_error(hass, "inv_credential", "SmartThinQ Sensors", msg) _LOGGER.error(msg) return False except Exception as exc: _LOGGER.warning("Connection not available. ThinQ platform not ready", exc_info=True) raise ConfigEntryNotReady("ThinQ platform not ready") from exc if not client.hasdevices: _LOGGER.error("No ThinQ devices found. Component setup aborted") return False _LOGGER.info("ThinQ client connected") try: lge_devices = await lge_devices_setup(hass, client) except Exception as exc: _LOGGER.warning("Connection not available. ThinQ platform not ready", exc_info=True) raise ConfigEntryNotReady("ThinQ platform not ready") from exc if not use_api_v2: _LOGGER.warning( "Integration configuration is using ThinQ APIv1 that is obsolete" " and not able to manage all ThinQ devices." " Please remove and re-add integration from HA user interface to" " enable the use of ThinQ APIv2") # remove device not available anymore await cleanup_orphan_lge_devices(hass, config_entry.entry_id, client) hass.data[DOMAIN] = {CLIENT: client, LGE_DEVICES: lge_devices} hass.config_entries.async_setup_platforms(config_entry, SMARTTHINQ_PLATFORMS) return True
async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> None: """Ensure that Z-Wave JS add-on is installed and running.""" addon_manager: AddonManager = get_addon_manager(hass) if addon_manager.task_in_progress(): raise ConfigEntryNotReady try: addon_info = await addon_manager.async_get_addon_info() except AddonError as err: raise ConfigEntryNotReady(err) from err usb_path: str = entry.data[CONF_USB_PATH] # s0_legacy_key was saved as network_key before s2 was added. s0_legacy_key: str = entry.data.get(CONF_S0_LEGACY_KEY, "") if not s0_legacy_key: s0_legacy_key = entry.data.get(CONF_NETWORK_KEY, "") s2_access_control_key: str = entry.data.get(CONF_S2_ACCESS_CONTROL_KEY, "") s2_authenticated_key: str = entry.data.get(CONF_S2_AUTHENTICATED_KEY, "") s2_unauthenticated_key: str = entry.data.get(CONF_S2_UNAUTHENTICATED_KEY, "") addon_state = addon_info.state if addon_state == AddonState.NOT_INSTALLED: addon_manager.async_schedule_install_setup_addon( usb_path, s0_legacy_key, s2_access_control_key, s2_authenticated_key, s2_unauthenticated_key, catch_error=True, ) raise ConfigEntryNotReady if addon_state == AddonState.NOT_RUNNING: addon_manager.async_schedule_setup_addon( usb_path, s0_legacy_key, s2_access_control_key, s2_authenticated_key, s2_unauthenticated_key, catch_error=True, ) raise ConfigEntryNotReady addon_options = addon_info.options addon_device = addon_options[CONF_ADDON_DEVICE] # s0_legacy_key was saved as network_key before s2 was added. addon_s0_legacy_key = addon_options.get(CONF_ADDON_S0_LEGACY_KEY, "") if not addon_s0_legacy_key: addon_s0_legacy_key = addon_options.get(CONF_ADDON_NETWORK_KEY, "") addon_s2_access_control_key = addon_options.get( CONF_ADDON_S2_ACCESS_CONTROL_KEY, "" ) addon_s2_authenticated_key = addon_options.get(CONF_ADDON_S2_AUTHENTICATED_KEY, "") addon_s2_unauthenticated_key = addon_options.get( CONF_ADDON_S2_UNAUTHENTICATED_KEY, "" ) updates = {} if usb_path != addon_device: updates[CONF_USB_PATH] = addon_device if s0_legacy_key != addon_s0_legacy_key: updates[CONF_S0_LEGACY_KEY] = addon_s0_legacy_key if s2_access_control_key != addon_s2_access_control_key: updates[CONF_S2_ACCESS_CONTROL_KEY] = addon_s2_access_control_key if s2_authenticated_key != addon_s2_authenticated_key: updates[CONF_S2_AUTHENTICATED_KEY] = addon_s2_authenticated_key if s2_unauthenticated_key != addon_s2_unauthenticated_key: updates[CONF_S2_UNAUTHENTICATED_KEY] = addon_s2_unauthenticated_key if updates: hass.config_entries.async_update_entry(entry, data={**entry.data, **updates})
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Synology DSM sensors.""" # Migrate device indentifiers dev_reg = await get_dev_reg(hass) devices: list[ DeviceEntry] = device_registry.async_entries_for_config_entry( dev_reg, entry.entry_id) for device in devices: old_identifier = list(next(iter(device.identifiers))) if len(old_identifier) > 2: new_identifier = {(old_identifier.pop(0), "_".join([str(x) for x in old_identifier]))} _LOGGER.debug("migrate identifier '%s' to '%s'", device.identifiers, new_identifier) dev_reg.async_update_device(device.id, new_identifiers=new_identifier) # Migrate existing entry configuration if entry.data.get(CONF_VERIFY_SSL) is None: hass.config_entries.async_update_entry( entry, data={ **entry.data, CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL }) # Continue setup api = SynoApi(hass, entry) try: await api.async_setup() except ( SynologyDSMLogin2SARequiredException, SynologyDSMLoginDisabledAccountException, SynologyDSMLoginInvalidException, SynologyDSMLoginPermissionDeniedException, ) as err: if err.args[0] and isinstance(err.args[0], dict): details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN) else: details = EXCEPTION_UNKNOWN raise ConfigEntryAuthFailed(f"reason: {details}") from err except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: if err.args[0] and isinstance(err.args[0], dict): details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN) else: details = EXCEPTION_UNKNOWN raise ConfigEntryNotReady(details) from err hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = { UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), SYNO_API: api, SYSTEM_LOADED: True, } # Services await async_setup_services(hass) # For SSDP compat if not entry.data.get(CONF_MAC): network = await hass.async_add_executor_job(getattr, api.dsm, "network") hass.config_entries.async_update_entry(entry, data={ **entry.data, CONF_MAC: network.macs }) async def async_coordinator_update_data_cameras( ) -> dict[str, dict[str, SynoCamera]] | None: """Fetch all camera data from api.""" if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]: raise UpdateFailed("System not fully loaded") if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: return None surveillance_station = api.surveillance_station try: async with async_timeout.timeout(30): await hass.async_add_executor_job(surveillance_station.update) except SynologyDSMAPIErrorException as err: raise UpdateFailed(f"Error communicating with API: {err}") from err return { "cameras": { camera.id: camera for camera in surveillance_station.get_all_cameras() } } async def async_coordinator_update_data_central() -> None: """Fetch all device and sensor data from api.""" try: await api.async_update() except Exception as err: raise UpdateFailed(f"Error communicating with API: {err}") from err return None async def async_coordinator_update_data_switches( ) -> dict[str, dict[str, Any]] | None: """Fetch all switch data from api.""" if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]: raise UpdateFailed("System not fully loaded") if SynoSurveillanceStation.HOME_MODE_API_KEY not in api.dsm.apis: return None surveillance_station = api.surveillance_station return { "switches": { "home_mode": await hass.async_add_executor_job( surveillance_station.get_home_mode_status) } } hass.data[DOMAIN][ entry.unique_id][COORDINATOR_CAMERAS] = DataUpdateCoordinator( hass, _LOGGER, name=f"{entry.unique_id}_cameras", update_method=async_coordinator_update_data_cameras, update_interval=timedelta(seconds=30), ) hass.data[DOMAIN][ entry.unique_id][COORDINATOR_CENTRAL] = DataUpdateCoordinator( hass, _LOGGER, name=f"{entry.unique_id}_central", update_method=async_coordinator_update_data_central, update_interval=timedelta(minutes=entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)), ) hass.data[DOMAIN][ entry.unique_id][COORDINATOR_SWITCHES] = DataUpdateCoordinator( hass, _LOGGER, name=f"{entry.unique_id}_switches", update_method=async_coordinator_update_data_switches, update_interval=timedelta(seconds=30), ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up UPnP/IGD device from a config entry.""" LOGGER.debug("Setting up config entry: %s", entry.entry_id) hass.data.setdefault(DOMAIN, {}) udn = entry.data[CONFIG_ENTRY_UDN] st = entry.data[CONFIG_ENTRY_ST] # pylint: disable=invalid-name usn = f"{udn}::{st}" # Register device discovered-callback. device_discovered_event = asyncio.Event() discovery_info: ssdp.SsdpServiceInfo | None = None async def device_discovered(headers: ssdp.SsdpServiceInfo, change: ssdp.SsdpChange) -> None: if change == ssdp.SsdpChange.BYEBYE: return nonlocal discovery_info LOGGER.debug("Device discovered: %s, at: %s", usn, headers.ssdp_location) discovery_info = headers device_discovered_event.set() cancel_discovered_callback = await ssdp.async_register_callback( hass, device_discovered, { "usn": usn, }, ) try: await asyncio.wait_for(device_discovered_event.wait(), timeout=10) except asyncio.TimeoutError as err: raise ConfigEntryNotReady(f"Device not discovered: {usn}") from err finally: cancel_discovered_callback() # Create device. assert discovery_info is not None assert discovery_info.ssdp_location is not None location = discovery_info.ssdp_location try: device = await async_create_device(hass, location) except UpnpConnectionError as err: raise ConfigEntryNotReady( f"Error connecting to device at location: {location}, err: {err}" ) from err # Track the original UDN such that existing sensors do not change their unique_id. if CONFIG_ENTRY_ORIGINAL_UDN not in entry.data: hass.config_entries.async_update_entry( entry=entry, data={ **entry.data, CONFIG_ENTRY_ORIGINAL_UDN: device.udn, }, ) device.original_udn = entry.data[CONFIG_ENTRY_ORIGINAL_UDN] # Store mac address for changed UDN matching. if device.host: device.mac_address = await async_get_mac_address_from_host( hass, device.host) if device.mac_address and not entry.data.get("CONFIG_ENTRY_MAC_ADDRESS"): hass.config_entries.async_update_entry( entry=entry, data={ **entry.data, CONFIG_ENTRY_MAC_ADDRESS: device.mac_address, }, ) connections = {(dr.CONNECTION_UPNP, device.udn)} if device.mac_address: connections.add((dr.CONNECTION_NETWORK_MAC, device.mac_address)) device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device(identifiers=set(), connections=connections) if device_entry: LOGGER.debug( "Found device using connections: %s, device_entry: %s", connections, device_entry, ) if not device_entry: # No device found, create new device entry. device_entry = device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections=connections, identifiers={(DOMAIN, device.usn)}, name=device.name, manufacturer=device.manufacturer, model=device.model_name, ) LOGGER.debug("Created device using UDN '%s', device_entry: %s", device.udn, device_entry) else: # Update identifier. device_entry = device_registry.async_update_device( device_entry.id, new_identifiers={(DOMAIN, device.usn)}, ) assert device_entry update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL) coordinator = UpnpDataUpdateCoordinator( hass, device=device, device_entry=device_entry, update_interval=update_interval, ) # Try an initial refresh. await coordinator.async_config_entry_first_refresh() # Save coordinator. hass.data[DOMAIN][entry.entry_id] = coordinator # Setup platforms, creating sensors/binary_sensors. hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up System Bridge from a config entry.""" bridge = Bridge( BridgeClient(aiohttp_client.async_get_clientsession(hass)), f"http://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}", entry.data[CONF_API_KEY], ) try: async with async_timeout.timeout(30): await bridge.async_get_information() except BridgeAuthenticationException as exception: raise ConfigEntryAuthFailed( f"Authentication failed for {entry.title} ({entry.data[CONF_HOST]})" ) from exception except BRIDGE_CONNECTION_ERRORS as exception: raise ConfigEntryNotReady( f"Could not connect to {entry.title} ({entry.data[CONF_HOST]})." ) from exception coordinator = SystemBridgeDataUpdateCoordinator(hass, bridge, _LOGGER, entry=entry) await coordinator.async_config_entry_first_refresh() # Wait for initial data try: async with async_timeout.timeout(60): while (coordinator.bridge.battery is None or coordinator.bridge.cpu is None or coordinator.bridge.display is None or coordinator.bridge.filesystem is None or coordinator.bridge.graphics is None or coordinator.bridge.information is None or coordinator.bridge.memory is None or coordinator.bridge.network is None or coordinator.bridge.os is None or coordinator.bridge.processes is None or coordinator.bridge.system is None): _LOGGER.debug( "Waiting for initial data from %s (%s)", entry.title, entry.data[CONF_HOST], ) await asyncio.sleep(1) except asyncio.TimeoutError as exception: raise ConfigEntryNotReady( f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})." ) from exception hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator hass.config_entries.async_setup_platforms(entry, PLATFORMS) if hass.services.has_service(DOMAIN, SERVICE_SEND_COMMAND): return True async def handle_send_command(call): """Handle the send_command service call.""" device_registry = dr.async_get(hass) device_id = call.data[CONF_BRIDGE] device_entry = device_registry.async_get(device_id) if device_entry is None: _LOGGER.warning("Missing device: %s", device_id) return command = call.data[CONF_COMMAND] arguments = shlex.split(call.data.get(CONF_ARGUMENTS, "")) entry_id = next(entry.entry_id for entry in hass.config_entries.async_entries(DOMAIN) if entry.entry_id in device_entry.config_entries) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ entry_id] bridge: Bridge = coordinator.bridge _LOGGER.debug( "Command payload: %s", { CONF_COMMAND: command, CONF_ARGUMENTS: arguments, CONF_WAIT: False }, ) try: response: CommandResponse = await bridge.async_send_command({ CONF_COMMAND: command, CONF_ARGUMENTS: arguments, CONF_WAIT: False }) if response.success: _LOGGER.debug("Sent command. Response message was: %s", response.message) else: _LOGGER.warning( "Error sending command. Response message was: %s", response.message) except (BridgeAuthenticationException, *BRIDGE_CONNECTION_ERRORS) as exception: _LOGGER.warning("Error sending command. Error was: %s", exception) async def handle_open(call): """Handle the open service call.""" device_registry = dr.async_get(hass) device_id = call.data[CONF_BRIDGE] device_entry = device_registry.async_get(device_id) if device_entry is None: _LOGGER.warning("Missing device: %s", device_id) return path = call.data[CONF_PATH] entry_id = next(entry.entry_id for entry in hass.config_entries.async_entries(DOMAIN) if entry.entry_id in device_entry.config_entries) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ entry_id] bridge: Bridge = coordinator.bridge _LOGGER.debug("Open payload: %s", {CONF_PATH: path}) try: await bridge.async_open({CONF_PATH: path}) _LOGGER.debug("Sent open request") except (BridgeAuthenticationException, *BRIDGE_CONNECTION_ERRORS) as exception: _LOGGER.warning("Error sending. Error was: %s", exception) hass.services.async_register( DOMAIN, SERVICE_SEND_COMMAND, handle_send_command, schema=SERVICE_SEND_COMMAND_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_OPEN, handle_open, schema=SERVICE_OPEN_SCHEMA, ) # Reload entry when its updated. entry.async_on_unload(entry.add_update_listener(async_reload_entry)) return True
async def async_setup_entry(hass, config_entry): """Set up Tesla as config entry.""" # pylint: disable=too-many-locals hass.data.setdefault(DOMAIN, {}) config = config_entry.data # Because users can have multiple accounts, we always create a new session so they have separate cookies async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60) email = config_entry.title if not hass.data[DOMAIN]: async_setup_services(hass) if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][ email]: scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL] hass.config_entries.async_update_entry( config_entry, options={CONF_SCAN_INTERVAL: scan_interval}) hass.data[DOMAIN].pop(email) try: controller = TeslaAPI( async_client, email=config.get(CONF_USERNAME), refresh_token=config[CONF_TOKEN], access_token=config[CONF_ACCESS_TOKEN], expiration=config.get(CONF_EXPIRATION, 0), auth_domain=config.get(CONF_DOMAIN, AUTH_DOMAIN), update_interval=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL), polling_policy=config_entry.options.get(CONF_POLLING_POLICY, DEFAULT_POLLING_POLICY), ) result = await controller.connect( wake_if_asleep=config_entry.options.get(CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START)) refresh_token = result["refresh_token"] access_token = result["access_token"] expiration = result["expiration"] except IncompleteCredentials as ex: await async_client.aclose() raise ConfigEntryAuthFailed from ex except httpx.ConnectTimeout as ex: await async_client.aclose() raise ConfigEntryNotReady from ex except TeslaException as ex: await async_client.aclose() if ex.code == HTTPStatus.UNAUTHORIZED: raise ConfigEntryAuthFailed from ex if ex.message in [ "VEHICLE_UNAVAILABLE", "TOO_MANY_REQUESTS", "SERVICE_MAINTENANCE", "UPSTREAM_TIMEOUT", ]: raise ConfigEntryNotReady( f"Temporarily unable to communicate with Tesla API: {ex.message}" ) from ex _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message) return False async def _async_close_client(*_): await async_client.aclose() @callback def _async_create_close_task(): asyncio.create_task(_async_close_client()) config_entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_close_client)) config_entry.async_on_unload(_async_create_close_task) _async_save_tokens(hass, config_entry, access_token, refresh_token, expiration) coordinator = TeslaDataUpdateCoordinator(hass, config_entry=config_entry, controller=controller) # Fetch initial data so we have data when entities subscribe entry_data = hass.data[DOMAIN][config_entry.entry_id] = { "coordinator": coordinator, "devices": defaultdict(list), DATA_LISTENER: [config_entry.add_update_listener(update_listener)], } _LOGGER.debug("Connected to the Tesla API") await coordinator.async_config_entry_first_refresh() all_devices = controller.get_homeassistant_components() if not all_devices: return False for device in all_devices: entry_data["devices"][device.hass_type].append(device) hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Notion as a config entry.""" hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}}) if not entry.unique_id: hass.config_entries.async_update_entry( entry, unique_id=entry.data[CONF_USERNAME]) session = aiohttp_client.async_get_clientsession(hass) try: client = await async_get_client(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], session=session) except InvalidCredentialsError as err: raise ConfigEntryAuthFailed( "Invalid username and/or password") from err except NotionError as err: raise ConfigEntryNotReady("Config entry failed to load") from err async def async_update() -> dict[str, dict[str, Any]]: """Get the latest data from the Notion API.""" data: dict[str, dict[str, Any]] = { "bridges": {}, "sensors": {}, "tasks": {} } tasks = { "bridges": client.bridge.async_all(), "sensors": client.sensor.async_all(), "tasks": client.task.async_all(), } results = await asyncio.gather(*tasks.values(), return_exceptions=True) for attr, result in zip(tasks, results): if isinstance(result, InvalidCredentialsError): raise ConfigEntryAuthFailed( "Invalid username and/or password") from result if isinstance(result, NotionError): raise UpdateFailed( f"There was a Notion error while updating {attr}: {result}" ) from result if isinstance(result, Exception): raise UpdateFailed( f"There was an unknown error while updating {attr}: {result}" ) from result for item in result: if attr == "bridges" and item["id"] not in data["bridges"]: # If a new bridge is discovered, register it: hass.async_create_task( async_register_new_bridge(hass, item, entry)) data[attr][item["id"]] = item return data coordinator = hass.data[DOMAIN][DATA_COORDINATOR][ entry.entry_id] = DataUpdateCoordinator( hass, LOGGER, name=entry.data[CONF_USERNAME], update_interval=DEFAULT_SCAN_INTERVAL, update_method=async_update, ) await coordinator.async_config_entry_first_refresh() hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
device.discovery = discovery if white_channel_type := entry.data.get(CONF_WHITE_CHANNEL_TYPE): device.white_channel_channel_type = NAME_TO_WHITE_CHANNEL_TYPE[ white_channel_type ] @callback def _async_state_changed(*_: Any) -> None: _LOGGER.debug("%s: Device state updated: %s", device.ipaddr, device.raw_state) async_dispatcher_send(hass, signal) try: await device.async_setup(_async_state_changed) except FLUX_LED_EXCEPTIONS as ex: raise ConfigEntryNotReady( str(ex) or f"Timed out trying to connect to {device.ipaddr}" ) from ex # UDP probe after successful connect only if discovery_cached: if directed_discovery := await async_discover_device(hass, host): device.discovery = discovery = directed_discovery discovery_cached = False if entry.unique_id and discovery.get(ATTR_ID): mac = dr.format_mac(cast(str, discovery[ATTR_ID])) if not mac_matches_by_one(mac, entry.unique_id): # The device is offline and another flux_led device is now using the ip address raise ConfigEntryNotReady( f"Unexpected device found at {host}; Expected {entry.unique_id}, found {mac}" )
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Overkiz from a config entry.""" username = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] server = SUPPORTED_SERVERS[entry.data[CONF_HUB]] if await _block_if_core_is_configured(hass, entry): raise ConfigEntryNotReady( "You cannot use Overkiz from core and custom component at the same time." ) # To allow users with multiple accounts/hubs, we create a new session so they have separate cookies session = async_create_clientsession(hass) client = OverkizClient(username=username, password=password, session=session, server=server) try: await client.login() tasks = [ client.get_setup(), client.get_scenarios(), ] setup, scenarios = await asyncio.gather(*tasks) except BadCredentialsException as exception: raise ConfigEntryAuthFailed from exception except TooManyRequestsException as exception: raise ConfigEntryNotReady( "Too many requests, try again later") from exception except (TimeoutError, ClientError, ServerDisconnectedError) as exception: raise ConfigEntryNotReady("Failed to connect") from exception except MaintenanceException as exception: raise ConfigEntryNotReady( "Server is down for maintenance") from exception except Exception as exception: # pylint: disable=broad-except _LOGGER.exception(exception) return False coordinator = OverkizDataUpdateCoordinator( hass, _LOGGER, name="device events", client=client, devices=setup.devices, places=setup.root_place, update_interval=UPDATE_INTERVAL, config_entry_id=entry.entry_id, ) await coordinator.async_config_entry_first_refresh() if coordinator.is_stateless: _LOGGER.debug( "All devices have assumed state. Update interval has been reduced to: %s", UPDATE_INTERVAL_ALL_ASSUMED_STATE, ) coordinator.update_interval = UPDATE_INTERVAL_ALL_ASSUMED_STATE platforms: defaultdict[Platform, list[Device]] = defaultdict(list) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = HomeAssistantOverkizData( coordinator=coordinator, platforms=platforms, scenarios=scenarios) # Map Overkiz device to Home Assistant platform for device in coordinator.data.values(): platform = OVERKIZ_DEVICE_TO_PLATFORM.get( device.widget) or OVERKIZ_DEVICE_TO_PLATFORM.get(device.ui_class) if platform: platforms[platform].append(device) log_device("Added device", device) elif (device.widget not in IGNORED_OVERKIZ_DEVICES and device.ui_class not in IGNORED_OVERKIZ_DEVICES): log_device("Unsupported device detected", device) hass.config_entries.async_setup_platforms(entry, SUPPORTED_PLATFORMS) device_registry = await dr.async_get_registry(hass) for gateway in setup.gateways: _LOGGER.debug("Added gateway (%s)", gateway) device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, gateway.id)}, model=gateway.sub_type.beautify_name if gateway.sub_type else None, manufacturer=server.manufacturer, name=gateway.type.beautify_name, sw_version=gateway.connectivity.protocol_version, configuration_url=server.configuration_url, ) async def handle_execute_command(call: ServiceCall): """Handle execute command service.""" entity_registry = await hass.helpers.entity_registry.async_get_registry( ) for entity_id in call.data.get("entity_id"): entity = entity_registry.entities.get(entity_id) try: await coordinator.client.execute_command( entity.unique_id, Command(call.data.get("command"), call.data.get("args")), "Home Assistant Service", ) except InvalidCommandException as exception: _LOGGER.error(exception) service.async_register_admin_service( hass, DOMAIN, SERVICE_EXECUTE_COMMAND, handle_execute_command, vol.Schema( { vol.Required("entity_id"): [cv.entity_id], vol.Required("command"): cv.string, vol.Optional("args", default=[]): vol.All(cv.ensure_list, [vol.Any(str, int)]), }, ), ) async def handle_get_execution_history(call): """Handle get execution history service.""" await write_execution_history_to_log(coordinator.client) service.async_register_admin_service( hass, DOMAIN, "get_execution_history", handle_get_execution_history, ) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Sense from a config entry.""" entry_data = entry.data email = entry_data[CONF_EMAIL] timeout = entry_data[CONF_TIMEOUT] access_token = entry_data.get("access_token", "") user_id = entry_data.get("user_id", "") monitor_id = entry_data.get("monitor_id", "") client_session = async_get_clientsession(hass) gateway = ASyncSenseable(api_timeout=timeout, wss_timeout=timeout, client_session=client_session) gateway.rate_limit = ACTIVE_UPDATE_RATE try: gateway.load_auth(access_token, user_id, monitor_id) await gateway.get_monitor_data() except (SenseAuthenticationException, SenseMFARequiredException) as err: _LOGGER.warning("Sense authentication expired") raise ConfigEntryAuthFailed(err) from err sense_devices_data = SenseDevicesData() try: sense_discovered_devices = await gateway.get_discovered_device_data() await gateway.update_realtime() except SENSE_TIMEOUT_EXCEPTIONS as err: raise ConfigEntryNotReady( str(err) or "Timed out during realtime update") from err except SENSE_EXCEPTIONS as err: raise ConfigEntryNotReady(str(err) or "Error during realtime update") from err async def _async_update_trend(): """Update the trend data.""" try: await gateway.update_trend_data() except (SenseAuthenticationException, SenseMFARequiredException) as err: _LOGGER.warning("Sense authentication expired") raise ConfigEntryAuthFailed(err) from err trends_coordinator: DataUpdateCoordinator[None] = DataUpdateCoordinator( hass, _LOGGER, name=f"Sense Trends {email}", update_method=_async_update_trend, update_interval=timedelta(seconds=300), ) # Start out as unavailable so we do not report 0 data # until the update happens trends_coordinator.last_update_success = False # This can take longer than 60s and we already know # sense is online since get_discovered_device_data was # successful so we do it later. asyncio.create_task(trends_coordinator.async_request_refresh()) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { SENSE_DATA: gateway, SENSE_DEVICES_DATA: sense_devices_data, SENSE_TRENDS_COORDINATOR: trends_coordinator, SENSE_DISCOVERED_DEVICES_DATA: sense_discovered_devices, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) async def async_sense_update(_): """Retrieve latest state.""" try: await gateway.update_realtime() except SENSE_TIMEOUT_EXCEPTIONS as ex: _LOGGER.error("Timeout retrieving data: %s", ex) except SENSE_EXCEPTIONS as ex: _LOGGER.error("Failed to update data: %s", ex) data = gateway.get_realtime() if "devices" in data: sense_devices_data.set_devices_data(data["devices"]) async_dispatcher_send( hass, f"{SENSE_DEVICE_UPDATE}-{gateway.sense_monitor_id}") remove_update_callback = async_track_time_interval( hass, async_sense_update, timedelta(seconds=ACTIVE_UPDATE_RATE)) @callback def _remove_update_callback_at_stop(event): remove_update_callback() entry.async_on_unload(remove_update_callback) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _remove_update_callback_at_stop)) return True
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """ set up digitalSTROM component from config entry """ _LOGGER.debug("digitalstrom setup started") # initialize component data hass.data.setdefault(DOMAIN, dict()) # old installations don't have an app token in their config entry if not entry.data.get(CONF_TOKEN, None): raise InvalidStateError( "No app token in config entry, please re-setup the integration") # setup client and listener client = DSClient( host=entry.data[CONF_HOST], port=entry.data[CONF_PORT], apptoken=entry.data[CONF_TOKEN], apartment_name=entry.data[CONF_ALIAS], stack_delay=entry.data.get(CONF_DELAY, DEFAULT_DELAY), loop=hass.loop, ) listener = DSWebsocketEventListener(client=client, event_name="callScene") # store client in hass data for future usage entry_slug = slugify_entry(host=entry.data[CONF_HOST], port=entry.data[CONF_PORT]) hass.data[DOMAIN].setdefault(entry_slug, dict()) hass.data[DOMAIN][entry_slug]["client"] = client hass.data[DOMAIN][entry_slug]["listener"] = listener # load all scenes from digitalSTROM server # this fails often on the first connection, but works on the second try: await client.initialize() except (DSException, RuntimeError, ConnectionResetError): try: await client.initialize() except (DSException, RuntimeError, ConnectionResetError): raise ConfigEntryNotReady( f"Failed to initialize digitalSTROM server at {client.host}") # we're connected _LOGGER.debug( f"Successfully retrieved session token from digitalSTROM server at {client.host}" ) # register devices for component in COMPONENT_TYPES: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component)) # start websocket listener and action delayer loops on hass startup async def digitalstrom_start_loops(event): _LOGGER.debug( f"loops started for digitalSTROM server at {client.host}") hass.async_add_job(listener.start) hass.async_add_job(client.stack.start) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, digitalstrom_start_loops) # start websocket listener and action delayer loops on hass shutdown async def digitalstrom_stop_loops(event): _LOGGER.debug( f"loops stopped for digitalSTROM server at {client.host}") hass.async_add_job(client.stack.stop) hass.async_add_job(listener.stop) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, digitalstrom_stop_loops) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Aqualink from a config entry.""" username = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] hass.data.setdefault(DOMAIN, {}) # These will contain the initialized devices binary_sensors = hass.data[DOMAIN][BINARY_SENSOR_DOMAIN] = [] climates = hass.data[DOMAIN][CLIMATE_DOMAIN] = [] lights = hass.data[DOMAIN][LIGHT_DOMAIN] = [] sensors = hass.data[DOMAIN][SENSOR_DOMAIN] = [] switches = hass.data[DOMAIN][SWITCH_DOMAIN] = [] session = async_get_clientsession(hass) aqualink = AqualinkClient(username, password, session) try: await aqualink.login() except AqualinkServiceException as login_exception: _LOGGER.error("Failed to login: %s", login_exception) return False except ( asyncio.TimeoutError, aiohttp.client_exceptions.ClientConnectorError, ) as aio_exception: raise ConfigEntryNotReady( f"Error while attempting login: {aio_exception}" ) from aio_exception try: systems = await aqualink.get_systems() except AqualinkServiceException as svc_exception: raise ConfigEntryNotReady( f"Error while attempting to retrieve systems list: {svc_exception}" ) from svc_exception systems = list(systems.values()) if not systems: _LOGGER.error("No systems detected or supported") return False # Only supporting the first system for now. try: devices = await systems[0].get_devices() except AqualinkServiceException as svc_exception: raise ConfigEntryNotReady( f"Error while attempting to retrieve devices list: {svc_exception}" ) from svc_exception for dev in devices.values(): if isinstance(dev, AqualinkThermostat): climates += [dev] elif isinstance(dev, AqualinkLight): lights += [dev] elif isinstance(dev, AqualinkBinarySensor): binary_sensors += [dev] elif isinstance(dev, AqualinkSensor): sensors += [dev] elif isinstance(dev, AqualinkToggle): switches += [dev] forward_setup = hass.config_entries.async_forward_entry_setup if binary_sensors: _LOGGER.debug("Got %s binary sensors: %s", len(binary_sensors), binary_sensors) hass.async_create_task(forward_setup(entry, Platform.BINARY_SENSOR)) if climates: _LOGGER.debug("Got %s climates: %s", len(climates), climates) hass.async_create_task(forward_setup(entry, Platform.CLIMATE)) if lights: _LOGGER.debug("Got %s lights: %s", len(lights), lights) hass.async_create_task(forward_setup(entry, Platform.LIGHT)) if sensors: _LOGGER.debug("Got %s sensors: %s", len(sensors), sensors) hass.async_create_task(forward_setup(entry, Platform.SENSOR)) if switches: _LOGGER.debug("Got %s switches: %s", len(switches), switches) hass.async_create_task(forward_setup(entry, Platform.SWITCH)) async def _async_systems_update(now): """Refresh internal state for all systems.""" prev = systems[0].online try: await systems[0].update() except AqualinkServiceException as svc_exception: if prev is not None: _LOGGER.warning("Failed to refresh iAqualink state: %s", svc_exception) else: cur = systems[0].online if cur is True and prev is not True: _LOGGER.warning("Reconnected to iAqualink") async_dispatcher_send(hass, DOMAIN) async_track_time_interval(hass, _async_systems_update, UPDATE_INTERVAL) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Async setup hass config entry.""" hass.data.setdefault(DOMAIN, {}) # Project type has been renamed to auth type in the upstream Tuya IoT SDK. # This migrates existing config entries to reflect that name change. if CONF_PROJECT_TYPE in entry.data: data = {**entry.data, CONF_AUTH_TYPE: entry.data[CONF_PROJECT_TYPE]} data.pop(CONF_PROJECT_TYPE) hass.config_entries.async_update_entry(entry, data=data) auth_type = AuthType(entry.data[CONF_AUTH_TYPE]) api = TuyaOpenAPI( endpoint=entry.data[CONF_ENDPOINT], access_id=entry.data[CONF_ACCESS_ID], access_secret=entry.data[CONF_ACCESS_SECRET], auth_type=auth_type, ) api.set_dev_channel("hass") try: if auth_type == AuthType.CUSTOM: response = await hass.async_add_executor_job( api.connect, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]) else: response = await hass.async_add_executor_job( api.connect, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], entry.data[CONF_COUNTRY_CODE], entry.data[CONF_APP_TYPE], ) except requests.exceptions.RequestException as err: raise ConfigEntryNotReady(err) from err if response.get("success", False) is False: raise ConfigEntryNotReady(response) tuya_mq = TuyaOpenMQ(api) tuya_mq.start() device_ids: set[str] = set() device_manager = TuyaDeviceManager(api, tuya_mq) home_manager = TuyaHomeManager(api, tuya_mq, device_manager) listener = DeviceListener(hass, device_manager, device_ids) device_manager.add_device_listener(listener) hass.data[DOMAIN][entry.entry_id] = HomeAssistantTuyaData( device_listener=listener, device_manager=device_manager, home_manager=home_manager, ) # Get devices & clean up device entities await hass.async_add_executor_job(home_manager.update_device_cache) await cleanup_device_registry(hass, device_manager) # Migrate old unique_ids to the new format async_migrate_entities_unique_ids(hass, entry, device_manager) # Register known device IDs device_registry = dr.async_get(hass) for device in device_manager.device_map.values(): device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, device.id)}, manufacturer="Tuya", name=device.name, model=f"{device.product_name} (unsupported)", ) device_ids.add(device.id) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): """Set up Hive from a config entry.""" # Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) hive = await hive_data(hass) hive_config = dict(entry.data) hive_options = dict(entry.options) async def heating_boost(service_call): """Handle the service call.""" node_id = hive.entity_lookup.get(service_call.data[ATTR_ENTITY_ID]) device = pyhiveapi.Hive_Helper.get_device_from_id(node_id) if not node_id: # log or raise error _LOGGER.error("Cannot boost entity id entered") return minutes = service_call.data[ATTR_TIME_PERIOD] temperature = service_call.data[ATTR_TEMPERATURE] await hive.heating.turn_boost_on(device, minutes, temperature) async def hot_water_boost(service_call): """Handle the service call.""" node_id = hive.entity_lookup.get(service_call.data[ATTR_ENTITY_ID]) device = pyhiveapi.Hive_Helper.get_device_from_id(node_id) if not node_id: # log or raise error _LOGGER.error("Cannot boost entity id entered") return minutes = service_call.data[ATTR_TIME_PERIOD] mode = service_call.data[ATTR_MODE] if mode == "on": await hive.hotwater.turn_boost_on(device, minutes) elif mode == "off": await hive.hotwater.turn_boost_off(device) Tokens = hive_config.get("tokens", None) Username = hive_config["options"].get(CONF_USERNAME) Password = hive_config.get(CONF_PASSWORD) Update = "Y" if datetime.now() >= datetime.strptime(entry.data.get( "created"), '%Y-%m-%d %H:%M:%S.%f') + timedelta(minutes=60) else "N" # Update config entry options hive_options = hive_options if len( hive_options) > 0 else hive_config["options"] hive_config["options"].update(hive_options) hass.config_entries.async_update_entry(entry, options=hive_options) hass.data[DOMAIN][entry.entry_id] = hive try: devices = await hive.session.start_session(Tokens, Update, hive_config) except HTTPException as error: _LOGGER.error("Could not connect to the internet: %s", error) raise ConfigEntryNotReady() from error if devices == "INVALID_REAUTH": return hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"username": Username, "password": Password} ) ) hive.devices = devices for component in PLATFORMS: devicelist = devices.get(component) if devicelist: hass.async_create_task( hass.config_entries.async_forward_entry_setup( entry, component) ) if component == "climate": hass.services.async_register( DOMAIN, SERVICE_BOOST_HEATING, heating_boost, schema=BOOST_HEATING_SCHEMA, ) if component == "water_heater": hass.services.async_register( DOMAIN, SERVICE_BOOST_HOT_WATER, hot_water_boost, schema=BOOST_HOT_WATER_SCHEMA, ) return True
async def async_setup_gateway_entry( hass: core.HomeAssistant, entry: config_entries.ConfigEntry ): """Set up the Xiaomi Gateway component from a config entry.""" host = entry.data[CONF_HOST] token = entry.data[CONF_TOKEN] name = entry.title gateway_id = entry.unique_id # For backwards compat if entry.unique_id.endswith("-gateway"): hass.config_entries.async_update_entry(entry, unique_id=entry.data["mac"]) entry.async_on_unload(entry.add_update_listener(update_listener)) # Connect to gateway gateway = ConnectXiaomiGateway(hass, entry) try: await gateway.async_connect_gateway(host, token) except AuthException as error: raise ConfigEntryAuthFailed() from error except SetupException as error: raise ConfigEntryNotReady() from error gateway_info = gateway.gateway_info gateway_model = f"{gateway_info.model}-{gateway_info.hardware_version}" device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, gateway_info.mac_address)}, identifiers={(DOMAIN, gateway_id)}, manufacturer="Xiaomi", name=name, model=gateway_model, sw_version=gateway_info.firmware_version, ) def update_data(): """Fetch data from the subdevice.""" data = {} for sub_device in gateway.gateway_device.devices.values(): try: sub_device.update() except GatewayException as ex: _LOGGER.error("Got exception while fetching the state: %s", ex) data[sub_device.sid] = {ATTR_AVAILABLE: False} else: data[sub_device.sid] = {ATTR_AVAILABLE: True} return data async def async_update_data(): """Fetch data from the subdevice using async_add_executor_job.""" return await hass.async_add_executor_job(update_data) # Create update coordinator coordinator = DataUpdateCoordinator( hass, _LOGGER, name=name, update_method=async_update_data, # Polling interval. Will only be polled if there are subscribers. update_interval=UPDATE_INTERVAL, ) hass.data[DOMAIN][entry.entry_id] = { CONF_GATEWAY: gateway.gateway_device, KEY_COORDINATOR: coordinator, } for platform in GATEWAY_PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) )
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the wiz integration from a config entry.""" ip_address = entry.data[CONF_HOST] _LOGGER.debug("Get bulb with IP: %s", ip_address) bulb = wizlight(ip_address) try: scenes = await bulb.getSupportedScenes() await bulb.getMac() except WIZ_CONNECT_EXCEPTIONS as err: await bulb.async_close() raise ConfigEntryNotReady(f"{ip_address}: {err}") from err async def _async_update() -> None: """Update the WiZ device.""" try: await bulb.updateState() except WIZ_EXCEPTIONS as ex: raise UpdateFailed( f"Failed to update device at {ip_address}: {ex}") from ex coordinator = DataUpdateCoordinator( hass=hass, logger=_LOGGER, name=entry.title, update_interval=timedelta(seconds=15), update_method=_async_update, # We don't want an immediate refresh since the device # takes a moment to reflect the state change request_refresh_debouncer=Debouncer(hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False), ) try: await coordinator.async_config_entry_first_refresh() except ConfigEntryNotReady as err: await bulb.async_close() raise err async def _async_shutdown_on_stop(event: Event) -> None: await bulb.async_close() entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown_on_stop)) @callback def _async_push_update(state: PilotParser) -> None: """Receive a push update.""" _LOGGER.debug("%s: Got push update: %s", bulb.mac, state.pilotResult) coordinator.async_set_updated_data(None) if state.get_source() == PIR_SOURCE: async_dispatcher_send(hass, SIGNAL_WIZ_PIR.format(bulb.mac)) await bulb.start_push(_async_push_update) bulb.set_discovery_callback( lambda bulb: async_trigger_discovery(hass, [bulb])) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData(coordinator=coordinator, bulb=bulb, scenes=scenes) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up LED BLE from a config entry.""" address: str = entry.data[CONF_ADDRESS] ble_device = bluetooth.async_ble_device_from_address( hass, address.upper(), True) if not ble_device: raise ConfigEntryNotReady( f"Could not find LED BLE device with address {address}") led_ble = LEDBLE(ble_device) @callback def _async_update_ble( service_info: bluetooth.BluetoothServiceInfoBleak, change: bluetooth.BluetoothChange, ) -> None: """Update from a ble callback.""" led_ble.set_ble_device(service_info.device) entry.async_on_unload( bluetooth.async_register_callback( hass, _async_update_ble, BluetoothCallbackMatcher({ADDRESS: address}), bluetooth.BluetoothScanningMode.PASSIVE, )) async def _async_update(): """Update the device state.""" try: await led_ble.update() except BLEAK_EXCEPTIONS as ex: raise UpdateFailed(str(ex)) from ex startup_event = asyncio.Event() cancel_first_update = led_ble.register_callback( lambda *_: startup_event.set()) coordinator = DataUpdateCoordinator( hass, _LOGGER, name=led_ble.name, update_method=_async_update, update_interval=timedelta(seconds=UPDATE_SECONDS), ) try: await coordinator.async_config_entry_first_refresh() except ConfigEntryNotReady: cancel_first_update() raise try: async with async_timeout.timeout(DEVICE_TIMEOUT): await startup_event.wait() except asyncio.TimeoutError as ex: raise ConfigEntryNotReady( "Unable to communicate with the device; " f"Try moving the Bluetooth adapter closer to {led_ble.name}" ) from ex finally: cancel_first_update() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = LEDBLEData( entry.title, led_ble, coordinator) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(_async_update_listener)) async def _async_stop(event: Event) -> None: """Close the connection.""" await led_ble.stop() entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop)) return True
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool: username = config_entry.data[CONF_USERNAME] yaml_config = hass.data.get(DATA_CONFIG) if config_entry.source == SOURCE_IMPORT and not (yaml_config and username in yaml_config): _LOGGER.info( "Removing entry %s after removal from YAML configuration." % config_entry.entry_id) hass.async_create_task( hass.config_entries.async_remove(config_entry.entry_id)) return False config = extract_config(hass, config_entry) device_info = None if config_entry.options: device_info = config_entry.options.get(CONF_DEVICE_INFO) if device_info is None: device_info = config.get(CONF_DEVICE_INFO) else: device_info = DEVICE_INFO_SCHEMA(device_info) _LOGGER.debug('Setting up config entry for user "%s"' % username) from custom_components.moscow_pgu._base import MoscowPGUEntity password = config[CONF_PASSWORD] additional_args = { "cache_lifetime": MoscowPGUEntity.MIN_SCAN_INTERVAL.total_seconds() } if device_info: additional_args.update({ arg: device_info[conf] for arg, conf in { "app_version": CONF_APP_VERSION, "device_os": CONF_DEVICE_OS, "device_agent": CONF_DEVICE_AGENT, "user_agent": CONF_USER_AGENT, "guid": CONF_GUID, }.items() if conf in device_info }) if not additional_args.get("guid"): # @TODO: this can be randomly generated? additional_args["guid"] = generate_guid(config) token = config_entry.options.get(CONF_TOKEN) if not token: token = config.get(CONF_TOKEN) if token: additional_args["token"] = token session_id = await async_load_session(hass, username) api_object = API( username=username, password=config[CONF_PASSWORD], session_id=session_id, **additional_args, ) try: try: await api_object.init_session() await async_authenticate_api_object(hass, api_object) except MoscowPGUException as e: raise ConfigEntryNotReady( "Error occurred while authenticating: %s", e) except BaseException: await api_object.close_session() raise hass.data.setdefault(DOMAIN, {})[username] = api_object hass.data.setdefault(DATA_ENTITIES, {})[config_entry.entry_id] = {} for platform in SUPPORTED_PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup( config_entry, platform)) update_listener = config_entry.add_update_listener(async_reload_entry) hass.data.setdefault(DATA_UPDATE_LISTENERS, {})[config_entry.entry_id] = update_listener return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up SmartThinQ integration from a config entry.""" if not is_valid_ha_version(): msg = "This integration require at least HomeAssistant version " \ f" {__min_ha_version__}, you are running version {__version__}." \ " Please upgrade HomeAssistant to continue use this integration." _notify_error(hass, "inv_ha_version", "SmartThinQ Sensors", msg) _LOGGER.warning(msg) return False refresh_token = entry.data[CONF_TOKEN] region = entry.data[CONF_REGION] language = entry.data[CONF_LANGUAGE] oauth_url = entry.data.get(CONF_OAUTH_URL) use_api_v2 = entry.data.get(CONF_USE_API_V2, False) if not use_api_v2: _LOGGER.warning( "Integration configuration is using ThinQ APIv1 that is unsupported. Please reconfigure" ) # Launch config entries setup hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=entry.data)) return False _LOGGER.info(STARTUP) _LOGGER.info( "Initializing ThinQ platform with region: %s - language: %s", region, language, ) # if network is not connected we can have some error # raising ConfigEntryNotReady platform setup will be retried lge_auth = LGEAuthentication(region, language) try: client = await lge_auth.create_client_from_token( hass, refresh_token, oauth_url) except InvalidCredentialError: msg = "Invalid ThinQ credential error, integration setup aborted." \ " Please use the LG App on your mobile device to ensure your" \ " credentials are correct, then restart HomeAssistant." \ " If your credential changed, you must reconfigure integration" _notify_error(hass, "inv_credential", "SmartThinQ Sensors", msg) _LOGGER.error(msg) return False except Exception as exc: _LOGGER.warning("Connection not available. ThinQ platform not ready", exc_info=True) raise ConfigEntryNotReady("ThinQ platform not ready") from exc if not client.has_devices: _LOGGER.error("No ThinQ devices found. Component setup aborted") return False _LOGGER.info("ThinQ client connected") try: lge_devices, unsupported_devices = await lge_devices_setup( hass, client) except Exception as exc: _LOGGER.warning("Connection not available. ThinQ platform not ready", exc_info=True) raise ConfigEntryNotReady("ThinQ platform not ready") from exc # remove device not available anymore cleanup_orphan_lge_devices(hass, entry.entry_id, client) hass.data[DOMAIN] = { CLIENT: client, LGE_DEVICES: lge_devices, UNSUPPORTED_DEVICES: unsupported_devices, } hass.config_entries.async_setup_platforms(entry, SMARTTHINQ_PLATFORMS) return True
async def async_setup_entry( # noqa: C901 hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Synology DSM sensors.""" # Migrate old unique_id @callback def _async_migrator( entity_entry: entity_registry.RegistryEntry, ) -> dict[str, str] | None: """Migrate away from ID using label.""" # Reject if new unique_id if "SYNO." in entity_entry.unique_id: return None entries = ( *STORAGE_DISK_BINARY_SENSORS, *STORAGE_DISK_SENSORS, *STORAGE_VOL_SENSORS, *UTILISATION_SENSORS, ) infos = entity_entry.unique_id.split("_") serial = infos.pop(0) label = infos.pop(0) device_id = "_".join(infos) # Removed entity if ("Type" in entity_entry.unique_id or "Device" in entity_entry.unique_id or "Name" in entity_entry.unique_id): return None entity_type: str | None = None for description in entries: if (device_id and description.name == "Status" and "Status" in entity_entry.unique_id and "(Smart)" not in entity_entry.unique_id): if "sd" in device_id and "disk" in description.key: entity_type = description.key continue if "volume" in device_id and "volume" in description.key: entity_type = description.key continue if description.name == label: entity_type = description.key if entity_type is None: return None new_unique_id = "_".join([serial, entity_type]) if device_id: new_unique_id += f"_{device_id}" _LOGGER.info( "Migrating unique_id from [%s] to [%s]", entity_entry.unique_id, new_unique_id, ) return {"new_unique_id": new_unique_id} await entity_registry.async_migrate_entries(hass, entry.entry_id, _async_migrator) # migrate device indetifiers dev_reg = await get_dev_reg(hass) devices: list[ DeviceEntry] = device_registry.async_entries_for_config_entry( dev_reg, entry.entry_id) for device in devices: old_identifier = list(next(iter(device.identifiers))) if len(old_identifier) > 2: new_identifier = {(old_identifier.pop(0), "_".join([str(x) for x in old_identifier]))} _LOGGER.debug("migrate identifier '%s' to '%s'", device.identifiers, new_identifier) dev_reg.async_update_device(device.id, new_identifiers=new_identifier) # Migrate existing entry configuration if entry.data.get(CONF_VERIFY_SSL) is None: hass.config_entries.async_update_entry( entry, data={ **entry.data, CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL }) # Continue setup api = SynoApi(hass, entry) try: await api.async_setup() except ( SynologyDSMLogin2SARequiredException, SynologyDSMLoginDisabledAccountException, SynologyDSMLoginInvalidException, SynologyDSMLoginPermissionDeniedException, ) as err: if err.args[0] and isinstance(err.args[0], dict): # pylint: disable=no-member details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN) else: details = EXCEPTION_UNKNOWN raise ConfigEntryAuthFailed(f"reason: {details}") from err except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: if err.args[0] and isinstance(err.args[0], dict): # pylint: disable=no-member details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN) else: details = EXCEPTION_UNKNOWN raise ConfigEntryNotReady(details) from err hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = { UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), SYNO_API: api, SYSTEM_LOADED: True, } # Services await _async_setup_services(hass) # For SSDP compat if not entry.data.get(CONF_MAC): network = await hass.async_add_executor_job(getattr, api.dsm, "network") hass.config_entries.async_update_entry(entry, data={ **entry.data, CONF_MAC: network.macs }) async def async_coordinator_update_data_cameras( ) -> dict[str, dict[str, SynoCamera]] | None: """Fetch all camera data from api.""" if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]: raise UpdateFailed("System not fully loaded") if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: return None surveillance_station = api.surveillance_station try: async with async_timeout.timeout(10): await hass.async_add_executor_job(surveillance_station.update) except SynologyDSMAPIErrorException as err: raise UpdateFailed(f"Error communicating with API: {err}") from err return { "cameras": { camera.id: camera for camera in surveillance_station.get_all_cameras() } } async def async_coordinator_update_data_central() -> None: """Fetch all device and sensor data from api.""" try: await api.async_update() except Exception as err: raise UpdateFailed(f"Error communicating with API: {err}") from err return None async def async_coordinator_update_data_switches( ) -> dict[str, dict[str, Any]] | None: """Fetch all switch data from api.""" if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]: raise UpdateFailed("System not fully loaded") if SynoSurveillanceStation.HOME_MODE_API_KEY not in api.dsm.apis: return None surveillance_station = api.surveillance_station return { "switches": { "home_mode": await hass.async_add_executor_job( surveillance_station.get_home_mode_status) } } hass.data[DOMAIN][ entry.unique_id][COORDINATOR_CAMERAS] = DataUpdateCoordinator( hass, _LOGGER, name=f"{entry.unique_id}_cameras", update_method=async_coordinator_update_data_cameras, update_interval=timedelta(seconds=30), ) hass.data[DOMAIN][ entry.unique_id][COORDINATOR_CENTRAL] = DataUpdateCoordinator( hass, _LOGGER, name=f"{entry.unique_id}_central", update_method=async_coordinator_update_data_central, update_interval=timedelta(minutes=entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)), ) hass.data[DOMAIN][ entry.unique_id][COORDINATOR_SWITCHES] = DataUpdateCoordinator( hass, _LOGGER, name=f"{entry.unique_id}_switches", update_method=async_coordinator_update_data_switches, update_interval=timedelta(seconds=30), ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Withings from a config entry.""" config_updates = {} # Add a unique id if it's an older config entry. if entry.unique_id != entry.data["token"]["userid"] or not isinstance( entry.unique_id, str): config_updates["unique_id"] = str(entry.data["token"]["userid"]) # Add the webhook configuration. if CONF_WEBHOOK_ID not in entry.data: webhook_id = webhook.async_generate_id() config_updates["data"] = { **entry.data, **{ const.CONF_USE_WEBHOOK: hass.data[DOMAIN][const.CONFIG][const.CONF_USE_WEBHOOK], CONF_WEBHOOK_ID: webhook_id, const.CONF_WEBHOOK_URL: entry.data.get( const.CONF_WEBHOOK_URL, webhook.async_generate_url(hass, webhook_id), ), }, } if config_updates: hass.config_entries.async_update_entry(entry, **config_updates) data_manager = await async_get_data_manager(hass, entry) _LOGGER.debug("Confirming %s is authenticated to withings", data_manager.profile) await data_manager.poll_data_update_coordinator.async_refresh() if not data_manager.poll_data_update_coordinator.last_update_success: raise ConfigEntryNotReady() webhook.async_register( hass, const.DOMAIN, "Withings notify", data_manager.webhook_config.id, async_webhook_handler, ) # Perform first webhook subscription check. if data_manager.webhook_config.enabled: data_manager.async_start_polling_webhook_subscriptions() @callback def async_call_later_callback(now) -> None: hass.async_create_task( data_manager.subscription_update_coordinator.async_refresh()) # Start subscription check in the background, outside this component's setup. async_call_later(hass, 1, async_call_later_callback) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, BINARY_SENSOR_DOMAIN)) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, SENSOR_DOMAIN)) return True
async def async_setup_entry(hass: HomeAssistantType, config_entry): """ This class is called by the HomeAssistant framework when a configuration entry is provided. """ refresh_token = config_entry.data.get(CONF_TOKEN) region = config_entry.data.get(CONF_REGION) language = config_entry.data.get(CONF_LANGUAGE) use_api_v2 = config_entry.data.get(CONF_USE_API_V2, False) oauth_url = config_entry.data.get(CONF_OAUTH_URL) oauth_user_num = config_entry.data.get(CONF_OAUTH_USER_NUM) use_tls_v1 = config_entry.data.get(CONF_USE_TLS_V1, False) exclude_dh = config_entry.data.get(CONF_EXCLUDE_DH, False) _LOGGER.info(STARTUP) _LOGGER.info( "Initializing ThinQ platform with region: %s - language: %s", region, language, ) hass.data.setdefault(DOMAIN, {})[LGE_DEVICES] = {} # if network is not connected we can have some error # raising ConfigEntryNotReady platform setup will be retried lgeauth = LGEAuthentication(region, language, use_api_v2) lgeauth.initHttpAdapter(use_tls_v1, exclude_dh) try: client = await hass.async_add_executor_job( lgeauth.createClientFromToken, refresh_token, oauth_url, oauth_user_num) except InvalidCredentialError: _LOGGER.error( "Invalid ThinQ credential error. Component setup aborted") return False except Exception: _LOGGER.warning("Connection not available. ThinQ platform not ready", exc_info=True) raise ConfigEntryNotReady() if not client.hasdevices: _LOGGER.error("No ThinQ devices found. Component setup aborted") return False _LOGGER.info("ThinQ client connected") try: lge_devices = await lge_devices_setup(hass, client) except Exception: _LOGGER.warning("Connection not available. ThinQ platform not ready", exc_info=True) raise ConfigEntryNotReady() if not use_api_v2: _LOGGER.warning( "Integration configuration is using ThinQ APIv1 that is obsolete" " and not able to manage all ThinQ devices." " Please remove and re-add integration from HA user interface to" " enable the use of ThinQ APIv2") hass.data.setdefault(DOMAIN, {}).update({ CLIENT: client, LGE_DEVICES: lge_devices }) for platform in SMARTTHINQ_COMPONENTS: hass.async_create_task( hass.config_entries.async_forward_entry_setup( config_entry, platform)) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up System Bridge from a config entry.""" # Check version before initialising version = Version( entry.data[CONF_HOST], entry.data[CONF_PORT], entry.data[CONF_API_KEY], session=async_get_clientsession(hass), ) try: if not await version.check_supported(): raise ConfigEntryNotReady( f"You are not running a supported version of System Bridge. Please update to {SUPPORTED_VERSION} or higher." ) except AuthenticationException as exception: _LOGGER.error("Authentication failed for %s: %s", entry.title, exception) raise ConfigEntryAuthFailed from exception except (ConnectionClosedException, ConnectionErrorException) as exception: raise ConfigEntryNotReady( f"Could not connect to {entry.title} ({entry.data[CONF_HOST]})." ) from exception except asyncio.TimeoutError as exception: raise ConfigEntryNotReady( f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})." ) from exception coordinator = SystemBridgeDataUpdateCoordinator( hass, _LOGGER, entry=entry, ) try: async with async_timeout.timeout(30): await coordinator.async_get_data(MODULES) except AuthenticationException as exception: _LOGGER.error("Authentication failed for %s: %s", entry.title, exception) raise ConfigEntryAuthFailed from exception except (ConnectionClosedException, ConnectionErrorException) as exception: raise ConfigEntryNotReady( f"Could not connect to {entry.title} ({entry.data[CONF_HOST]})." ) from exception except asyncio.TimeoutError as exception: raise ConfigEntryNotReady( f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})." ) from exception await coordinator.async_config_entry_first_refresh() try: # Wait for initial data async with async_timeout.timeout(30): while not coordinator.is_ready(): _LOGGER.debug( "Waiting for initial data from %s (%s)", entry.title, entry.data[CONF_HOST], ) await asyncio.sleep(1) except asyncio.TimeoutError as exception: raise ConfigEntryNotReady( f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})." ) from exception _LOGGER.debug( "Initial coordinator data for %s (%s):\n%s", entry.title, entry.data[CONF_HOST], coordinator.data.json(), ) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) if hass.services.has_service(DOMAIN, SERVICE_OPEN_URL): return True def valid_device(device: str): """Check device is valid.""" device_registry = dr.async_get(hass) device_entry = device_registry.async_get(device) if device_entry is not None: try: return next( entry.entry_id for entry in hass.config_entries.async_entries(DOMAIN) if entry.entry_id in device_entry.config_entries) except StopIteration as exception: raise vol.Invalid from exception raise vol.Invalid(f"Device {device} does not exist") async def handle_open_path(call: ServiceCall) -> None: """Handle the open path service call.""" _LOGGER.info("Open: %s", call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ call.data[CONF_BRIDGE]] await coordinator.websocket_client.open_path( OpenPath(path=call.data[CONF_PATH])) async def handle_open_url(call: ServiceCall) -> None: """Handle the open url service call.""" _LOGGER.info("Open: %s", call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ call.data[CONF_BRIDGE]] await coordinator.websocket_client.open_url( OpenUrl(url=call.data[CONF_URL])) async def handle_send_keypress(call: ServiceCall) -> None: """Handle the send_keypress service call.""" coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ call.data[CONF_BRIDGE]] await coordinator.websocket_client.keyboard_keypress( KeyboardKey(key=call.data[CONF_KEY])) async def handle_send_text(call: ServiceCall) -> None: """Handle the send_keypress service call.""" coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ call.data[CONF_BRIDGE]] await coordinator.websocket_client.keyboard_text( KeyboardText(text=call.data[CONF_TEXT])) hass.services.async_register( DOMAIN, SERVICE_OPEN_PATH, handle_open_path, schema=vol.Schema( { vol.Required(CONF_BRIDGE): valid_device, vol.Required(CONF_PATH): cv.string, }, ), ) hass.services.async_register( DOMAIN, SERVICE_OPEN_URL, handle_open_url, schema=vol.Schema( { vol.Required(CONF_BRIDGE): valid_device, vol.Required(CONF_URL): cv.string, }, ), ) hass.services.async_register( DOMAIN, SERVICE_SEND_KEYPRESS, handle_send_keypress, schema=vol.Schema( { vol.Required(CONF_BRIDGE): valid_device, vol.Required(CONF_KEY): cv.string, }, ), ) hass.services.async_register( DOMAIN, SERVICE_SEND_TEXT, handle_send_text, schema=vol.Schema( { vol.Required(CONF_BRIDGE): valid_device, vol.Required(CONF_TEXT): cv.string, }, ), ) # Reload entry when its updated. entry.async_on_unload(entry.add_update_listener(async_reload_entry)) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Emporia Vue from a config entry.""" global device_gids global device_information device_gids = [] device_information = {} entry_data = entry.data email = entry_data[CONF_EMAIL] password = entry_data[CONF_PASSWORD] # _LOGGER.info(entry_data) vue = PyEmVue() loop = asyncio.get_event_loop() try: result = await loop.run_in_executor(None, vue.login, email, password) if not result: raise Exception("Could not authenticate with Emporia API") except Exception: _LOGGER.error("Could not authenticate with Emporia API") return False scales_1m = [] scales_1s = [] try: devices = await loop.run_in_executor(None, vue.get_devices) total_channels = 0 for d in devices: total_channels += len(d.channels) _LOGGER.warn( "Found {0} Emporia devices with {1} total channels".format( len(devices), total_channels ) ) for device in devices: if not device.device_gid in device_gids: device_gids.append(device.device_gid) await loop.run_in_executor(None, vue.populate_device_properties, device) device_information[device.device_gid] = device else: device_information[device.device_gid].channels += device.channels async def async_update_data_1min(): """Fetch data from API endpoint at a 1 minute interval This is the place to pre-process the data to lookup tables so entities can quickly look up their data. """ return await update_sensors(vue, scales_1m) async def async_update_data_1second(): """Fetch data from API endpoint at a 1 second interval This is the place to pre-process the data to lookup tables so entities can quickly look up their data. """ return await update_sensors(vue, scales_1s) if ENABLE_1M not in entry_data or entry_data[ENABLE_1M]: scales_1m.append(Scale.MINUTE.value) if ENABLE_1D not in entry_data or entry_data[ENABLE_1D]: scales_1m.append(Scale.DAY.value) if ENABLE_1MON not in entry_data or entry_data[ENABLE_1MON]: scales_1m.append(Scale.MONTH.value) coordinator_1min = None if scales_1m: coordinator_1min = DataUpdateCoordinator( hass, _LOGGER, # Name of the data. For logging purposes. name="sensor", update_method=async_update_data_1min, # Polling interval. Will only be polled if there are subscribers. update_interval=timedelta(seconds=60), ) await coordinator_1min.async_config_entry_first_refresh() _LOGGER.warn(f"1min Update data: {coordinator_1min.data}") coordinator_1s = None if ENABLE_1S in entry_data and entry_data[ENABLE_1S]: scales_1s.append(Scale.SECOND.value) coordinator_1s = DataUpdateCoordinator( hass, _LOGGER, # Name of the data. For logging purposes. name="sensor1s", update_method=async_update_data_1second, # Polling interval. Will only be polled if there are subscribers. update_interval=timedelta(seconds=1), ) await coordinator_1s.async_config_entry_first_refresh() _LOGGER.warn(f"1s Update data: {coordinator_1s.data}") except Exception as err: _LOGGER.warn(f"Exception while setting up Emporia Vue. Will retry. {err}") raise ConfigEntryNotReady( f"Exception while setting up Emporia Vue. Will retry. {err}" ) hass.data[DOMAIN][entry.entry_id] = { VUE_DATA: vue, "coordinator_1min": coordinator_1min, "coordinator_1s": coordinator_1s, } try: for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) except Exception as err: _LOGGER.warn(f"Error setting up platforms: {err}") raise ConfigEntryNotReady(f"Error setting up platforms: {err}") return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" if use_addon := entry.data.get(CONF_USE_ADDON): await async_ensure_addon_running(hass, entry) client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) # connect and throw error if connection failed try: async with timeout(CONNECT_TIMEOUT): await client.connect() except InvalidServerVersion as err: if use_addon: async_ensure_addon_updated(hass) raise ConfigEntryNotReady(f"Invalid server version: {err}") from err except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: raise ConfigEntryNotReady(f"Failed to connect: {err}") from err else: LOGGER.info("Connected to Zwave JS Server") dev_reg = device_registry.async_get(hass) ent_reg = entity_registry.async_get(hass) services = ZWaveServices(hass, ent_reg, dev_reg) services.async_register() # Set up websocket API async_register_api(hass) platform_task = hass.async_create_task(start_platforms(hass, entry, client)) hass.data[DOMAIN].setdefault(entry.entry_id, {})[
async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry) -> bool: """Set up the ISY 994 integration.""" # As there currently is no way to import options from yaml # when setting up a config entry, we fallback to adding # the options to the config entry and pull them out here if # they are missing from the options _async_import_options_from_data_if_missing(hass, entry) hass.data[DOMAIN][entry.entry_id] = {} hass_isy_data = hass.data[DOMAIN][entry.entry_id] hass_isy_data[ISY994_NODES] = {} for platform in PLATFORMS: hass_isy_data[ISY994_NODES][platform] = [] hass_isy_data[ISY994_PROGRAMS] = {} for platform in PROGRAM_PLATFORMS: hass_isy_data[ISY994_PROGRAMS][platform] = [] hass_isy_data[ISY994_VARIABLES] = [] isy_config = entry.data isy_options = entry.options # Required user = isy_config[CONF_USERNAME] password = isy_config[CONF_PASSWORD] host = urlparse(isy_config[CONF_HOST]) # Optional tls_version = isy_config.get(CONF_TLS_VER) ignore_identifier = isy_options.get(CONF_IGNORE_STRING, DEFAULT_IGNORE_STRING) sensor_identifier = isy_options.get(CONF_SENSOR_STRING, DEFAULT_SENSOR_STRING) variable_identifier = isy_options.get(CONF_VAR_SENSOR_STRING, DEFAULT_VAR_SENSOR_STRING) if host.scheme == "http": https = False port = host.port or 80 session = aiohttp_client.async_create_clientsession( hass, verify_ssl=None, cookie_jar=CookieJar(unsafe=True)) elif host.scheme == "https": https = True port = host.port or 443 session = aiohttp_client.async_get_clientsession(hass) else: _LOGGER.error("The isy994 host value in configuration is invalid") return False # Connect to ISY controller. isy = ISY( host.hostname, port, username=user, password=password, use_https=https, tls_ver=tls_version, webroot=host.path, websession=session, use_websocket=True, ) try: async with async_timeout.timeout(60): await isy.initialize() except asyncio.TimeoutError as err: raise ConfigEntryNotReady( f"Timed out initializing the ISY; device may be busy, trying again later: {err}" ) from err except ISYInvalidAuthError as err: _LOGGER.error( "Invalid credentials for the ISY, please adjust settings and try again: %s", err, ) return False except ISYConnectionError as err: raise ConfigEntryNotReady( f"Failed to connect to the ISY, please adjust settings and try again: {err}" ) from err except ISYResponseParseError as err: raise ConfigEntryNotReady( f"Invalid XML response from ISY; Ensure the ISY is running the latest firmware: {err}" ) from err _categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier) _categorize_programs(hass_isy_data, isy.programs) _categorize_variables(hass_isy_data, isy.variables, variable_identifier) # Dump ISY Clock Information. Future: Add ISY as sensor to Hass with attrs _LOGGER.info(repr(isy.clock)) hass_isy_data[ISY994_ISY] = isy await _async_get_or_create_isy_device_in_registry(hass, entry, isy) # Load platforms for the devices in the ISY controller that we support. hass.config_entries.async_setup_platforms(entry, PLATFORMS) @callback def _async_stop_auto_update(event) -> None: """Stop the isy auto update on Home Assistant Shutdown.""" _LOGGER.debug("ISY Stopping Event Stream and automatic updates") isy.websocket.stop() _LOGGER.debug("ISY Starting Event Stream and automatic updates") isy.websocket.start() entry.async_on_unload(entry.add_update_listener(_async_update_listener)) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_auto_update)) # Register Integration-wide Services: async_setup_services(hass) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tile as config entry.""" hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}, DATA_TILE: {}}) hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {} hass.data[DOMAIN][DATA_TILE][entry.entry_id] = {} @callback def async_migrate_callback(entity_entry: RegistryEntry) -> dict | None: """ Define a callback to migrate appropriate Tile entities to new unique IDs. Old: tile_{uuid} New: {username}_{uuid} """ if entity_entry.unique_id.startswith(entry.data[CONF_USERNAME]): return None new_unique_id = f"{entry.data[CONF_USERNAME]}_".join( entity_entry.unique_id.split(f"{DOMAIN}_")) LOGGER.debug( "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", entity_entry.entity_id, entity_entry.unique_id, new_unique_id, ) return {"new_unique_id": new_unique_id} await async_migrate_entries(hass, entry.entry_id, async_migrate_callback) # Tile's API uses cookies to identify a consumer; in order to allow for multiple # instances of this config entry, we use a new session each time: websession = aiohttp_client.async_create_clientsession(hass) try: client = await async_login( entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], session=websession, ) hass.data[DOMAIN][DATA_TILE][ entry.entry_id] = await client.async_get_tiles() except InvalidAuthError: LOGGER.error("Invalid credentials provided") return False except TileError as err: raise ConfigEntryNotReady("Error during integration setup") from err async def async_update_tile(tile: Tile) -> None: """Update the Tile.""" try: await tile.async_update() except SessionExpiredError: LOGGER.info("Tile session expired; creating a new one") await client.async_init() except TileError as err: raise UpdateFailed(f"Error while retrieving data: {err}") from err coordinator_init_tasks = [] for tile_uuid, tile in hass.data[DOMAIN][DATA_TILE][ entry.entry_id].items(): coordinator = hass.data[DOMAIN][DATA_COORDINATOR][ entry.entry_id][tile_uuid] = DataUpdateCoordinator( hass, LOGGER, name=tile.name, update_interval=DEFAULT_UPDATE_INTERVAL, update_method=partial(async_update_tile, tile), ) coordinator_init_tasks.append(coordinator.async_refresh()) await gather_with_concurrency(DEFAULT_INIT_TASK_LIMIT, *coordinator_init_tasks) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async_update_entry_from_discovery(hass, entry, discovery) device: AIOWifiLedBulb = async_wifi_bulb_for_host(host) signal = SIGNAL_STATE_UPDATED.format(device.ipaddr) @callback def _async_state_changed(*_: Any) -> None: _LOGGER.debug("%s: Device state updated: %s", device.ipaddr, device.raw_state) async_dispatcher_send(hass, signal) try: await device.async_setup(_async_state_changed) except FLUX_LED_EXCEPTIONS as ex: raise ConfigEntryNotReady( str(ex) or f"Timed out trying to connect to {device.ipaddr}") from ex coordinator = FluxLedUpdateCoordinator(hass, device) hass.data[DOMAIN][entry.entry_id] = coordinator hass.config_entries.async_setup_platforms( entry, PLATFORMS_BY_TYPE[device.device_type]) entry.async_on_unload(entry.add_update_listener(async_update_listener)) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" device: AIOWifiLedBulb = hass.data[DOMAIN][entry.entry_id].device platforms = PLATFORMS_BY_TYPE[device.device_type] if unload_ok := await hass.config_entries.async_unload_platforms(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Tuya platform.""" tuya = TuyaApi() username = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] country_code = entry.data[CONF_COUNTRYCODE] platform = entry.data[CONF_PLATFORM] try: await hass.async_add_executor_job( tuya.init, username, password, country_code, platform ) except ( TuyaNetException, TuyaServerException, TuyaFrequentlyInvokeException, ) as exc: raise ConfigEntryNotReady() from exc except TuyaAPIRateLimitException as exc: _LOGGER.error("Tuya login rate limited") raise ConfigEntryNotReady() from exc except TuyaAPIException as exc: _LOGGER.error( "Connection error during integration setup. Error: %s", exc, ) return False hass.data[DOMAIN] = { TUYA_DATA: tuya, TUYA_DEVICES_CONF: entry.options.copy(), TUYA_TRACKER: None, ENTRY_IS_SETUP: set(), "entities": {}, "pending": {}, "listener": entry.add_update_listener(update_listener), } _update_discovery_interval( hass, entry.options.get(CONF_DISCOVERY_INTERVAL, DEFAULT_DISCOVERY_INTERVAL) ) _update_query_interval( hass, entry.options.get(CONF_QUERY_INTERVAL, DEFAULT_QUERY_INTERVAL) ) async def async_load_devices(device_list): """Load new devices by device_list.""" device_type_list = {} for device in device_list: dev_type = device.device_type() if ( dev_type in TUYA_TYPE_TO_HA and device.object_id() not in hass.data[DOMAIN]["entities"] ): ha_type = TUYA_TYPE_TO_HA[dev_type] if ha_type not in device_type_list: device_type_list[ha_type] = [] device_type_list[ha_type].append(device.object_id()) hass.data[DOMAIN]["entities"][device.object_id()] = None for ha_type, dev_ids in device_type_list.items(): config_entries_key = f"{ha_type}.tuya" if config_entries_key not in hass.data[DOMAIN][ENTRY_IS_SETUP]: hass.data[DOMAIN]["pending"][ha_type] = dev_ids hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, ha_type) ) hass.data[DOMAIN][ENTRY_IS_SETUP].add(config_entries_key) else: async_dispatcher_send(hass, TUYA_DISCOVERY_NEW.format(ha_type), dev_ids) await async_load_devices(tuya.get_all_devices()) def _get_updated_devices(): try: tuya.poll_devices_update() except TuyaFrequentlyInvokeException as exc: _LOGGER.error(exc) return tuya.get_all_devices() async def async_poll_devices_update(event_time): """Check if accesstoken is expired and pull device list from server.""" _LOGGER.debug("Pull devices from Tuya") # Add new discover device. device_list = await hass.async_add_executor_job(_get_updated_devices) await async_load_devices(device_list) # Delete not exist device. newlist_ids = [] for device in device_list: newlist_ids.append(device.object_id()) for dev_id in list(hass.data[DOMAIN]["entities"]): if dev_id not in newlist_ids: async_dispatcher_send(hass, SIGNAL_DELETE_ENTITY, dev_id) hass.data[DOMAIN]["entities"].pop(dev_id) hass.data[DOMAIN][TUYA_TRACKER] = async_track_time_interval( hass, async_poll_devices_update, timedelta(minutes=2) ) hass.services.async_register( DOMAIN, SERVICE_PULL_DEVICES, async_poll_devices_update ) async def async_force_update(call): """Force all devices to pull data.""" async_dispatcher_send(hass, SIGNAL_UPDATE_ENTITY) hass.services.async_register(DOMAIN, SERVICE_FORCE_UPDATE, async_force_update) return True
async def async_setup_entry(hass: HomeAssistantType, config_entry: config_entries.ConfigEntry): username = config_entry.data[CONF_USERNAME] # Check if leftovers from previous setup are present if config_entry.entry_id in hass.data.get(DATA_FINAL_CONFIG, {}): raise ConfigEntryNotReady( 'Configuration entry with username "%s" already set up' % (username, )) # Source full configuration if config_entry.source == config_entries.SOURCE_IMPORT: # Source configuration from YAML yaml_config = hass.data.get(DATA_YAML_CONFIG) if not yaml_config or username not in yaml_config: _LOGGER.info( 'Removing entry %s after removal from YAML configuration.' % config_entry.entry_id) hass.async_create_task( hass.config_entries.async_remove(config_entry.entry_id)) return False user_cfg = yaml_config[username] else: # Source and convert configuration from input data all_cfg = {**config_entry.data} if config_entry.options: all_cfg.update(config_entry.options) user_cfg = CONFIG_ENTRY_SCHEMA(all_cfg) _LOGGER.info('Setting up config entry for user "%s"' % username) from custom_components.mosenergosbyt.api import API, MosenergosbytException try: api_object = API(username=username, password=user_cfg[CONF_PASSWORD], user_agent=user_cfg.get(CONF_USER_AGENT)) await api_object.login() # Fetch all accounts accounts, unsupported_accounts = \ await api_object.get_accounts(return_unsupported_accounts=True) # Filter accounts if CONF_FILTER in user_cfg: account_filter = user_cfg[CONF_FILTER] accounts = [ account for account in accounts if account_filter[account.account_code] ] except MosenergosbytException as e: _LOGGER.error('Error authenticating with user "%s": %s' % (username, str(e))) return False if unsupported_accounts: async_handle_unsupported_accounts(hass, username, unsupported_accounts) if not accounts: # Cancel setup because no accounts provided _LOGGER.warning('No supported accounts found under username "%s"', username) return False entry_id = config_entry.entry_id # Create data placeholders hass.data.setdefault(DATA_API_OBJECTS, {})[entry_id] = api_object hass.data.setdefault(DATA_ENTITIES, {})[entry_id] = {} hass.data.setdefault(DATA_UPDATERS, {})[entry_id] = {} # Save final configuration data hass.data.setdefault(DATA_FINAL_CONFIG, {})[entry_id] = user_cfg # Forward entry setup to sensor platform hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, SENSOR_DOMAIN)) hass.data.setdefault(DATA_UPDATE_LISTENERS, {})[entry_id] = \ config_entry.add_update_listener(async_reload_entry) _LOGGER.debug('Successfully set up user "%s"' % username) return True