"""Flux de configuration par interface utilisateur de l'intégration Arkteos.""" from __future__ import annotations import asyncio from typing import Any import voluptuous as vol from homeassistant import config_entries from homeassistant.data_entry_flow import FlowResult from .client import ArkteosClient from .const import CONF_HOST, CONF_PORT, DEFAULT_PORT, DOMAIN VALIDATION_TIMEOUT = 10.0 class CannotConnect(Exception): """La connexion au proxy a échoué.""" class ValidationTimeout(Exception): """Aucune trame valide n'a été reçue dans le délai imparti.""" class ArkteosConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Gère la création et la reconfiguration des config entries Arkteos.""" VERSION = 1 async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult: """Affiche et traite le formulaire initial.""" if user_input is not None: data, errors = await self._async_validate_input(user_input) if not errors: if self._entry_exists(data[CONF_HOST], data[CONF_PORT]): return self.async_abort(reason="already_configured") await self.async_set_unique_id(self._unique_id(data)) self._abort_if_unique_id_configured() try: await self._async_validate_proxy(data) except CannotConnect: errors["base"] = "cannot_connect" except ValidationTimeout: errors["base"] = "timeout" except Exception: errors["base"] = "unknown" else: return self.async_create_entry(title=self._title(data), data=data) return self.async_show_form( step_id="user", data_schema=self._schema(data if "data" in locals() else None), errors=errors ) return self.async_show_form(step_id="user", data_schema=self._schema()) async def async_step_reconfigure( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Permet la modification de l'hôte et du port d'une entrée existante.""" entry = self._get_reconfigure_entry() if user_input is not None: data, errors = await self._async_validate_input(user_input) if not errors: if self._entry_exists(data[CONF_HOST], data[CONF_PORT], exclude_entry_id=entry.entry_id): errors["base"] = "already_configured" else: try: await self._async_validate_proxy(data) except CannotConnect: errors["base"] = "cannot_connect" except ValidationTimeout: errors["base"] = "timeout" except Exception: errors["base"] = "unknown" else: return self.async_update_reload_and_abort( entry, data_updates=data, title=self._title(data) ) return self.async_show_form( step_id="reconfigure", data_schema=self._schema(data if "data" in locals() else entry.data), errors=errors ) return self.async_show_form(step_id="reconfigure", data_schema=self._schema(entry.data)) async def _async_validate_input( self, user_input: dict[str, Any] ) -> tuple[dict[str, Any], dict[str, str]]: errors: dict[str, str] = {} host = str(user_input.get(CONF_HOST, "")).strip() if not host: errors[CONF_HOST] = "invalid_host" try: port = int(user_input.get(CONF_PORT, DEFAULT_PORT)) except (TypeError, ValueError): errors[CONF_PORT] = "invalid_port" port = DEFAULT_PORT if not 1 <= port <= 65535: errors[CONF_PORT] = "invalid_port" return {CONF_HOST: host, CONF_PORT: port}, errors async def _async_validate_proxy(self, data: dict[str, Any]) -> None: client = ArkteosClient(data[CONF_HOST], data[CONF_PORT]) try: await client.start() if not await client.wait_until_connected(VALIDATION_TIMEOUT): raise CannotConnect deadline = asyncio.get_running_loop().time() + VALIDATION_TIMEOUT while client.last_valid_frame is None: if asyncio.get_running_loop().time() >= deadline: raise ValidationTimeout if client.last_error is not None and not client.connected: raise CannotConnect await asyncio.sleep(0.05) finally: await client.stop() def _entry_exists(self, host: str, port: int, exclude_entry_id: str | None = None) -> bool: return any( entry.entry_id != exclude_entry_id and entry.data.get(CONF_HOST) == host and entry.data.get(CONF_PORT) == port for entry in self._async_current_entries() ) @staticmethod def _title(data: dict[str, Any]) -> str: return f"Arkteos {data[CONF_HOST]}:{data[CONF_PORT]}" @staticmethod def _unique_id(data: dict[str, Any]) -> str: return f"{data[CONF_HOST]}:{data[CONF_PORT]}" @staticmethod def _schema(defaults: dict[str, Any] | None = None) -> vol.Schema: defaults = defaults or {} return vol.Schema( { vol.Required(CONF_HOST, default=defaults.get(CONF_HOST, "")): str, vol.Required(CONF_PORT, default=defaults.get(CONF_PORT, DEFAULT_PORT)): vol.Coerce(int), } )