Exemple #1
0
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
    """Set up Point from a config entry."""
    from pypoint import PointSession

    def token_saver(token):
        _LOGGER.debug('Saving updated token')
        entry.data[CONF_TOKEN] = token
        hass.config_entries.async_update_entry(entry, data={**entry.data})

    # Force token update.
    entry.data[CONF_TOKEN]['expires_in'] = -1
    session = PointSession(
        entry.data['refresh_args']['client_id'],
        token=entry.data[CONF_TOKEN],
        auto_refresh_kwargs=entry.data['refresh_args'],
        token_saver=token_saver,
    )

    if not session.is_authorized:
        _LOGGER.error('Authentication Error')
        return False

    await async_setup_webhook(hass, entry, session)
    client = MinutPointClient(hass, entry, session)
    hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: client})
    await client.update()

    return True
Exemple #2
0
async def async_setup_webhook(hass: HomeAssistantType, entry: ConfigEntry,
                              session):
    """Set up a webhook to handle binary sensor events."""
    if CONF_WEBHOOK_ID not in entry.data:
        entry.data[CONF_WEBHOOK_ID] = \
            hass.components.webhook.async_generate_id()
        entry.data[CONF_WEBHOOK_URL] = \
            hass.components.webhook.async_generate_url(
                entry.data[CONF_WEBHOOK_ID])
        _LOGGER.info('Registering new webhook at: %s',
                     entry.data[CONF_WEBHOOK_URL])
        hass.config_entries.async_update_entry(
            entry, data={
                **entry.data,
            })
    session.update_webhook(entry.data[CONF_WEBHOOK_URL],
                           entry.data[CONF_WEBHOOK_ID])

    hass.components.webhook.async_register(
        DOMAIN, 'Point', entry.data[CONF_WEBHOOK_ID], handle_webhook)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    entry.async_on_unload(entry.add_update_listener(update_listener))
    return True
Exemple #4
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up the AVM FRITZ!SmartHome platforms."""
    fritz = Fritzhome(
        host=entry.data[CONF_HOST],
        user=entry.data[CONF_USERNAME],
        password=entry.data[CONF_PASSWORD],
    )

    try:
        await hass.async_add_executor_job(fritz.login)
    except LoginError as err:
        raise ConfigEntryAuthFailed from err

    hass.data.setdefault(DOMAIN, {})
    hass.data[DOMAIN][entry.entry_id] = {
        CONF_CONNECTIONS: fritz,
    }

    def _update_fritz_devices() -> dict[str, FritzhomeDevice]:
        """Update all fritzbox device data."""
        try:
            devices = fritz.get_devices()
        except requests.exceptions.HTTPError:
            # If the device rebooted, login again
            try:
                fritz.login()
            except requests.exceptions.HTTPError as ex:
                raise ConfigEntryAuthFailed from ex
            devices = fritz.get_devices()

        data = {}
        for device in devices:
            device.update()
            data[device.ain] = device
        return data

    async def async_update_coordinator() -> dict[str, FritzhomeDevice]:
        """Fetch all device data."""
        return await hass.async_add_executor_job(_update_fritz_devices)

    hass.data[DOMAIN][entry.entry_id][
        CONF_COORDINATOR
    ] = coordinator = DataUpdateCoordinator(
        hass,
        LOGGER,
        name=f"{entry.entry_id}",
        update_method=async_update_coordinator,
        update_interval=timedelta(seconds=30),
    )

    await coordinator.async_config_entry_first_refresh()

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    def logout_fritzbox(event: Event) -> None:
        """Close connections to this fritzbox."""
        fritz.logout()

    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, logout_fritzbox)
    )

    return True
Exemple #5
0
async def async_setup_gateway_entry(hass: HomeAssistant,
                                    entry: ConfigEntry) -> None:
    """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

    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_info.model,
        sw_version=gateway_info.firmware_version,
        hw_version=gateway_info.hardware_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:
    """Perform Volkswagen WeConnect component setup."""
    def register_services():
        cs = ChargerService(hass)
        ss = SchedulerService(hass)
        hass.services.async_register(
            domain=DOMAIN,
            service=SERVICE_SET_TIMER_BASIC_SETTINGS,
            service_func=ss.set_timer_basic_settings,
            schema=SERVICE_SET_TIMER_BASIC_SETTINGS_SCHEMA,
        )
        hass.services.async_register(
            domain=DOMAIN,
            service=SERVICE_UPDATE_SCHEDULE,
            service_func=ss.update_schedule,
            schema=SERVICE_UPDATE_SCHEDULE_SCHEMA,
        )
        hass.services.async_register(
            domain=DOMAIN,
            service=SERVICE_UPDATE_PROFILE,
            service_func=ss.update_profile,
            schema=SERVICE_UPDATE_PROFILE_SCHEMA,
        )
        hass.services.async_register(
            domain=DOMAIN,
            service=SERVICE_SET_CHARGER_MAX_CURRENT,
            service_func=cs.set_charger_max_current,
            schema=SERVICE_SET_CHARGER_MAX_CURRENT_SCHEMA,
        )

    if entry.options.get(CONF_SCAN_INTERVAL):
        update_interval = timedelta(minutes=entry.options[CONF_SCAN_INTERVAL])
    else:
        update_interval = timedelta(minutes=DEFAULT_UPDATE_INTERVAL)

    coordinator = VolkswagenCoordinator(hass, entry, update_interval)

    coordinator.async_add_listener(coordinator.async_update_listener)

    if not await coordinator.async_login():
        await hass.config_entries.flow.async_init(
            DOMAIN,
            context={"source": SOURCE_REAUTH},
            data=entry,
        )
        return False

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
                               coordinator.async_logout)

    # First refresh, with retry on errors
    await coordinator.async_config_entry_first_refresh()

    data: VolkswagenData = VolkswagenData(entry.data, coordinator)
    instruments = coordinator.data

    def is_enabled(attr):
        """Return true if the user has enabled the resource."""
        return attr in entry.options.get(CONF_RESOURCES, [attr])

    def is_new(attr):
        """Return true if the resource is new."""
        return attr not in entry.options.get(CONF_AVAILABLE_RESOURCES, [attr])

    components = set()
    for instrument in (instrument for instrument in instruments
                       if instrument.component in COMPONENTS):
        # Add resource if it's enabled or new
        if is_enabled(instrument.slug_attr) or (is_new(
                instrument.slug_attr) and not entry.pref_disable_new_entities):
            data.instruments.add(instrument)
            components.add(COMPONENTS[instrument.component])

    for component in components:
        coordinator.platforms.append(component)
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(entry, component))

    hass.data[DOMAIN][entry.entry_id] = {
        UPDATE_CALLBACK: update_callback,
        DATA: data,
        UNDO_UPDATE_LISTENER:
        entry.add_update_listener(_async_update_listener),
    }

    register_services()

    return True
