Пример #1
0
 async def _async_set_unique_id_or_update(self, isy_mac: str,
                                          ip_address: str,
                                          port: int | None) -> None:
     """Abort and update the ip address on change."""
     existing_entry = await self.async_set_unique_id(isy_mac)
     if not existing_entry:
         return
     if existing_entry.source == config_entries.SOURCE_IGNORE:
         raise AbortFlow("already_configured")
     parsed_url = urlparse(existing_entry.data[CONF_HOST])
     if parsed_url.hostname != ip_address:
         new_netloc = ip_address
         if port:
             new_netloc = f"{ip_address}:{port}"
         elif parsed_url.port:
             new_netloc = f"{ip_address}:{parsed_url.port}"
         self.hass.config_entries.async_update_entry(
             existing_entry,
             data={
                 **existing_entry.data,
                 CONF_HOST:
                 urlunparse((
                     parsed_url.scheme,
                     new_netloc,
                     parsed_url.path,
                     parsed_url.query,
                     parsed_url.fragment,
                     None,
                 )),
             },
         )
     raise AbortFlow("already_configured")
Пример #2
0
    async def _async_try_connect_and_fetch(ip_address: str) -> dict[str, Any]:
        """Try to connect."""

        _LOGGER.debug("config_flow _async_try_connect_and_fetch")

        # Make connection with device
        # This is to test the connection and to get info for unique_id
        energy_api = HomeWizardEnergy(ip_address)

        try:
            device = await energy_api.device()

        except DisabledError as ex:
            _LOGGER.error("API disabled, API must be enabled in the app")
            raise AbortFlow("api_not_enabled") from ex

        except UnsupportedError as ex:
            _LOGGER.error("API version unsuppored")
            raise AbortFlow("unsupported_api_version") from ex

        except Exception as ex:
            _LOGGER.exception(
                "Error connecting with Energy Device at %s",
                ip_address,
            )
            raise AbortFlow("unknown_error") from ex

        finally:
            await energy_api.close()

        return {
            CONF_PRODUCT_NAME: device.product_name,
            CONF_PRODUCT_TYPE: device.product_type,
            CONF_SERIAL: device.serial,
        }
Пример #3
0
 async def async_get_currencies(self) -> dict[str, str]:
     """Get the available currencies."""
     if not self.currencies:
         client = Client("dummy-api-key",
                         async_get_clientsession(self.hass))
         try:
             async with async_timeout.timeout(CLIENT_TIMEOUT):
                 self.currencies = await client.get_currencies()
         except OpenExchangeRatesClientError as err:
             raise AbortFlow("cannot_connect") from err
         except asyncio.TimeoutError as err:
             raise AbortFlow("timeout_connect") from err
     return self.currencies
Пример #4
0
    async def _async_parse_discovery(self,
                                     discovery_info: ssdp.SsdpServiceInfo,
                                     raise_on_progress: bool = True) -> None:
        """Get required details from an SSDP discovery.

        Aborts if a device matching the SSDP USN has already been configured.
        """
        LOGGER.debug(
            "_async_parse_discovery: location: %s, USN: %s",
            discovery_info.ssdp_location,
            discovery_info.ssdp_usn,
        )

        if not discovery_info.ssdp_location or not discovery_info.ssdp_usn:
            raise AbortFlow("bad_ssdp")

        if not self._location:
            self._location = discovery_info.ssdp_location

        self._usn = discovery_info.ssdp_usn
        await self.async_set_unique_id(self._usn,
                                       raise_on_progress=raise_on_progress)

        # Abort if already configured, but update the last-known location
        self._abort_if_unique_id_configured(updates={CONF_URL: self._location},
                                            reload_on_update=False)

        self._name = (discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME)
                      or urlparse(self._location).hostname or DEFAULT_NAME)
