146 lines
5.6 KiB
Python
146 lines
5.6 KiB
Python
"""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),
|
|
}
|
|
)
|