async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up Sonos from a config entry."""
    platform = entity_platform.async_get_current_platform()

    @callback
    def async_create_entities(speaker: SonosSpeaker) -> None:
        """Handle device discovery and create entities."""
        async_add_entities([SonosMediaPlayerEntity(speaker)])

    @service.verify_domain_control(hass, SONOS_DOMAIN)
    async def async_service_handle(service_call: ServiceCall) -> None:
        """Handle dispatched services."""
        assert platform is not None
        entities = await platform.async_extract_from_service(service_call)

        if not entities:
            return

        speakers = []
        for entity in entities:
            assert isinstance(entity, SonosMediaPlayerEntity)
            speakers.append(entity.speaker)

        if service_call.service == SERVICE_JOIN:
            master = platform.entities.get(service_call.data[ATTR_MASTER])
            if master:
                await SonosSpeaker.join_multi(hass, master.speaker, speakers)  # type: ignore[arg-type]
            else:
                _LOGGER.error(
                    "Invalid master specified for join service: %s",
                    service_call.data[ATTR_MASTER],
                )
        elif service_call.service == SERVICE_UNJOIN:
            await SonosSpeaker.unjoin_multi(hass, speakers)  # type: ignore[arg-type]
        elif service_call.service == SERVICE_SNAPSHOT:
            await SonosSpeaker.snapshot_multi(
                hass, speakers, service_call.data[ATTR_WITH_GROUP]  # type: ignore[arg-type]
            )
        elif service_call.service == SERVICE_RESTORE:
            await SonosSpeaker.restore_multi(
                hass, speakers, service_call.data[ATTR_WITH_GROUP]  # type: ignore[arg-type]
            )

    config_entry.async_on_unload(
        async_dispatcher_connect(hass, SONOS_CREATE_MEDIA_PLAYER, async_create_entities)
    )

    hass.services.async_register(
        SONOS_DOMAIN,
        SERVICE_JOIN,
        async_service_handle,
        cv.make_entity_service_schema({vol.Required(ATTR_MASTER): cv.entity_id}),
    )

    hass.services.async_register(
        SONOS_DOMAIN,
        SERVICE_UNJOIN,
        async_service_handle,
        cv.make_entity_service_schema({}),
    )

    join_unjoin_schema = cv.make_entity_service_schema(
        {vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean}
    )

    hass.services.async_register(
        SONOS_DOMAIN, SERVICE_SNAPSHOT, async_service_handle, join_unjoin_schema
    )

    hass.services.async_register(
        SONOS_DOMAIN, SERVICE_RESTORE, async_service_handle, join_unjoin_schema
    )

    platform.async_register_entity_service(  # type: ignore
        SERVICE_SET_TIMER,
        {
            vol.Required(ATTR_SLEEP_TIME): vol.All(
                vol.Coerce(int), vol.Range(min=0, max=86399)
            )
        },
        "set_sleep_timer",
    )

    platform.async_register_entity_service(SERVICE_CLEAR_TIMER, {}, "clear_sleep_timer")  # type: ignore

    platform.async_register_entity_service(  # type: ignore
        SERVICE_UPDATE_ALARM,
        {
            vol.Required(ATTR_ALARM_ID): cv.positive_int,
            vol.Optional(ATTR_TIME): cv.time,
            vol.Optional(ATTR_VOLUME): cv.small_float,
            vol.Optional(ATTR_ENABLED): cv.boolean,
            vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean,
        },
        "set_alarm",
    )

    platform.async_register_entity_service(  # type: ignore
        SERVICE_SET_OPTION,
        {
            vol.Optional(ATTR_EQ_BASS): vol.All(
                vol.Coerce(int), vol.Range(min=-10, max=10)
            ),
            vol.Optional(ATTR_EQ_TREBLE): vol.All(
                vol.Coerce(int), vol.Range(min=-10, max=10)
            ),
        },
        "set_option",
    )

    platform.async_register_entity_service(  # type: ignore
        SERVICE_PLAY_QUEUE,
        {vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
        "play_queue",
    )

    platform.async_register_entity_service(  # type: ignore
        SERVICE_REMOVE_FROM_QUEUE,
        {vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
        "remove_from_queue",
    )
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Control4 from a config entry."""
    hass.data.setdefault(DOMAIN, {})
    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)

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
Exemple #9
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry,
                            async_add_entities: AddEntitiesCallback) -> None:
    """Set up device tracker for Netgear component."""
    router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER]
    coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR]
    coordinator_traffic = hass.data[DOMAIN][
        entry.entry_id][KEY_COORDINATOR_TRAFFIC]
    coordinator_speed = hass.data[DOMAIN][
        entry.entry_id][KEY_COORDINATOR_SPEED]
    coordinator_utilization = hass.data[DOMAIN][
        entry.entry_id][KEY_COORDINATOR_UTIL]
    coordinator_link = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_LINK]

    # Router entities
    router_entities = []

    for description in SENSOR_TRAFFIC_TYPES:
        router_entities.append(
            NetgearRouterSensorEntity(coordinator_traffic, router,
                                      description))

    for description in SENSOR_SPEED_TYPES:
        router_entities.append(
            NetgearRouterSensorEntity(coordinator_speed, router, description))

    for description in SENSOR_UTILIZATION:
        router_entities.append(
            NetgearRouterSensorEntity(coordinator_utilization, router,
                                      description))

    for description in SENSOR_LINK_TYPES:
        router_entities.append(
            NetgearRouterSensorEntity(coordinator_link, router, description))

    async_add_entities(router_entities)

    # Entities per network device
    tracked = set()
    sensors = ["type", "link_rate", "signal"]
    if router.method_version == 2:
        sensors.extend(["ssid", "conn_ap_mac"])

    @callback
    def new_device_callback() -> None:
        """Add new devices if needed."""
        if not coordinator.data:
            return

        new_entities = []

        for mac, device in router.devices.items():
            if mac in tracked:
                continue

            new_entities.extend([
                NetgearSensorEntity(coordinator, router, device, attribute)
                for attribute in sensors
            ])
            tracked.add(mac)

        if new_entities:
            async_add_entities(new_entities)

    entry.async_on_unload(coordinator.async_add_listener(new_device_callback))

    coordinator.data = True
    new_device_callback()
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

    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_send_command(call):
        """Handle the send_command service call."""
        coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][
            call.data[CONF_BRIDGE]]
        bridge: Bridge = coordinator.bridge

        command = call.data[CONF_COMMAND]
        arguments = shlex.split(call.data[CONF_ARGUMENTS])

        _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 not response.success:
                raise HomeAssistantError(
                    f"Error sending command. Response message was: {response.message}"
                )
        except (BridgeAuthenticationException,
                *BRIDGE_CONNECTION_ERRORS) as exception:
            raise HomeAssistantError("Error sending command") from exception
        _LOGGER.debug("Sent command. Response message was: %s",
                      response.message)

    async def handle_open(call):
        """Handle the open service call."""
        coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][
            call.data[CONF_BRIDGE]]
        bridge: Bridge = coordinator.bridge

        path = call.data[CONF_PATH]

        _LOGGER.debug("Open payload: %s", {CONF_PATH: path})
        try:
            await bridge.async_open({CONF_PATH: path})
        except (BridgeAuthenticationException,
                *BRIDGE_CONNECTION_ERRORS) as exception:
            raise HomeAssistantError("Error sending") from exception
        _LOGGER.debug("Sent open request")

    async def handle_send_keypress(call):
        """Handle the send_keypress service call."""
        coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][
            call.data[CONF_BRIDGE]]
        bridge: Bridge = coordinator.data

        keyboard_payload: KeyboardPayload = {
            CONF_KEY: call.data[CONF_KEY],
            CONF_MODIFIERS: shlex.split(call.data.get(CONF_MODIFIERS, "")),
        }

        _LOGGER.debug("Keypress payload: %s", keyboard_payload)
        try:
            await bridge.async_send_keypress(keyboard_payload)
        except (BridgeAuthenticationException,
                *BRIDGE_CONNECTION_ERRORS) as exception:
            raise HomeAssistantError("Error sending") from exception
        _LOGGER.debug("Sent keypress request")

    async def handle_send_text(call):
        """Handle the send_keypress service call."""
        coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][
            call.data[CONF_BRIDGE]]
        bridge: Bridge = coordinator.data

        keyboard_payload: KeyboardPayload = {CONF_TEXT: call.data[CONF_TEXT]}

        _LOGGER.debug("Text payload: %s", keyboard_payload)
        try:
            await bridge.async_send_keypress(keyboard_payload)
        except (BridgeAuthenticationException,
                *BRIDGE_CONNECTION_ERRORS) as exception:
            raise HomeAssistantError("Error sending") from exception
        _LOGGER.debug("Sent text request")

    hass.services.async_register(
        DOMAIN,
        SERVICE_SEND_COMMAND,
        handle_send_command,
        schema=vol.Schema(
            {
                vol.Required(CONF_BRIDGE): valid_device,
                vol.Required(CONF_COMMAND): cv.string,
                vol.Optional(CONF_ARGUMENTS, ""): cv.string,
            }, ),
    )

    hass.services.async_register(
        DOMAIN,
        SERVICE_OPEN,
        handle_open,
        schema=vol.Schema(
            {
                vol.Required(CONF_BRIDGE): valid_device,
                vol.Required(CONF_PATH): 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,
                vol.Optional(CONF_MODIFIERS): 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 the Tapo: Cameras Control component from a config entry."""
    hass.data.setdefault(DOMAIN, {})

    host = entry.data.get(CONF_IP_ADDRESS)
    username = entry.data.get(CONF_USERNAME)
    password = entry.data.get(CONF_PASSWORD)
    motionSensor = entry.data.get(ENABLE_MOTION_SENSOR)
    cloud_password = entry.data.get(CLOUD_PASSWORD)
    enableTimeSync = entry.data.get(ENABLE_TIME_SYNC)

    try:
        if cloud_password != "":
            tapoController = await hass.async_add_executor_job(
                registerController, host, "admin", cloud_password)
        else:
            tapoController = await hass.async_add_executor_job(
                registerController, host, username, password)

        async def async_update_data():
            LOGGER.debug("async_update_data - entry")
            host = entry.data.get(CONF_IP_ADDRESS)
            username = entry.data.get(CONF_USERNAME)
            password = entry.data.get(CONF_PASSWORD)
            motionSensor = entry.data.get(ENABLE_MOTION_SENSOR)
            enableTimeSync = entry.data.get(ENABLE_TIME_SYNC)

            # motion detection retries
            if motionSensor or enableTimeSync:
                LOGGER.debug("Motion sensor or time sync is enabled.")
                if (not hass.data[DOMAIN][entry.entry_id]["eventsDevice"]
                        or not hass.data[DOMAIN][
                            entry.entry_id]["onvifManagement"]):
                    LOGGER.debug("Setting up subscription to motion sensor...")
                    # retry if connection to onvif failed
                    LOGGER.debug("Initiating onvif.")
                    onvifDevice = await initOnvifEvents(
                        hass, host, username, password)
                    LOGGER.debug(onvifDevice)
                    hass.data[DOMAIN][
                        entry.entry_id]["eventsDevice"] = onvifDevice["device"]
                    hass.data[DOMAIN][entry.entry_id][
                        "onvifManagement"] = onvifDevice["device_mgmt"]
                    if motionSensor:
                        await setupOnvif(hass, entry)
                elif (not hass.data[DOMAIN][entry.entry_id]["eventsSetup"]
                      and motionSensor):
                    LOGGER.debug(
                        "Setting up subcription to motion sensor events...")
                    # retry if subscription to events failed
                    hass.data[DOMAIN][
                        entry.entry_id]["eventsSetup"] = await setupEvents(
                            hass, entry)
                else:
                    LOGGER.debug("Motion sensor: OK")

                if (hass.data[DOMAIN][entry.entry_id]["onvifManagement"]
                        and enableTimeSync):
                    ts = datetime.datetime.utcnow().timestamp()
                    if (ts - hass.data[DOMAIN][entry.entry_id]["lastTimeSync"]
                            > TIME_SYNC_PERIOD):
                        await syncTime(hass, entry)

            # cameras state
            someCameraEnabled = False
            for entity in hass.data[DOMAIN][entry.entry_id]["entities"]:
                if entity._enabled:
                    someCameraEnabled = True

            if someCameraEnabled:
                try:
                    camData = await getCamData(hass, tapoController)
                except Exception as e:
                    camData = False
                    LOGGER.error(e)
                hass.data[DOMAIN][entry.entry_id]["camData"] = camData
                for entity in hass.data[DOMAIN][entry.entry_id]["entities"]:
                    if entity._enabled:
                        entity.updateCam(camData)
                        entity.async_schedule_update_ha_state(True)
                        if (not hass.data[DOMAIN][
                                entry.entry_id]["noiseSensorStarted"]
                                and entity._enable_sound_detection):
                            await entity.startNoiseDetection()

        tapoCoordinator = DataUpdateCoordinator(
            hass,
            LOGGER,
            name="Tapo resource status",
            update_method=async_update_data,
        )

        camData = await getCamData(hass, tapoController)

        hass.data[DOMAIN][entry.entry_id] = {
            "controller": tapoController,
            "update_listener": entry.add_update_listener(update_listener),
            "coordinator": tapoCoordinator,
            "camData": camData,
            "lastTimeSync": 0,
            "motionSensorCreated": False,
            "eventsDevice": False,
            "onvifManagement": False,
            "eventsSetup": False,
            "events": False,
            "name": camData["basic_info"]["device_alias"],
        }
        if motionSensor or enableTimeSync:
            onvifDevice = await initOnvifEvents(hass, host, username, password)
            hass.data[DOMAIN][
                entry.entry_id]["eventsDevice"] = onvifDevice["device"]
            hass.data[DOMAIN][
                entry.entry_id]["onvifManagement"] = onvifDevice["device_mgmt"]
            if motionSensor:
                LOGGER.debug("Seting up motion sensor for the first time.")
                await setupOnvif(hass, entry)
            if enableTimeSync:
                await syncTime(hass, entry)

        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(entry, "camera"))

        async def unsubscribe(event):
            if hass.data[DOMAIN][entry.entry_id]["events"]:
                await hass.data[DOMAIN][entry.entry_id]["events"].async_stop()

        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unsubscribe)

    except Exception as e:
        LOGGER.error(
            "Unable to connect to Tapo: Cameras Control controller: %s",
            str(e))
        raise ConfigEntryNotReady

    return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up RainMachine as config entry."""
    hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {}

    entry_updates = {}
    if not entry.unique_id:
        # If the config entry doesn't already have a unique ID, set one:
        entry_updates["unique_id"] = entry.data[CONF_IP_ADDRESS]
    if CONF_ZONE_RUN_TIME in entry.data:
        # If a zone run time exists in the config entry's data, pop it and move it to
        # options:
        data = {**entry.data}
        entry_updates["data"] = data
        entry_updates["options"] = {
            **entry.options,
            CONF_ZONE_RUN_TIME: data.pop(CONF_ZONE_RUN_TIME),
        }
    if entry_updates:
        hass.config_entries.async_update_entry(entry, **entry_updates)

    websession = aiohttp_client.async_get_clientsession(hass)
    client = Client(session=websession)

    try:
        await client.load_local(
            entry.data[CONF_IP_ADDRESS],
            entry.data[CONF_PASSWORD],
            port=entry.data[CONF_PORT],
            ssl=entry.data.get(CONF_SSL, DEFAULT_SSL),
        )
    except RainMachineError as err:
        LOGGER.error("An error occurred: %s", err)
        raise ConfigEntryNotReady from err

    # regenmaschine can load multiple controllers at once, but we only grab the one
    # we loaded above:
    controller = hass.data[DOMAIN][DATA_CONTROLLER][entry.entry_id] = next(
        iter(client.controllers.values()))

    async def async_update(api_category: str) -> dict:
        """Update the appropriate API data based on a category."""
        try:
            if api_category == DATA_PROGRAMS:
                return await controller.programs.all(include_inactive=True)

            if api_category == DATA_PROVISION_SETTINGS:
                return await controller.provisioning.settings()

            if api_category == DATA_RESTRICTIONS_CURRENT:
                return await controller.restrictions.current()

            if api_category == DATA_RESTRICTIONS_UNIVERSAL:
                return await controller.restrictions.universal()

            return await controller.zones.all(details=True,
                                              include_inactive=True)
        except RainMachineError as err:
            raise UpdateFailed(err) from err

    controller_init_tasks = []
    for api_category in [
            DATA_PROGRAMS,
            DATA_PROVISION_SETTINGS,
            DATA_RESTRICTIONS_CURRENT,
            DATA_RESTRICTIONS_UNIVERSAL,
            DATA_ZONES,
    ]:
        coordinator = hass.data[DOMAIN][DATA_COORDINATOR][
            entry.entry_id][api_category] = DataUpdateCoordinator(
                hass,
                LOGGER,
                name=f'{controller.name} ("{api_category}")',
                update_interval=DEFAULT_UPDATE_INTERVAL,
                update_method=partial(async_update, api_category),
            )
        controller_init_tasks.append(coordinator.async_refresh())

    await asyncio.gather(*controller_init_tasks)

    for component in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(entry, component))

    hass.data[DOMAIN][DATA_LISTENER] = entry.add_update_listener(
        async_reload_entry)

    return True
Exemple #13
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
    """Set up TDAmeritrade from a config entry."""
    _LOGGER.debug("Setting up entry")
    config_flow.OAuth2FlowHandler.async_register_implementation(
        hass,
        config_entry_oauth2_flow.LocalOAuth2Implementation(
            hass,
            entry.domain,
            entry.data["consumer_key"],
            None,
            OAUTH2_AUTHORIZE,
            OAUTH2_TOKEN,
        ),
    )

    implementation = (
        await config_entry_oauth2_flow.async_get_config_entry_implementation(
            hass, entry))

    session = config_entry_oauth2_flow.OAuth2Session(hass, entry,
                                                     implementation)

    auth = api.AsyncConfigEntryAuth(
        aiohttp_client.async_get_clientsession(hass), session)

    client = AmeritradeAPI(auth)

    async def place_order_service(call):
        """Handle a place trade service call."""

        price = call.data["price"]
        instruction = call.data["instruction"]
        quantity = call.data["quantity"]
        symbol = call.data["symbol"]
        account_id = call.data["account_id"]
        order_type = call.data["order_type"]
        session = call.data["session"]
        duration = call.data["duration"]
        order_strategy_type = call.data["orderStrategyType"]
        asset_type = call.data["assetType"]

        return await client.async_place_order(
            price,
            instruction,
            quantity,
            symbol,
            account_id,
            order_type=order_type,
            session=session,
            duration=duration,
            orderStrategyType=order_strategy_type,
            assetType=asset_type,
        )

    async def get_quote_service(call):
        """Handle a place trade service call."""
        symbol = call.data["symbol"]
        res = await client.async_get_quote(ticker=symbol)

        hass.states.async_set(
            f"get_quote_service.{symbol}",
            res[symbol]["lastPrice"],
            attributes=res[symbol],
        )

        return True

    _LOGGER.debug("Registering Services")
    hass.services.async_register(DOMAIN, "place_order", place_order_service)
    hass.services.async_register(DOMAIN, "get_quote", get_quote_service)

    hass.data.setdefault(DOMAIN, {})
    hass_data = dict(entry.data)
    entry.update_listeners = []
    hass_data["unsub"] = entry.add_update_listener(options_update_listener)
    hass_data["client"] = AmeritradeAPI(auth)
    hass.data[DOMAIN][entry.entry_id] = hass_data
    if entry.state == "not_loaded":
        for component in PLATFORMS:
            _LOGGER.debug("Setting up %s component", component)
            hass.async_create_task(
                hass.config_entries.async_forward_entry_setup(
                    entry, component))
    return True
Exemple #14
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Yeelight from a config entry."""
    async def _initialize(host: str, capabilities: dict | None = None) -> None:
        remove_dispatcher = async_dispatcher_connect(
            hass,
            DEVICE_INITIALIZED.format(host),
            _load_platforms,
        )
        hass.data[DOMAIN][DATA_CONFIG_ENTRIES][
            entry.entry_id][DATA_REMOVE_INIT_DISPATCHER] = remove_dispatcher

        device = await _async_get_device(hass, host, entry, capabilities)
        hass.data[DOMAIN][DATA_CONFIG_ENTRIES][
            entry.entry_id][DATA_DEVICE] = device

        await device.async_setup()

    async def _load_platforms():

        for platform in PLATFORMS:
            hass.async_create_task(
                hass.config_entries.async_forward_entry_setup(entry, platform))

    # Move options from data for imported entries
    # Initialize options with default values for other entries
    if not entry.options:
        hass.config_entries.async_update_entry(
            entry,
            data={
                CONF_HOST: entry.data.get(CONF_HOST),
                CONF_ID: entry.data.get(CONF_ID),
            },
            options={
                CONF_NAME:
                entry.data.get(CONF_NAME, ""),
                CONF_MODEL:
                entry.data.get(CONF_MODEL, ""),
                CONF_TRANSITION:
                entry.data.get(CONF_TRANSITION, DEFAULT_TRANSITION),
                CONF_MODE_MUSIC:
                entry.data.get(CONF_MODE_MUSIC, DEFAULT_MODE_MUSIC),
                CONF_SAVE_ON_CHANGE:
                entry.data.get(CONF_SAVE_ON_CHANGE, DEFAULT_SAVE_ON_CHANGE),
                CONF_NIGHTLIGHT_SWITCH:
                entry.data.get(CONF_NIGHTLIGHT_SWITCH,
                               DEFAULT_NIGHTLIGHT_SWITCH),
            },
        )

    hass.data[DOMAIN][DATA_CONFIG_ENTRIES][entry.entry_id] = {
        DATA_UNSUB_UPDATE_LISTENER:
        entry.add_update_listener(_async_update_listener)
    }

    if entry.data.get(CONF_HOST):
        # manually added device
        await _initialize(entry.data[CONF_HOST])
    else:
        # discovery
        scanner = YeelightScanner.async_get(hass)
        scanner.async_register_callback(entry.data[CONF_ID], _initialize)

    return True
Exemple #15
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Netgear component."""
    router = NetgearRouter(hass, entry)
    try:
        if not await router.async_setup():
            raise ConfigEntryNotReady
    except CannotLoginException as ex:
        raise ConfigEntryNotReady from ex

    port = entry.data.get(CONF_PORT)
    ssl = entry.data.get(CONF_SSL)
    if port != router.port or ssl != router.ssl:
        data = {**entry.data, CONF_PORT: router.port, CONF_SSL: router.ssl}
        hass.config_entries.async_update_entry(entry, data=data)
        _LOGGER.info(
            "Netgear port-SSL combination updated from (%i, %r) to (%i, %r), "
            "this should only occur after a firmware update",
            port,
            ssl,
            router.port,
            router.ssl,
        )

    hass.data.setdefault(DOMAIN, {})

    entry.async_on_unload(entry.add_update_listener(update_listener))

    assert entry.unique_id
    device_registry = dr.async_get(hass)
    device_registry.async_get_or_create(
        config_entry_id=entry.entry_id,
        identifiers={(DOMAIN, entry.unique_id)},
        manufacturer="Netgear",
        name=router.device_name,
        model=router.model,
        sw_version=router.firmware_version,
        hw_version=router.hardware_version,
        configuration_url=f"http://{entry.data[CONF_HOST]}/",
    )

    async def async_update_devices() -> bool:
        """Fetch data from the router."""
        if router.mode == MODE_ROUTER:
            return await router.async_update_device_trackers()
        return False

    async def async_update_traffic_meter() -> dict[str, Any] | None:
        """Fetch data from the router."""
        return await router.async_get_traffic_meter()

    # Create update coordinators
    coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=f"{router.device_name} Devices",
        update_method=async_update_devices,
        update_interval=SCAN_INTERVAL,
    )
    coordinator_traffic_meter = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=f"{router.device_name} Traffic meter",
        update_method=async_update_traffic_meter,
        update_interval=SCAN_INTERVAL,
    )

    if router.mode == MODE_ROUTER:
        await coordinator.async_config_entry_first_refresh()
    await coordinator_traffic_meter.async_config_entry_first_refresh()

    hass.data[DOMAIN][entry.entry_id] = {
        KEY_ROUTER: router,
        KEY_COORDINATOR: coordinator,
        KEY_COORDINATOR_TRAFFIC: coordinator_traffic_meter,
    }

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
Exemple #16
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up RainMachine as config entry."""
    hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {}

    entry_updates = {}
    if not entry.unique_id:
        # If the config entry doesn't already have a unique ID, set one:
        entry_updates["unique_id"] = entry.data[CONF_IP_ADDRESS]
    if CONF_ZONE_RUN_TIME in entry.data:
        # If a zone run time exists in the config entry's data, pop it and move it to
        # options:
        data = {**entry.data}
        entry_updates["data"] = data
        entry_updates["options"] = {
            **entry.options,
            CONF_ZONE_RUN_TIME: data.pop(CONF_ZONE_RUN_TIME),
        }
    if entry_updates:
        hass.config_entries.async_update_entry(entry, **entry_updates)

    _verify_domain_control = verify_domain_control(hass, DOMAIN)

    websession = aiohttp_client.async_get_clientsession(hass)
    client = Client(session=websession)

    try:
        await client.load_local(
            entry.data[CONF_IP_ADDRESS],
            entry.data[CONF_PASSWORD],
            port=entry.data[CONF_PORT],
            ssl=entry.data.get(CONF_SSL, DEFAULT_SSL),
        )
    except RainMachineError as err:
        LOGGER.error("An error occurred: %s", err)
        raise ConfigEntryNotReady from err

    # regenmaschine can load multiple controllers at once, but we only grab the one
    # we loaded above:
    controller = hass.data[DOMAIN][DATA_CONTROLLER][entry.entry_id] = next(
        iter(client.controllers.values()))

    async def async_update(api_category: str) -> dict:
        """Update the appropriate API data based on a category."""
        try:
            if api_category == DATA_PROGRAMS:
                return await controller.programs.all(include_inactive=True)

            if api_category == DATA_PROVISION_SETTINGS:
                return await controller.provisioning.settings()

            if api_category == DATA_RESTRICTIONS_CURRENT:
                return await controller.restrictions.current()

            if api_category == DATA_RESTRICTIONS_UNIVERSAL:
                return await controller.restrictions.universal()

            return await controller.zones.all(details=True,
                                              include_inactive=True)
        except RainMachineError as err:
            raise UpdateFailed(err) from err

    controller_init_tasks = []
    for api_category in [
            DATA_PROGRAMS,
            DATA_PROVISION_SETTINGS,
            DATA_RESTRICTIONS_CURRENT,
            DATA_RESTRICTIONS_UNIVERSAL,
            DATA_ZONES,
    ]:
        coordinator = hass.data[DOMAIN][DATA_COORDINATOR][
            entry.entry_id][api_category] = DataUpdateCoordinator(
                hass,
                LOGGER,
                name=f'{controller.name} ("{api_category}")',
                update_interval=DEFAULT_UPDATE_INTERVAL,
                update_method=partial(async_update, api_category),
            )
        controller_init_tasks.append(coordinator.async_refresh())

    await asyncio.gather(*controller_init_tasks)

    for component in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(entry, component))

    @_verify_domain_control
    async def disable_program(call: ServiceCall):
        """Disable a program."""
        await controller.programs.disable(call.data[CONF_PROGRAM_ID])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def disable_zone(call: ServiceCall):
        """Disable a zone."""
        await controller.zones.disable(call.data[CONF_ZONE_ID])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def enable_program(call: ServiceCall):
        """Enable a program."""
        await controller.programs.enable(call.data[CONF_PROGRAM_ID])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def enable_zone(call: ServiceCall):
        """Enable a zone."""
        await controller.zones.enable(call.data[CONF_ZONE_ID])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def pause_watering(call: ServiceCall):
        """Pause watering for a set number of seconds."""
        await controller.watering.pause_all(call.data[CONF_SECONDS])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def start_program(call: ServiceCall):
        """Start a particular program."""
        await controller.programs.start(call.data[CONF_PROGRAM_ID])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def start_zone(call: ServiceCall):
        """Start a particular zone for a certain amount of time."""
        await controller.zones.start(call.data[CONF_ZONE_ID],
                                     call.data[CONF_ZONE_RUN_TIME])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def stop_all(call: ServiceCall):
        """Stop all watering."""
        await controller.watering.stop_all()
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def stop_program(call: ServiceCall):
        """Stop a program."""
        await controller.programs.stop(call.data[CONF_PROGRAM_ID])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def stop_zone(call: ServiceCall):
        """Stop a zone."""
        await controller.zones.stop(call.data[CONF_ZONE_ID])
        await async_update_programs_and_zones(hass, entry)

    @_verify_domain_control
    async def unpause_watering(call: ServiceCall):
        """Unpause watering."""
        await controller.watering.unpause_all()
        await async_update_programs_and_zones(hass, entry)

    for service, method, schema in [
        ("disable_program", disable_program, SERVICE_ALTER_PROGRAM),
        ("disable_zone", disable_zone, SERVICE_ALTER_ZONE),
        ("enable_program", enable_program, SERVICE_ALTER_PROGRAM),
        ("enable_zone", enable_zone, SERVICE_ALTER_ZONE),
        ("pause_watering", pause_watering, SERVICE_PAUSE_WATERING),
        ("start_program", start_program, SERVICE_START_PROGRAM_SCHEMA),
        ("start_zone", start_zone, SERVICE_START_ZONE_SCHEMA),
        ("stop_all", stop_all, {}),
        ("stop_program", stop_program, SERVICE_STOP_PROGRAM_SCHEMA),
        ("stop_zone", stop_zone, SERVICE_STOP_ZONE_SCHEMA),
        ("unpause_watering", unpause_watering, {}),
    ]:
        hass.services.async_register(DOMAIN, service, method, schema=schema)

    hass.data[DOMAIN][DATA_LISTENER] = entry.add_update_listener(
        async_reload_entry)

    return True
Exemple #17
0
async def async_setup_entry(hass: HomeAssistantType,
                            config_entry: ConfigEntry):
    """Set up UPnP/IGD device from a config entry."""
    domain_data = hass.data[DOMAIN]
    conf = domain_data['config']

    # discover and construct
    device = await async_discover_and_construct(hass,
                                                config_entry.data.get('udn'))
    if not device:
        _LOGGER.info('Unable to create UPnP/IGD, aborting')
        return False

    # 'register'/save UDN
    config_entry.data['udn'] = device.udn
    hass.data[DOMAIN]['devices'][device.udn] = device
    hass.config_entries.async_update_entry(entry=config_entry,
                                           data=config_entry.data)

    # create device registry entry
    device_registry = await dr.async_get_registry(hass)
    device_registry.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={
            (dr.CONNECTION_UPNP, device.udn)
        },
        identifiers={
            (DOMAIN, device.udn)
        },
        name=device.name,
        manufacturer=device.manufacturer,
    )

    # set up sensors
    if conf.get(CONF_ENABLE_SENSORS):
        _LOGGER.debug('Enabling sensors')

        # register sensor setup handlers
        hass.async_create_task(hass.config_entries.async_forward_entry_setup(
            config_entry, 'sensor'))

    # set up port mapping
    if conf.get(CONF_ENABLE_PORT_MAPPING):
        _LOGGER.debug('Enabling port mapping')
        local_ip = domain_data['local_ip']
        ports = conf.get('ports', {})

        hass_port = None
        if hasattr(hass, 'http'):
            hass_port = hass.http.server_port

        ports = _substitute_hass_ports(ports, hass_port=hass_port)
        await device.async_add_port_mappings(ports, local_ip)

    # set up port mapping deletion on stop-hook
    async def delete_port_mapping(event):
        """Delete port mapping on quit."""
        _LOGGER.debug('Deleting port mappings')
        await device.async_delete_port_mappings()

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, delete_port_mapping)

    return True
Exemple #18
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry,
                            async_add_entities: AddEntitiesCallback) -> None:
    """Set up the Netatmo weather and homecoach platform."""
    data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER]
    platform_not_ready = True

    async def find_entities(data_class_name: str) -> list:
        """Find all entities."""
        all_module_infos = {}
        data = data_handler.data

        if data_class_name not in data:
            return []

        if data[data_class_name] is None:
            return []

        data_class = data[data_class_name]

        for station_id in data_class.stations:
            for module_id in data_class.get_modules(station_id):
                all_module_infos[module_id] = data_class.get_module(module_id)

            all_module_infos[station_id] = data_class.get_station(station_id)

        entities = []
        for module in all_module_infos.values():
            if "_id" not in module:
                _LOGGER.debug("Skipping module %s", module.get("module_name"))
                continue

            conditions = [
                c.lower() for c in data_class.get_monitored_conditions(
                    module_id=module["_id"]) if c.lower() in SENSOR_TYPES_KEYS
            ]
            for condition in conditions:
                if f"{condition}_value" in SENSOR_TYPES_KEYS:
                    conditions.append(f"{condition}_value")
                elif f"{condition}_lvl" in SENSOR_TYPES_KEYS:
                    conditions.append(f"{condition}_lvl")

            entities.extend([
                NetatmoSensor(data_handler, data_class_name, module,
                              description) for description in SENSOR_TYPES
                if description.key in conditions
            ])

        _LOGGER.debug("Adding weather sensors %s", entities)
        return entities

    for data_class_name in (
            WEATHERSTATION_DATA_CLASS_NAME,
            HOMECOACH_DATA_CLASS_NAME,
    ):
        await data_handler.register_data_class(data_class_name,
                                               data_class_name, None)
        data_class = data_handler.data.get(data_class_name)

        if data_class and data_class.raw_data:
            platform_not_ready = False

        async_add_entities(await find_entities(data_class_name), True)

    device_registry = await hass.helpers.device_registry.async_get_registry()

    async def add_public_entities(update: bool = True) -> None:
        """Retrieve Netatmo public weather entities."""
        entities = {
            device.name: device.id
            for device in async_entries_for_config_entry(
                device_registry, entry.entry_id)
            if device.model == "Public Weather stations"
        }

        new_entities = []
        for area in [
                NetatmoArea(**i)
                for i in entry.options.get(CONF_WEATHER_AREAS, {}).values()
        ]:
            signal_name = f"{PUBLICDATA_DATA_CLASS_NAME}-{area.uuid}"

            if area.area_name in entities:
                entities.pop(area.area_name)

                if update:
                    async_dispatcher_send(
                        hass,
                        f"netatmo-config-{area.area_name}",
                        area,
                    )
                    continue

            await data_handler.register_data_class(
                PUBLICDATA_DATA_CLASS_NAME,
                signal_name,
                None,
                lat_ne=area.lat_ne,
                lon_ne=area.lon_ne,
                lat_sw=area.lat_sw,
                lon_sw=area.lon_sw,
            )
            data_class = data_handler.data.get(signal_name)

            if data_class and data_class.raw_data:
                nonlocal platform_not_ready
                platform_not_ready = False

            new_entities.extend([
                NetatmoPublicSensor(data_handler, area, description)
                for description in SENSOR_TYPES
                if description.key in SUPPORTED_PUBLIC_SENSOR_TYPES
            ])

        for device_id in entities.values():
            device_registry.async_remove_device(device_id)

        if new_entities:
            async_add_entities(new_entities)

    async_dispatcher_connect(
        hass, f"signal-{DOMAIN}-public-update-{entry.entry_id}",
        add_public_entities)

    @callback
    def _create_entity(netatmo_device: NetatmoDevice) -> None:
        entity = NetatmoClimateBatterySensor(netatmo_device)
        _LOGGER.debug("Adding climate battery sensor %s", entity)
        async_add_entities([entity])

    entry.async_on_unload(
        async_dispatcher_connect(hass, NETATMO_CREATE_BATTERY, _create_entity))

    await add_public_entities(False)

    if platform_not_ready:
        raise PlatformNotReady
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up the Goodwe components from a config entry."""
    hass.data.setdefault(DOMAIN, {})
    name = entry.title
    host = entry.data[CONF_HOST]
    model_family = entry.data[CONF_MODEL_FAMILY]

    # Connect to Goodwe inverter
    try:
        inverter = await connect(
            host=host,
            family=model_family,
            retries=10,
        )
    except InverterError as err:
        raise ConfigEntryNotReady from err

    device_info = DeviceInfo(
        configuration_url="https://www.semsportal.com",
        identifiers={(DOMAIN, inverter.serial_number)},
        name=entry.title,
        manufacturer="GoodWe",
        model=inverter.model_name,
        sw_version=f"{inverter.software_version} ({inverter.arm_version})",
    )

    async def async_update_data():
        """Fetch data from the inverter."""
        try:
            return await inverter.read_runtime_data()
        except RequestFailedException as ex:
            # UDP communication with inverter is by definition unreliable.
            # It is rather normal in many environments to fail to receive
            # proper response in usual time, so we intentionally ignore isolated
            # failures and report problem with availability only after
            # consecutive streak of 3 of failed requests.
            if ex.consecutive_failures_count < 3:
                _LOGGER.debug("No response received (streak of %d)",
                              ex.consecutive_failures_count)
                # return empty dictionary, sensors will keep their previous values
                return {}
            # Inverter does not respond anymore (e.g. it went to sleep mode)
            _LOGGER.debug("Inverter not responding (streak of %d)",
                          ex.consecutive_failures_count)
            raise UpdateFailed(ex) from ex
        except InverterError as ex:
            raise UpdateFailed(ex) from ex

    # 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=SCAN_INTERVAL,
    )

    # Fetch initial data so we have data when entities subscribe
    await coordinator.async_config_entry_first_refresh()

    hass.data[DOMAIN][entry.entry_id] = {
        KEY_INVERTER: inverter,
        KEY_COORDINATOR: coordinator,
        KEY_DEVICE_INFO: device_info,
    }

    entry.async_on_unload(entry.add_update_listener(update_listener))

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
Exemple #20
0
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 SUPPORTED_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
    elif host.scheme == "https":
        https = True
        port = host.port or 443
    else:
        _LOGGER.error("isy994 host value in configuration is invalid")
        return False

    # Connect to ISY controller.
    isy = await hass.async_add_executor_job(
        partial(
            ISY,
            host.hostname,
            port,
            username=user,
            password=password,
            use_https=https,
            tls_ver=tls_version,
            webroot=host.path,
        )
    )
    if not isy.connected:
        return False

    # Trigger a status update for all nodes, not done automatically in PyISY v2.x
    await hass.async_add_executor_job(isy.nodes.update)

    _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.
    for platform in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(entry, platform)
        )

    def _start_auto_update() -> None:
        """Start isy auto update."""
        _LOGGER.debug("ISY Starting Event Stream and automatic updates")
        isy.auto_update = True

    await hass.async_add_executor_job(_start_auto_update)

    undo_listener = entry.add_update_listener(_async_update_listener)

    hass_isy_data[UNDO_UPDATE_LISTENER] = undo_listener

    # Register Integration-wide Services:
    async_setup_services(hass)

    return True