Пример #5
0
    async def async_step_discovery_confirm(self,
                                           user_input: dict[str, Any]
                                           | None = None) -> FlowResult:
        """Confirm discovery."""
        if user_input is not None:
            if (self.config[CONF_API_ENABLED]) != "1":
                raise AbortFlow(reason="api_not_enabled")

            # Check connection
            await self._async_try_connect_and_fetch(
                str(self.config[CONF_IP_ADDRESS]))

            return self.async_create_entry(
                title=
                f"{self.config[CONF_PRODUCT_NAME]} ({self.config[CONF_SERIAL]})",
                data={
                    CONF_IP_ADDRESS: self.config[CONF_IP_ADDRESS],
                },
            )

        self._set_confirm_only()

        self.context["title_placeholders"] = {
            "name":
            f"{self.config[CONF_PRODUCT_NAME]} ({self.config[CONF_SERIAL]})"
        }

        return self.async_show_form(
            step_id="discovery_confirm",
            description_placeholders={
                CONF_PRODUCT_TYPE: self.config[CONF_PRODUCT_TYPE],
                CONF_SERIAL: self.config[CONF_SERIAL],
                CONF_IP_ADDRESS: self.config[CONF_IP_ADDRESS],
            },
        )
 async def async_step_zeroconf(
     self, discovery_info: zeroconf.ZeroconfServiceInfo
 ) -> FlowResult:
     """Handle a flow initialized by zeroconf discovery."""
     name: str = discovery_info.name
     host: str = discovery_info.host
     bond_id = name.partition(".")[0]
     await self.async_set_unique_id(bond_id)
     for entry in self._async_current_entries():
         if entry.unique_id != bond_id:
             continue
         updates = {CONF_HOST: host}
         if entry.state == ConfigEntryState.SETUP_ERROR and (
             token := await async_get_token(self.hass, host)
         ):
             updates[CONF_ACCESS_TOKEN] = token
         new_data = {**entry.data, **updates}
         if new_data != dict(entry.data):
             self.hass.config_entries.async_update_entry(
                 entry, data={**entry.data, **updates}
             )
             self.hass.async_create_task(
                 self.hass.config_entries.async_reload(entry.entry_id)
             )
         raise AbortFlow("already_configured")
Пример #7
0
    async def async_step_finish_addon_setup(self,
                                            user_input: dict[str, Any]
                                            | None = None) -> FlowResult:
        """Prepare info needed to complete the config entry.

        Get add-on discovery info and server version info.
        Set unique id and abort if already configured.
        """
        if not self.ws_address:
            discovery_info = await self._async_get_addon_discovery_info()
            self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}"

        if not self.unique_id or self.context[
                "source"] == config_entries.SOURCE_USB:
            if not self.version_info:
                try:
                    self.version_info = await async_get_version_info(
                        self.hass, self.ws_address)
                except CannotConnect as err:
                    raise AbortFlow("cannot_connect") from err

            await self.async_set_unique_id(str(self.version_info.home_id),
                                           raise_on_progress=False)

        self._abort_if_unique_id_configured(
            updates={
                CONF_URL: self.ws_address,
                CONF_USB_PATH: self.usb_path,
                CONF_S0_LEGACY_KEY: self.s0_legacy_key,
                CONF_S2_ACCESS_CONTROL_KEY: self.s2_access_control_key,
                CONF_S2_AUTHENTICATED_KEY: self.s2_authenticated_key,
                CONF_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_key,
            })
        return self._async_create_entry_from_vars()
Пример #8
0
    async def _async_set_discovered_mac(self, device: FluxLEDDiscovery,
                                        allow_update_mac: bool) -> None:
        """Set the discovered mac.

        We only allow it to be updated if it comes from udp
        discovery since the dhcp mac can be one digit off from
        the udp discovery mac for devices with multiple network interfaces
        """
        mac_address = device[ATTR_ID]
        assert mac_address is not None
        mac = dr.format_mac(mac_address)
        await self.async_set_unique_id(mac)
        for entry in self._async_current_entries(include_ignore=False):
            if entry.data[CONF_HOST] == device[ATTR_IPADDR] or (
                    entry.unique_id and ":" in entry.unique_id
                    and mac_matches_by_one(entry.unique_id, mac)):
                if (async_update_entry_from_discovery(self.hass, entry, device,
                                                      None, allow_update_mac)
                        or entry.state
                        == config_entries.ConfigEntryState.SETUP_RETRY):
                    self.hass.async_create_task(
                        self.hass.config_entries.async_reload(entry.entry_id))
                else:
                    async_dispatcher_send(
                        self.hass,
                        FLUX_LED_DISCOVERY_SIGNAL.format(
                            entry_id=entry.entry_id),
                    )
                raise AbortFlow("already_configured")