Exemple #21
0
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
    """Set up SimpliSafe as config entry."""
    hass.data.setdefault(DOMAIN, {DATA_CLIENT: {}})
    hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = []

    _async_standardize_config_entry(hass, config_entry)

    _verify_domain_control = verify_domain_control(hass, DOMAIN)

    client_id = await async_get_client_id(hass)
    websession = aiohttp_client.async_get_clientsession(hass)

    try:
        api = await get_api(
            config_entry.data[CONF_USERNAME],
            config_entry.data[CONF_PASSWORD],
            client_id=client_id,
            session=websession,
        )
    except InvalidCredentialsError as err:
        raise ConfigEntryAuthFailed from err
    except SimplipyError as err:
        LOGGER.error("Config entry failed: %s", err)
        raise ConfigEntryNotReady from err

    simplisafe = SimpliSafe(hass, config_entry, api)

    try:
        await simplisafe.async_init()
    except SimplipyError as err:
        raise ConfigEntryNotReady from err

    hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = simplisafe
    hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)

    @callback
    def verify_system_exists(
        coro: Callable[..., Awaitable]
    ) -> Callable[..., Awaitable]:
        """Log an error if a service call uses an invalid system ID."""

        async def decorator(call: ServiceCall) -> None:
            """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: Callable[..., Awaitable]) -> Callable[..., Awaitable]:
        """Log an error if the decorated coroutine is called with a v2 system."""

        async def decorator(call: ServiceCall) -> None:
            """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 clear_notifications(call: ServiceCall) -> None:
        """Clear all active notifications."""
        system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]]
        try:
            await system.clear_notifications()
        except SimplipyError as err:
            LOGGER.error("Error during service call: %s", err)

    @verify_system_exists
    @_verify_domain_control
    async def remove_pin(call: ServiceCall) -> None:
        """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)

    @verify_system_exists
    @_verify_domain_control
    async def set_pin(call: ServiceCall) -> None:
        """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)

    @verify_system_exists
    @v3_only
    @_verify_domain_control
    async def set_system_properties(call: ServiceCall) -> None:
        """Set one or more system parameters."""
        system = cast(SystemV3, 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)

    for service, method, schema in (
        ("clear_notifications", clear_notifications, None),
        ("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)

    config_entry.async_on_unload(config_entry.add_update_listener(async_reload_entry))

    return True
Exemple #22
0
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
    """Set up BLE Monitor from a config entry."""
    _LOGGER.debug("Initializing BLE Monitor entry (config entry): %s",
                  config_entry)

    # Prevent unload to be triggered each time we update the config entry
    global UPDATE_UNLISTENER
    if UPDATE_UNLISTENER:
        UPDATE_UNLISTENER()

    if not config_entry.unique_id:
        hass.config_entries.async_update_entry(config_entry,
                                               unique_id=config_entry.title)

    _LOGGER.debug("async_setup_entry: domain %s", CONFIG_YAML)

    config = {}

    if not CONFIG_YAML:
        for key, value in config_entry.data.items():
            config[key] = value

        for key, value in config_entry.options.items():
            config[key] = value

        config[CONFIG_IS_FLOW] = True
        if CONF_DEVICES not in config:
            config[CONF_DEVICES] = []
        else:
            # device configuration is taken from yaml, but yaml config already removed
            # save unique IDs (only once)
            if "ids_from_name" in config:
                devlist = config[CONF_DEVICES]
                for dev_idx, dev_conf in enumerate(devlist):
                    if CONF_NAME in dev_conf:
                        devlist[dev_idx][CONF_UNIQUE_ID] = dev_conf[CONF_NAME]
                del config["ids_from_name"]
    else:
        for key, value in CONFIG_YAML.items():
            config[key] = value
        if CONF_HCI_INTERFACE in CONFIG_YAML:
            hci_list = []
            if isinstance(CONFIG_YAML[CONF_HCI_INTERFACE], list):
                for hci in CONFIG_YAML[CONF_HCI_INTERFACE]:
                    hci_list.append(str(hci))
            else:
                hci_list.append(str(CONFIG_YAML[CONF_HCI_INTERFACE]))
            config[CONF_HCI_INTERFACE] = hci_list

    hass.config_entries.async_update_entry(config_entry,
                                           data={},
                                           options=config)

    _LOGGER.debug("async_setup_entry: %s", config)

    UPDATE_UNLISTENER = config_entry.add_update_listener(
        _async_update_listener)

    if CONF_HCI_INTERFACE not in config:
        config[CONF_HCI_INTERFACE] = [DEFAULT_HCI_INTERFACE]
    else:
        hci_list = config_entry.options.get(CONF_HCI_INTERFACE)
        for i, hci in enumerate(hci_list):
            hci_list[i] = int(hci)
        config[CONF_HCI_INTERFACE] = hci_list
    _LOGGER.debug("HCI interface is %s", config[CONF_HCI_INTERFACE])

    blemonitor = BLEmonitor(config)
    hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP,
                          blemonitor.shutdown_handler)
    blemonitor.start()

    hass.data[DOMAIN] = {}
    hass.data[DOMAIN]["blemonitor"] = blemonitor
    hass.data[DOMAIN]["config_entry_id"] = config_entry.entry_id

    for component in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(
                config_entry, component))

    return True
Exemple #23
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Initialize config entry which represents the HEOS controller."""
    # For backwards compat
    if entry.unique_id is None:
        hass.config_entries.async_update_entry(entry, unique_id=DOMAIN)

    host = entry.data[CONF_HOST]
    # Setting all_progress_events=False ensures that we only receive a
    # media position update upon start of playback or when media changes
    controller = Heos(host, all_progress_events=False)
    try:
        await controller.connect(auto_reconnect=True)
    # Auto reconnect only operates if initial connection was successful.
    except HeosError as error:
        await controller.disconnect()
        _LOGGER.debug("Unable to connect to controller %s: %s", host, error)
        raise ConfigEntryNotReady from error

    # Disconnect when shutting down
    async def disconnect_controller(event):
        await controller.disconnect()

    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect_controller)
    )

    # Get players and sources
    try:
        players = await controller.get_players()
        favorites = {}
        if controller.is_signed_in:
            favorites = await controller.get_favorites()
        else:
            _LOGGER.warning(
                "%s is not logged in to a HEOS account and will be unable to retrieve "
                "HEOS favorites: Use the 'heos.sign_in' service to sign-in to a HEOS account",
                host,
            )
        inputs = await controller.get_input_sources()
    except HeosError as error:
        await controller.disconnect()
        _LOGGER.debug("Unable to retrieve players and sources: %s", error)
        raise ConfigEntryNotReady from error

    controller_manager = ControllerManager(hass, controller)
    await controller_manager.connect_listeners()

    source_manager = SourceManager(favorites, inputs)
    source_manager.connect_update(hass, controller)

    hass.data[DOMAIN] = {
        DATA_CONTROLLER_MANAGER: controller_manager,
        DATA_SOURCE_MANAGER: source_manager,
        MEDIA_PLAYER_DOMAIN: players,
    }

    services.register(hass, controller)

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
Exemple #24
0
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
    """Set up BLE Monitor from a config entry."""
    _LOGGER.debug("Initializing BLE Monitor entry (config entry): %s",
                  config_entry)

    # Prevent unload to be triggered each time we update the config entry
    global UPDATE_UNLISTENER
    if UPDATE_UNLISTENER:
        UPDATE_UNLISTENER()

    if not config_entry.unique_id:
        hass.config_entries.async_update_entry(config_entry,
                                               unique_id=config_entry.title)

    _LOGGER.debug("async_setup_entry: domain %s", CONFIG_YAML)

    config = {}
    hci_list = []
    bt_mac_list = []

    if not CONFIG_YAML:
        # Configuration in UI
        for key, value in config_entry.data.items():
            config[key] = value

        for key, value in config_entry.options.items():
            config[key] = value

        config[CONFIG_IS_FLOW] = True
        if CONF_DEVICES not in config:
            config[CONF_DEVICES] = []
        else:
            # device configuration is taken from yaml, but yaml config already removed
            # save unique IDs (only once)
            if "ids_from_name" in config:
                devlist = config[CONF_DEVICES]
                for dev_idx, dev_conf in enumerate(devlist):
                    if CONF_NAME in dev_conf:
                        devlist[dev_idx][CONF_UNIQUE_ID] = dev_conf[CONF_NAME]
                del config["ids_from_name"]

        if not config[CONF_BT_INTERFACE]:
            default_hci = list(BT_INTERFACES.keys())[list(
                BT_INTERFACES.values()).index(DEFAULT_BT_INTERFACE)]
            hci_list.append(int(default_hci))
            bt_mac_list.append(str(DEFAULT_BT_INTERFACE))
        else:
            bt_interface_list = config[CONF_BT_INTERFACE]
            for bt_mac in bt_interface_list:
                hci = list(BT_INTERFACES.keys())[list(
                    BT_INTERFACES.values()).index(bt_mac)]
                hci_list.append(int(hci))
                bt_mac_list.append(str(bt_mac))
    else:
        # Configuration in YAML
        for key, value in CONFIG_YAML.items():
            config[key] = value
        _LOGGER.warning("Available Bluetooth interfaces for BLE monitor: %s",
                        BT_MAC_INTERFACES)

        if config[CONF_HCI_INTERFACE]:
            # Configuration of BT interface with hci number
            for hci in CONFIG_YAML[CONF_HCI_INTERFACE]:
                try:
                    hci_list.append(int(hci))
                    bt_mac = BT_INTERFACES.get(hci)
                    if bt_mac:
                        bt_mac_list.append(str(bt_mac))
                    else:
                        _LOGGER.error(
                            "Bluetooth interface hci%i is not available", hci)
                except ValueError:
                    _LOGGER.error("Bluetooth interface hci%i is not available",
                                  hci)
        else:
            # Configuration of BT interface with mac address
            CONF_BT_INTERFACES = [
                x.upper() for x in CONFIG_YAML[CONF_BT_INTERFACE]
            ]
            for bt_mac in CONF_BT_INTERFACES:
                try:
                    hci = list(BT_INTERFACES.keys())[list(
                        BT_INTERFACES.values()).index(bt_mac)]
                    hci_list.append(int(hci))
                    bt_mac_list.append(str(bt_mac))
                except ValueError:
                    _LOGGER.error(
                        "Bluetooth interface with MAC address %s is not available",
                        bt_mac)

    if not hci_list:
        # Fall back in case no hci interfaces are added
        default_hci = list(BT_INTERFACES.keys())[list(
            BT_INTERFACES.values()).index(DEFAULT_BT_INTERFACE)]
        hci_list.append(int(default_hci))
        bt_mac_list.append(str(DEFAULT_BT_INTERFACE))
        _LOGGER.warning(
            "No configured Bluetooth interfaces was found, using default interface instead"
        )

    config[CONF_HCI_INTERFACE] = hci_list
    config[CONF_BT_INTERFACE] = bt_mac_list

    hass.config_entries.async_update_entry(config_entry,
                                           data={},
                                           options=config)

    _LOGGER.debug("async_setup_entry: %s", config)

    UPDATE_UNLISTENER = config_entry.add_update_listener(
        _async_update_listener)

    _LOGGER.debug("HCI interface is %s", config[CONF_HCI_INTERFACE])

    blemonitor = BLEmonitor(config)
    hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP,
                          blemonitor.shutdown_handler)
    blemonitor.start()

    hass.data[DOMAIN] = {}
    hass.data[DOMAIN]["blemonitor"] = blemonitor
    hass.data[DOMAIN]["config_entry_id"] = config_entry.entry_id

    for component in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(
                config_entry, component))

    return True
Exemple #25
0
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
    except SENSE_TIMEOUT_EXCEPTIONS as err:
        raise ConfigEntryNotReady(
            str(err) or "Timed out during authentication") 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
Exemple #26
0
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 entity_key, entity_attrs in entries.items():
            if (device_id and entity_attrs[ATTR_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 entity_key:
                    entity_type = entity_key
                    continue
                if "volume" in device_id and "volume" in entity_key:
                    entity_type = entity_key
                    continue

            if entity_attrs[ATTR_NAME] == label:
                entity_type = entity_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
        _LOGGER.debug(
            "Reauthentication for DSM '%s' needed - reason: %s",
            entry.unique_id,
            details,
        )
        hass.async_create_task(
            hass.config_entries.flow.async_init(
                DOMAIN,
                context={
                    "source": SOURCE_REAUTH,
                    "data": {
                        **entry.data
                    },
                    EXCEPTION_DETAILS: details,
                },
            ))
        return False
    except (SynologyDSMLoginFailedException,
            SynologyDSMRequestException) as err:
        _LOGGER.debug("Unable to connect to DSM '%s' during setup: %s",
                      entry.unique_id, err)
        raise ConfigEntryNotReady 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
Exemple #27
0
async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the deCONZ lights and groups from a config entry."""
    gateway = get_gateway_from_config_entry(hass, config_entry)
    gateway.entities[DOMAIN] = set()

    @callback
    def async_add_light(
        lights: list[Light] | ValuesView[Light] = gateway.api.lights.values(),
    ) -> None:
        """Add light from deCONZ."""
        entities = []

        for light in lights:
            if (
                isinstance(light, Light)
                and light.type not in POWER_PLUGS
                and light.unique_id not in gateway.entities[DOMAIN]
            ):
                entities.append(DeconzLight(light, gateway))

        if entities:
            async_add_entities(entities)

    config_entry.async_on_unload(
        async_dispatcher_connect(
            hass,
            gateway.signal_new_light,
            async_add_light,
        )
    )

    @callback
    def async_add_group(
        groups: list[Group] | ValuesView[Group] = gateway.api.groups.values(),
    ) -> None:
        """Add group from deCONZ."""
        if not gateway.option_allow_deconz_groups:
            return

        entities = []

        for group in groups:
            if not group.lights:
                continue

            known_groups = set(gateway.entities[DOMAIN])
            new_group = DeconzGroup(group, gateway)
            if new_group.unique_id not in known_groups:
                entities.append(new_group)

        if entities:
            async_add_entities(entities)

    config_entry.async_on_unload(
        async_dispatcher_connect(
            hass,
            gateway.signal_new_group,
            async_add_group,
        )
    )

    async_add_light()
    async_add_group()