Пример #9
0
    async def async_step_finish_addon_setup(
        self, user_input: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Prepare info needed to complete the config entry.

        Get add-on discovery info and server version info.
        Set unique id and abort if already configured.
        """
        assert self.hass
        if not self.ws_address:
            discovery_info = await self._async_get_addon_discovery_info()
            self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}"

        if not self.unique_id:
            try:
                version_info = await async_get_version_info(self.hass, self.ws_address)
            except CannotConnect as err:
                raise AbortFlow("cannot_connect") from err
            await self.async_set_unique_id(
                version_info.home_id, raise_on_progress=False
            )

        self._abort_if_unique_id_configured(
            updates={
                CONF_URL: self.ws_address,
                CONF_USB_PATH: self.usb_path,
                CONF_NETWORK_KEY: self.network_key,
            }
        )
        return self._async_create_entry_from_vars()
Пример #10
0
    async def _async_try_connect_and_fetch(ip_address: str) -> dict[str, Any]:
        """Try to connect."""

        _LOGGER.debug("config_flow _async_try_connect_and_fetch")

        # Make connection with device
        # This is to test the connection and to get info for unique_id
        energy_api = aiohwenergy.HomeWizardEnergy(ip_address)

        try:
            with async_timeout.timeout(10):
                await energy_api.initialize()

        except aiohwenergy.DisabledError as ex:
            _LOGGER.error("API disabled, API must be enabled in the app")
            raise AbortFlow("api_not_enabled") from ex

        except Exception as ex:
            _LOGGER.exception(
                "Error connecting with Energy Device at %s",
                ip_address,
            )
            raise AbortFlow("unknown_error") from ex

        finally:
            await energy_api.close()

        if energy_api.device is None:
            _LOGGER.error("Initialization failed")
            raise AbortFlow("unknown_error")

        # Validate metadata
        if energy_api.device.api_version != "v1":
            raise AbortFlow("unsupported_api_version")

        if energy_api.device.product_type not in SUPPORTED_DEVICES:
            _LOGGER.error(
                "Device (%s) not supported by integration",
                energy_api.device.product_type,
            )
            raise AbortFlow("device_not_supported")

        return {
            CONF_PRODUCT_NAME: energy_api.device.product_name,
            CONF_PRODUCT_TYPE: energy_api.device.product_type,
            CONF_SERIAL: energy_api.device.serial,
        }
Пример #11
0
 async def _async_set_addon_config(self, config: dict) -> None:
     """Set Z-Wave JS add-on config."""
     addon_manager: AddonManager = get_addon_manager(self.hass)
     try:
         await addon_manager.async_set_addon_options(config)
     except AddonError as err:
         _LOGGER.error(err)
         raise AbortFlow("addon_set_config_failed") from err
Пример #12
0
 async def _async_set_addon_config(self, config):
     """Set OpenZWave add-on config."""
     options = {"options": config}
     try:
         await self.hass.components.hassio.async_set_addon_options(
             "core_zwave", options)
     except self.hass.components.hassio.HassioAPIError as err:
         _LOGGER.error("Failed to set OpenZWave add-on config: %s", err)
         raise AbortFlow("addon_set_config_failed") from err
Пример #13
0
    async def _async_get_addon_info(self) -> AddonInfo:
        """Return and cache Z-Wave JS add-on info."""
        addon_manager: AddonManager = get_addon_manager(self.hass)
        try:
            addon_info: AddonInfo = await addon_manager.async_get_addon_info()
        except AddonError as err:
            _LOGGER.error(err)
            raise AbortFlow("addon_info_failed") from err

        return addon_info
Пример #14
0
    async def _async_get_addon_discovery_info(self) -> dict:
        """Return add-on discovery info."""
        assert self.hass
        try:
            discovery_info: dict = (
                await
                self.hass.components.hassio.async_get_addon_discovery_info(
                    "core_zwave_js"))
        except self.hass.components.hassio.HassioAPIError as err:
            _LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s",
                          err)
            raise AbortFlow("addon_get_discovery_info_failed") from err

        if not discovery_info:
            _LOGGER.error("Failed to get Z-Wave JS add-on discovery info")
            raise AbortFlow("addon_missing_discovery_info")

        discovery_info_config: dict = discovery_info["config"]
        return discovery_info_config
Пример #15
0
    async def _async_get_addon_info(self):
        """Return and cache OpenZWave add-on info."""
        try:
            addon_info = await self.hass.components.hassio.async_get_addon_info(
                "core_zwave")
        except self.hass.components.hassio.HassioAPIError as err:
            _LOGGER.error("Failed to get OpenZWave add-on info: %s", err)
            raise AbortFlow("addon_info_failed") from err

        return addon_info
Пример #16
0
    async def _async_get_addon_discovery_info(self) -> dict:
        """Return add-on discovery info."""
        addon_manager: AddonManager = get_addon_manager(self.hass)
        try:
            discovery_info_config = await addon_manager.async_get_addon_discovery_info()
        except AddonError as err:
            _LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", err)
            raise AbortFlow("addon_get_discovery_info_failed") from err

        return discovery_info_config
Пример #17
0
    async def _async_get_addon_info(self) -> dict:
        """Return and cache Z-Wave JS add-on info."""
        assert self.hass
        try:
            addon_info: dict = await self.hass.components.hassio.async_get_addon_info(
                "core_zwave_js")
        except self.hass.components.hassio.HassioAPIError as err:
            _LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err)
            raise AbortFlow("addon_info_failed") from err

        return addon_info
Пример #18
0
 def _async_check_and_update_in_progress(self, host: str,
                                         unique_id: str) -> None:
     """Check for in-progress flows and update them with identifiers if needed."""
     for flow in self._async_in_progress(include_uninitialized=True):
         context = flow["context"]
         if (context.get("source") != config_entries.SOURCE_ZEROCONF
                 or context.get(CONF_ADDRESS) != host):
             continue
         if ("all_identifiers" in context
                 and unique_id not in context["all_identifiers"]):
             # Add potentially new identifiers from this device to the existing flow
             context["all_identifiers"].append(unique_id)
         raise AbortFlow("already_in_progress")
Пример #19
0
    async def _validate_and_create(self, data):
        """Validate the user input allows us to connect.

        Data has the keys from DATA_SCHEMA with values provided by the user.
        """

        for entry in self.hass.config_entries.async_entries(DOMAIN):
            if (
                entry.data[CONF_HOST] == data[CONF_HOST]
                and entry.data[CONF_PORT] == data[CONF_PORT]
            ):
                raise AbortFlow("already_configured")

        camera = FoscamCamera(
            data[CONF_HOST],
            data[CONF_PORT],
            data[CONF_USERNAME],
            data[CONF_PASSWORD],
            verbose=False,
        )

        # Validate data by sending a request to the camera
        ret, _ = await self.hass.async_add_executor_job(camera.get_product_all_info)

        if ret == ERROR_FOSCAM_UNAVAILABLE:
            raise CannotConnect

        if ret == ERROR_FOSCAM_AUTH:
            raise InvalidAuth

        if ret != FOSCAM_SUCCESS:
            LOGGER.error(
                "Unexpected error code from camera %s:%s: %s",
                data[CONF_HOST],
                data[CONF_PORT],
                ret,
            )
            raise InvalidResponse

        # Try to get camera name (only possible with admin account)
        ret, response = await self.hass.async_add_executor_job(camera.get_dev_info)

        dev_name = response.get(
            "devName", f"Foscam {data[CONF_HOST]}:{data[CONF_PORT]}"
        )

        name = data.pop(CONF_NAME, dev_name)

        return self.async_create_entry(title=name, data=data)
Пример #20
0
    async def _async_discovery_handler(self, ip_address: str) -> FlowResult:
        """Start the user flow from any discovery."""
        self.context[CONF_IP_ADDRESS] = ip_address
        self._abort_if_unique_id_configured({CONF_IP_ADDRESS: ip_address})

        self._async_abort_entries_match({CONF_IP_ADDRESS: ip_address})

        self._ip_address = ip_address
        for progress in self._async_in_progress():
            if progress.get("context",
                            {}).get(CONF_IP_ADDRESS) == self._ip_address:
                raise AbortFlow("already_in_progress")

        self._device_type = DEVICE_TYPE_ISMARTGATE
        return await self.async_step_user()
Пример #21
0
 async def _async_connect_discovered_or_abort(self) -> None:
     """Connect to the device and verify its responding."""
     device = self._discovered_device
     bulb = wizlight(device.ip_address)
     try:
         bulbtype = await bulb.get_bulbtype()
     except WIZ_CONNECT_EXCEPTIONS as ex:
         _LOGGER.debug(
             "Failed to connect to %s during discovery: %s",
             device.ip_address,
             ex,
             exc_info=True,
         )
         raise AbortFlow("cannot_connect") from ex
     self._name = name_from_bulb_type_and_mac(bulbtype, device.mac_address)
Пример #22
0
    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle the initial step."""
        errors = {}

        if user_input is not None:
            url = user_input[CONF_URL]
            username = user_input[CONF_USERNAME]
            password = user_input[CONF_PASSWORD]

            qsw = QnapQswApi(
                aiohttp_client.async_get_clientsession(self.hass),
                ConnectionOptions(url, username, password),
            )

            try:
                system_board = await qsw.validate()
            except LoginError:
                errors[CONF_PASSWORD] = "invalid_auth"
            except QswError:
                errors[CONF_URL] = "cannot_connect"
            else:
                mac = system_board.get_mac()
                if mac is None:
                    raise AbortFlow("invalid_id")

                await self.async_set_unique_id(format_mac(mac), raise_on_progress=False)
                self._abort_if_unique_id_configured()

                title = f"QNAP {system_board.get_product()} {mac}"
                return self.async_create_entry(title=title, data=user_input)

        return self.async_show_form(
            step_id="user",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_URL): str,
                    vol.Required(CONF_USERNAME): str,
                    vol.Required(CONF_PASSWORD): str,
                }
            ),
            errors=errors,
        )
Пример #23
0
    async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
        """Handle DHCP discovery."""
        self._discovered_url = f"http://{discovery_info.ip}"
        self._discovered_mac = discovery_info.macaddress

        _LOGGER.debug("DHCP discovery detected QSW: %s", self._discovered_mac)

        options = ConnectionOptions(self._discovered_url, "", "")
        qsw = QnapQswApi(aiohttp_client.async_get_clientsession(self.hass), options)

        try:
            await qsw.get_live()
        except QswError as err:
            raise AbortFlow("cannot_connect") from err

        await self.async_set_unique_id(format_mac(self._discovered_mac))
        self._abort_if_unique_id_configured()

        return await self.async_step_discovered_connection()
Пример #24
0
    async def async_set_device(self, device, raise_on_progress=True):
        """Define a device for the config flow."""
        if device.type not in DEVICE_TYPES:
            _LOGGER.error(
                "Unsupported device: %s. If it worked before, please open "
                "an issue at https://github.com/home-assistant/core/issues",
                hex(device.devtype),
            )
            raise AbortFlow("not_supported")

        await self.async_set_unique_id(device.mac.hex(),
                                       raise_on_progress=raise_on_progress)
        self.device = device

        self.context["title_placeholders"] = {
            "name": device.name,
            "model": device.model,
            "host": device.host[0],
        }