Exemple #28
0
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool:
    """Set up Huawei LTE component from config entry."""
    url = config_entry.data[CONF_URL]

    # Override settings from YAML config, but only if they're changed in it
    # Old values are stored as *_from_yaml in the config entry
    yaml_config = hass.data[DOMAIN].config.get(url)
    if yaml_config:
        # Config values
        new_data = {}
        for key in CONF_USERNAME, CONF_PASSWORD:
            if key in yaml_config:
                value = yaml_config[key]
                if value != config_entry.data.get(f"{key}_from_yaml"):
                    new_data[f"{key}_from_yaml"] = value
                    new_data[key] = value
        # Options
        new_options = {}
        yaml_recipient = yaml_config.get(NOTIFY_DOMAIN, {}).get(CONF_RECIPIENT)
        if yaml_recipient is not None and yaml_recipient != config_entry.options.get(
            f"{CONF_RECIPIENT}_from_yaml"
        ):
            new_options[f"{CONF_RECIPIENT}_from_yaml"] = yaml_recipient
            new_options[CONF_RECIPIENT] = yaml_recipient
        yaml_notify_name = yaml_config.get(NOTIFY_DOMAIN, {}).get(CONF_NAME)
        if (
            yaml_notify_name is not None
            and yaml_notify_name != config_entry.options.get(f"{CONF_NAME}_from_yaml")
        ):
            new_options[f"{CONF_NAME}_from_yaml"] = yaml_notify_name
            new_options[CONF_NAME] = yaml_notify_name
        # Update entry if overrides were found
        if new_data or new_options:
            hass.config_entries.async_update_entry(
                config_entry,
                data={**config_entry.data, **new_data},
                options={**config_entry.options, **new_options},
            )

    # Get MAC address for use in unique ids. Being able to use something
    # from the API would be nice, but all of that seems to be available only
    # through authenticated calls (e.g. device_information.SerialNumber), and
    # we want this available and the same when unauthenticated too.
    host = urlparse(url).hostname
    try:
        if ipaddress.ip_address(host).version == 6:
            mode = "ip6"
        else:
            mode = "ip"
    except ValueError:
        mode = "hostname"
    mac = await hass.async_add_executor_job(partial(get_mac_address, **{mode: host}))

    def get_connection() -> Connection:
        """
        Set up a connection.

        Authorized one if username/pass specified (even if empty), unauthorized one otherwise.
        """
        username = config_entry.data.get(CONF_USERNAME)
        password = config_entry.data.get(CONF_PASSWORD)
        if username or password:
            connection: Connection = AuthorizedConnection(
                url, username=username, password=password, timeout=CONNECTION_TIMEOUT
            )
        else:
            connection = Connection(url, timeout=CONNECTION_TIMEOUT)
        return connection

    def signal_update() -> None:
        """Signal updates to data."""
        dispatcher_send(hass, UPDATE_SIGNAL, url)

    try:
        connection = await hass.async_add_executor_job(get_connection)
    except Timeout as ex:
        raise ConfigEntryNotReady from ex

    # Set up router and store reference to it
    router = Router(connection, url, mac, signal_update)
    hass.data[DOMAIN].routers[url] = router

    # Do initial data update
    await hass.async_add_executor_job(router.update)

    # Clear all subscriptions, enabled entities will push back theirs
    router.subscriptions.clear()

    # Set up device registry
    device_data = {}
    sw_version = None
    if router.data.get(KEY_DEVICE_INFORMATION):
        device_info = router.data[KEY_DEVICE_INFORMATION]
        sw_version = device_info.get("SoftwareVersion")
        if device_info.get("DeviceName"):
            device_data["model"] = device_info["DeviceName"]
    if not sw_version and router.data.get(KEY_DEVICE_BASIC_INFORMATION):
        sw_version = router.data[KEY_DEVICE_BASIC_INFORMATION].get("SoftwareVersion")
    if sw_version:
        device_data["sw_version"] = sw_version
    device_registry = await dr.async_get_registry(hass)
    device_registry.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections=router.device_connections,
        identifiers=router.device_identifiers,
        name=router.device_name,
        manufacturer="Huawei",
        **device_data,
    )

    # Forward config entry setup to platforms
    for domain in CONFIG_ENTRY_PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(config_entry, domain)
        )
    # Notify doesn't support config entry setup yet, load with discovery for now
    await discovery.async_load_platform(
        hass,
        NOTIFY_DOMAIN,
        DOMAIN,
        {
            CONF_URL: url,
            CONF_NAME: config_entry.options.get(CONF_NAME, DEFAULT_NOTIFY_SERVICE_NAME),
            CONF_RECIPIENT: config_entry.options.get(CONF_RECIPIENT),
        },
        hass.data[DOMAIN].hass_config,
    )

    # Add config entry options update listener
    router.unload_handlers.append(
        config_entry.add_update_listener(async_signal_options_update)
    )

    def _update_router(*_: Any) -> None:
        """
        Update router data.

        Separate passthrough function because lambdas don't work with track_time_interval.
        """
        router.update()

    # Set up periodic update
    router.unload_handlers.append(
        async_track_time_interval(hass, _update_router, SCAN_INTERVAL)
    )

    # Clean up at end
    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, router.cleanup)

    return True