Пример #25
0
    def _async_discover_devices(self) -> None:
        current_addresses = self._async_current_ids()
        for connectable in (True, False):
            for discovery_info in async_discovered_service_info(self.hass, connectable):
                address = discovery_info.address
                if (
                    format_unique_id(address) in current_addresses
                    or address in self._discovered_advs
                ):
                    continue
                parsed = parse_advertisement_data(
                    discovery_info.device, discovery_info.advertisement
                )
                if not parsed:
                    continue
                model_name = parsed.data.get("modelName")
                if (
                    discovery_info.connectable
                    and model_name in CONNECTABLE_SUPPORTED_MODEL_TYPES
                ) or model_name in NON_CONNECTABLE_SUPPORTED_MODEL_TYPES:
                    self._discovered_advs[address] = parsed

        if not self._discovered_advs:
            raise AbortFlow("no_unconfigured_devices")
Пример #26
0
    async def async_step_zeroconf(
            self, discovery_info: zeroconf.ZeroconfServiceInfo) -> FlowResult:
        """Handle a discovered HomeKit accessory.

        This flow is triggered by the discovery component.
        """
        # Normalize properties from discovery
        # homekit_python has code to do this, but not in a form we can
        # easily use, so do the bare minimum ourselves here instead.
        properties = {
            key.lower(): value
            for (key, value) in discovery_info.properties.items()
        }

        if zeroconf.ATTR_PROPERTIES_ID not in properties:
            # This can happen if the TXT record is received after the PTR record
            # we will wait for the next update in this case
            _LOGGER.debug(
                "HomeKit device %s: id not exposed; TXT record may have not yet been received",
                properties,
            )
            return self.async_abort(reason="invalid_properties")

        # The hkid is a unique random number that looks like a pairing code.
        # It changes if a device is factory reset.
        hkid = properties[zeroconf.ATTR_PROPERTIES_ID]
        normalized_hkid = normalize_hkid(hkid)

        model = properties["md"]
        name = discovery_info.name.replace("._hap._tcp.local.", "")
        status_flags = int(properties["sf"])
        paired = not status_flags & 0x01

        # The configuration number increases every time the characteristic map
        # needs updating. Some devices use a slightly off-spec name so handle
        # both cases.
        try:
            config_num = int(properties["c#"])
        except KeyError:
            _LOGGER.warning(
                "HomeKit device %s: c# not exposed, in violation of spec",
                hkid)
            config_num = None

        # Set unique-id and error out if it's already configured
        existing_entry = await self.async_set_unique_id(
            normalized_hkid, raise_on_progress=False)
        updated_ip_port = {
            "AccessoryIP": discovery_info.host,
            "AccessoryPort": discovery_info.port,
        }

        # If the device is already paired and known to us we should monitor c#
        # (config_num) for changes. If it changes, we check for new entities
        if paired and hkid in self.hass.data.get(KNOWN_DEVICES, {}):
            if existing_entry:
                self.hass.config_entries.async_update_entry(
                    existing_entry,
                    data={
                        **existing_entry.data,
                        **updated_ip_port
                    })
            conn = self.hass.data[KNOWN_DEVICES][hkid]
            # When we rediscover the device, let aiohomekit know
            # that the device is available and we should not wait
            # to retry connecting any longer. reconnect_soon
            # will do nothing if the device is already connected
            await conn.pairing.connection.reconnect_soon()
            if conn.config_num != config_num:
                _LOGGER.debug(
                    "HomeKit info %s: c# incremented, refreshing entities",
                    hkid)
                self.hass.async_create_task(
                    conn.async_refresh_entity_map(config_num))
            return self.async_abort(reason="already_configured")

        _LOGGER.debug("Discovered device %s (%s - %s)", name, model, hkid)

        # Device isn't paired with us or anyone else.
        # But we have a 'complete' config entry for it - that is probably
        # invalid. Remove it automatically.
        existing = find_existing_host(self.hass, hkid)
        if not paired and existing:
            if self.controller is None:
                await self._async_setup_controller()

            pairing = self.controller.load_pairing(
                existing.data["AccessoryPairingID"], dict(existing.data))
            try:
                await pairing.list_accessories_and_characteristics()
            except AuthenticationError:
                _LOGGER.debug(
                    "%s (%s - %s) is unpaired. Removing invalid pairing for this device",
                    name,
                    model,
                    hkid,
                )
                await self.hass.config_entries.async_remove(existing.entry_id)
            else:
                _LOGGER.debug(
                    "%s (%s - %s) claims to be unpaired but isn't. "
                    "It's implementation of HomeKit is defective "
                    "or a zeroconf relay is broadcasting stale data",
                    name,
                    model,
                    hkid,
                )
                return self.async_abort(reason="already_paired")

        # Set unique-id and error out if it's already configured
        self._abort_if_unique_id_configured(updates=updated_ip_port)

        for progress in self._async_in_progress(include_uninitialized=True):
            if progress["context"].get("unique_id") == normalized_hkid:
                if paired:
                    # If the device gets paired, we want to dismiss
                    # an existing discovery since we can no longer
                    # pair with it
                    self.hass.config_entries.flow.async_abort(
                        progress["flow_id"])
                else:
                    raise AbortFlow("already_in_progress")

        if paired:
            # Device is paired but not to us - ignore it
            _LOGGER.debug("HomeKit device %s ignored as already paired", hkid)
            return self.async_abort(reason="already_paired")

        # Devices in HOMEKIT_IGNORE have native local integrations - users
        # should be encouraged to use native integration and not confused
        # by alternative HK API.
        if model in HOMEKIT_IGNORE:
            return self.async_abort(reason="ignored_model")

        # If this is a HomeKit bridge/accessory exported by *this* HA instance ignore it.
        if await self._hkid_is_homekit(hkid):
            return self.async_abort(reason="ignored_model")

        self.name = name
        self.model = model
        self.hkid = hkid

        # We want to show the pairing form - but don't call async_step_pair
        # directly as it has side effects (will ask the device to show a
        # pairing code)
        return self._async_step_pair_show_form()