Exemple #29
0
async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up Z-Wave sensor from config entry."""
    client: ZwaveClient = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT]

    @callback
    def async_add_sensor(info: ZwaveDiscoveryInfo) -> None:
        """Add Z-Wave Sensor."""
        entities: list[ZWaveBaseEntity] = []

        entity_description = ENTITY_DESCRIPTION_KEY_MAP.get(
            info.platform_data
        ) or ZwaveSensorEntityDescription("base_sensor")
        entity_description.info = info

        if info.platform_hint == "string_sensor":
            entities.append(ZWaveStringSensor(config_entry, client, entity_description))
        elif info.platform_hint == "numeric_sensor":
            entities.append(
                ZWaveNumericSensor(config_entry, client, entity_description)
            )
        elif info.platform_hint == "list_sensor":
            entities.append(ZWaveListSensor(config_entry, client, entity_description))
        elif info.platform_hint == "config_parameter":
            entities.append(
                ZWaveConfigParameterSensor(config_entry, client, entity_description)
            )
        elif info.platform_hint == "meter":
            entities.append(ZWaveMeterSensor(config_entry, client, entity_description))
        else:
            LOGGER.warning(
                "Sensor not implemented for %s/%s",
                info.platform_hint,
                info.primary_value.propertyname,
            )
            return

        async_add_entities(entities)

    @callback
    def async_add_node_status_sensor(node: ZwaveNode) -> None:
        """Add node status sensor."""
        async_add_entities([ZWaveNodeStatusSensor(config_entry, client, node)])

    config_entry.async_on_unload(
        async_dispatcher_connect(
            hass,
            f"{DOMAIN}_{config_entry.entry_id}_add_{SENSOR_DOMAIN}",
            async_add_sensor,
        )
    )

    config_entry.async_on_unload(
        async_dispatcher_connect(
            hass,
            f"{DOMAIN}_{config_entry.entry_id}_add_node_status_sensor",
            async_add_node_status_sensor,
        )
    )

    platform = entity_platform.async_get_current_platform()
    platform.async_register_entity_service(
        SERVICE_RESET_METER,
        {
            vol.Optional(ATTR_METER_TYPE): vol.Coerce(int),
            vol.Optional(ATTR_VALUE): vol.Coerce(int),
        },
        "async_reset_meter",
    )
Exemple #30
0
async def async_setup_entry(hass: core.HomeAssistant,
                            entry: config_entries.ConfigEntry):
    """Set up the motion_blinds components from a config entry."""
    hass.data.setdefault(DOMAIN, {})
    host = entry.data[CONF_HOST]
    key = entry.data[CONF_API_KEY]
    multicast_interface = entry.data.get(CONF_INTERFACE, DEFAULT_INTERFACE)
    wait_for_push = entry.options.get(CONF_WAIT_FOR_PUSH,
                                      DEFAULT_WAIT_FOR_PUSH)

    entry.async_on_unload(entry.add_update_listener(update_listener))

    # Create multicast Listener
    if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]:
        multicast = AsyncMotionMulticast(interface=multicast_interface)
        hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast
        # start listening for local pushes (only once)
        await multicast.Start_listen()

        # register stop callback to shutdown listening for local pushes
        def stop_motion_multicast(event):
            """Stop multicast thread."""
            _LOGGER.debug("Shutting down Motion Listener")
            multicast.Stop_listen()

        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
                                   stop_motion_multicast)

    # Connect to motion gateway
    multicast = hass.data[DOMAIN][KEY_MULTICAST_LISTENER]
    connect_gateway_class = ConnectMotionGateway(hass, multicast)
    if not await connect_gateway_class.async_connect_gateway(host, key):
        raise ConfigEntryNotReady
    motion_gateway = connect_gateway_class.gateway_device
    coordinator_info = {
        KEY_GATEWAY: motion_gateway,
        CONF_WAIT_FOR_PUSH: wait_for_push,
    }

    coordinator = DataUpdateCoordinatorMotionBlinds(
        hass,
        _LOGGER,
        coordinator_info,
        # Name of the data. For logging purposes.
        name=entry.title,
        # Polling interval. Will only be polled if there are subscribers.
        update_interval=timedelta(seconds=UPDATE_INTERVAL),
    )

    # Fetch initial data so we have data when entities subscribe
    await coordinator.async_config_entry_first_refresh()

    hass.data[DOMAIN][entry.entry_id] = {
        KEY_GATEWAY: motion_gateway,
        KEY_COORDINATOR: coordinator,
    }

    if motion_gateway.firmware is not None:
        version = f"{motion_gateway.firmware}, protocol: {motion_gateway.protocol}"
    else:
        version = f"Protocol: {motion_gateway.protocol}"

    device_registry = await dr.async_get_registry(hass)
    device_registry.async_get_or_create(
        config_entry_id=entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, motion_gateway.mac)},
        identifiers={(DOMAIN, entry.unique_id)},
        manufacturer=MANUFACTURER,
        name=entry.title,
        model="Wi-Fi bridge",
        sw_version=version,
    )

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
Exemple #31
0
async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
) -> bool:
    """Create a gateway."""
    tradfri_data: dict[str, Any] = {}
    hass.data.setdefault(DOMAIN, {})[entry.entry_id] = tradfri_data

    factory = await APIFactory.init(
        entry.data[CONF_HOST],
        psk_id=entry.data[CONF_IDENTITY],
        psk=entry.data[CONF_KEY],
    )
    tradfri_data[FACTORY] = factory  # Used for async_unload_entry

    async def on_hass_stop(event: Event) -> None:
        """Close connection when hass stops."""
        await factory.shutdown()

    # Setup listeners
    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop))

    api = factory.request
    gateway = Gateway()

    try:
        gateway_info = await api(gateway.get_gateway_info(),
                                 timeout=TIMEOUT_API)
        devices_commands: Command = await api(gateway.get_devices(),
                                              timeout=TIMEOUT_API)
        devices: list[Device] = await api(devices_commands,
                                          timeout=TIMEOUT_API)

    except RequestError as exc:
        await factory.shutdown()
        raise ConfigEntryNotReady from exc

    dev_reg = dr.async_get(hass)
    dev_reg.async_get_or_create(
        config_entry_id=entry.entry_id,
        connections=set(),
        identifiers={(DOMAIN, entry.data[CONF_GATEWAY_ID])},
        manufacturer="IKEA of Sweden",
        name="Gateway",
        # They just have 1 gateway model. Type is not exposed yet.
        model="E1526",
        sw_version=gateway_info.firmware_version,
    )

    remove_stale_devices(hass, entry, devices)

    # Setup the device coordinators
    coordinator_data = {
        CONF_GATEWAY_ID: gateway,
        KEY_API: api,
        COORDINATOR_LIST: [],
    }

    for device in devices:
        coordinator = TradfriDeviceDataUpdateCoordinator(hass=hass,
                                                         api=api,
                                                         device=device)
        await coordinator.async_config_entry_first_refresh()

        entry.async_on_unload(
            async_dispatcher_connect(hass, SIGNAL_GW,
                                     coordinator.set_hub_available))
        coordinator_data[COORDINATOR_LIST].append(coordinator)

    tradfri_data[COORDINATOR] = coordinator_data

    async def async_keep_alive(now: datetime) -> None:
        if hass.is_stopping:
            return

        gw_status = True
        try:
            await api(gateway.get_gateway_info())
        except RequestError:
            LOGGER.error("Keep-alive failed")
            gw_status = False

        async_dispatcher_send(hass, SIGNAL_GW, gw_status)

    entry.async_on_unload(
        async_track_time_interval(hass, async_keep_alive,
                                  timedelta(seconds=60)))

    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

    return True
Exemple #32
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """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 is not ConfigEntryState.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 as ex:
        raise ConfigEntryAuthFailed(
            f"Token not accepted, please reauthenticate Plex server '{entry.data[CONF_SERVER]}'"
        ) from ex
    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_get_config_entry_diagnostics(
        hass: HomeAssistant, config_entry: ConfigEntry) -> dict[str, Any]:
    """Return diagnostics for a config entry."""
    diagnostics = {"config_entry": config_entry.as_dict()}
    return diagnostics
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 scenes
        scenes = await async_get_entry_scenes(entry, api)

        # Get SmartApp token to sync subscriptions
        token = await api.generate_tokens(
            entry.data[CONF_OAUTH_CLIENT_ID],
            entry.data[CONF_OAUTH_CLIENT_SECRET],
            entry.data[CONF_REFRESH_TOKEN])
        entry.data[CONF_REFRESH_TOKEN] = token.refresh_token
        hass.config_entries.async_update_entry(entry)

        # 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 excluded",
                              device.label, device.device_id, exc_info=True)
                devices.remove(device)

        await asyncio.gather(*[retrieve_device_status(d)
                               for d in devices.copy()])

        # Sync device subscriptions
        await smartapp_sync_subscriptions(
            hass, token.access_token, installed_app.location_id,
            installed_app.installed_app_id, devices)

        # Setup device broker
        broker = DeviceBroker(hass, entry, token, smart_app, devices, scenes)
        broker.connect()
        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