Пример #27
0
    async def async_step_integration_discovery(
            self, discovery_info: DiscoveryInfoType) -> FlowResult:
        """Handle a discovered integration."""
        lock_cfg = ValidatedLockConfig(
            discovery_info["name"],
            discovery_info["address"],
            discovery_info["serial"],
            discovery_info["key"],
            discovery_info["slot"],
        )

        address = lock_cfg.address
        local_name = lock_cfg.local_name
        hass = self.hass

        # We do not want to raise on progress as integration_discovery takes
        # precedence over other discovery flows since we already have the keys.
        #
        # After we do discovery we will abort the flows that do not have the keys
        # below unless the user is already setting them up.
        await self.async_set_unique_id(address, raise_on_progress=False)
        new_data = {CONF_KEY: lock_cfg.key, CONF_SLOT: lock_cfg.slot}
        self._abort_if_unique_id_configured(updates=new_data)
        for entry in self._async_current_entries():
            if (local_name_is_unique(lock_cfg.local_name) and
                    entry.data.get(CONF_LOCAL_NAME) == lock_cfg.local_name):
                if hass.config_entries.async_update_entry(entry,
                                                          data={
                                                              **entry.data,
                                                              **new_data
                                                          }):
                    hass.async_create_task(
                        hass.config_entries.async_reload(entry.entry_id))
                raise AbortFlow(reason="already_configured")

        try:
            self._discovery_info = await async_get_service_info(
                hass, local_name, address)
        except asyncio.TimeoutError:
            return self.async_abort(reason="no_devices_found")

        # Integration discovery should abort other flows unless they
        # are already in the process of being set up since this discovery
        # will already have all the keys and the user can simply confirm.
        for progress in self._async_in_progress(include_uninitialized=True):
            context = progress["context"]
            if (local_name_is_unique(local_name) and context.get("local_name")
                    == local_name) or context.get("unique_id") == address:
                if context.get("active"):
                    # The user has already started interacting with this flow
                    # and entered the keys. We abort the discovery flow since
                    # we assume they do not want to use the discovered keys for
                    # some reason.
                    raise data_entry_flow.AbortFlow("already_in_progress")
                hass.config_entries.flow.async_abort(progress["flow_id"])

        self._lock_cfg = lock_cfg
        self.context["title_placeholders"] = {
            "name":
            human_readable_name(lock_cfg.name, lock_cfg.local_name,
                                self._discovery_info.address)
        }
        return await self.async_step_integration_discovery_confirm()