mirror of https://github.com/home-assistant/core
167 lines
5.5 KiB
Python
167 lines
5.5 KiB
Python
"""Config flow to configure the LG Netcast TV integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
from datetime import datetime, timedelta
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from pylgnetcast import AccessTokenError, LgNetCastClient, SessionIdError
|
|
import voluptuous as vol
|
|
|
|
from homeassistant import config_entries
|
|
from homeassistant.config_entries import ConfigFlowResult
|
|
from homeassistant.const import (
|
|
CONF_ACCESS_TOKEN,
|
|
CONF_HOST,
|
|
CONF_ID,
|
|
CONF_MODEL,
|
|
CONF_NAME,
|
|
)
|
|
from homeassistant.core import CALLBACK_TYPE, callback
|
|
from homeassistant.data_entry_flow import AbortFlow
|
|
from homeassistant.helpers.event import async_track_time_interval
|
|
from homeassistant.util.network import is_host_valid
|
|
|
|
from .const import DEFAULT_NAME, DOMAIN
|
|
from .helpers import LGNetCastDetailDiscoveryError, async_discover_netcast_details
|
|
|
|
DISPLAY_ACCESS_TOKEN_INTERVAL = timedelta(seconds=1)
|
|
|
|
|
|
class LGNetCast(config_entries.ConfigFlow, domain=DOMAIN):
|
|
"""Handle a config flow for LG Netcast TV integration."""
|
|
|
|
VERSION = 1
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize config flow."""
|
|
self.client: LgNetCastClient | None = None
|
|
self.device_config: dict[str, Any] = {}
|
|
self._discovered_devices: dict[str, Any] = {}
|
|
self._track_interval: CALLBACK_TYPE | None = None
|
|
|
|
def create_client(self) -> None:
|
|
"""Create LG Netcast client from config."""
|
|
host = self.device_config[CONF_HOST]
|
|
access_token = self.device_config.get(CONF_ACCESS_TOKEN)
|
|
self.client = LgNetCastClient(host, access_token)
|
|
|
|
async def async_step_user(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Handle the initial step."""
|
|
errors: dict[str, str] = {}
|
|
|
|
if user_input is not None:
|
|
host = user_input[CONF_HOST]
|
|
if is_host_valid(host):
|
|
self.device_config[CONF_HOST] = host
|
|
return await self.async_step_authorize()
|
|
|
|
errors[CONF_HOST] = "invalid_host"
|
|
|
|
return self.async_show_form(
|
|
step_id="user",
|
|
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
|
|
errors=errors,
|
|
)
|
|
|
|
async def async_discover_client(self):
|
|
"""Handle Discovery step."""
|
|
self.create_client()
|
|
|
|
if TYPE_CHECKING:
|
|
assert self.client is not None
|
|
|
|
if self.device_config.get(CONF_ID):
|
|
return
|
|
|
|
try:
|
|
details = await async_discover_netcast_details(self.hass, self.client)
|
|
except LGNetCastDetailDiscoveryError as err:
|
|
raise AbortFlow("cannot_connect") from err
|
|
|
|
if (unique_id := details["uuid"]) is None:
|
|
raise AbortFlow("invalid_host")
|
|
|
|
self.device_config[CONF_ID] = unique_id
|
|
self.device_config[CONF_MODEL] = details["model_name"]
|
|
|
|
if CONF_NAME not in self.device_config:
|
|
self.device_config[CONF_NAME] = details["friendly_name"] or DEFAULT_NAME
|
|
|
|
async def async_step_authorize(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Handle Authorize step."""
|
|
errors: dict[str, str] = {}
|
|
self.async_stop_display_access_token()
|
|
|
|
if user_input is not None and user_input.get(CONF_ACCESS_TOKEN) is not None:
|
|
self.device_config[CONF_ACCESS_TOKEN] = user_input[CONF_ACCESS_TOKEN]
|
|
|
|
await self.async_discover_client()
|
|
assert self.client is not None
|
|
|
|
await self.async_set_unique_id(self.device_config[CONF_ID])
|
|
self._abort_if_unique_id_configured(
|
|
updates={CONF_HOST: self.device_config[CONF_HOST]}
|
|
)
|
|
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
self.client._get_session_id # noqa: SLF001
|
|
)
|
|
except AccessTokenError:
|
|
if user_input is not None:
|
|
errors[CONF_ACCESS_TOKEN] = "invalid_access_token"
|
|
except SessionIdError:
|
|
errors["base"] = "cannot_connect"
|
|
else:
|
|
return await self.async_create_device()
|
|
|
|
self._track_interval = async_track_time_interval(
|
|
self.hass,
|
|
self.async_display_access_token,
|
|
DISPLAY_ACCESS_TOKEN_INTERVAL,
|
|
cancel_on_shutdown=True,
|
|
)
|
|
|
|
return self.async_show_form(
|
|
step_id="authorize",
|
|
data_schema=vol.Schema(
|
|
{
|
|
vol.Optional(CONF_ACCESS_TOKEN): vol.All(str, vol.Length(max=6)),
|
|
}
|
|
),
|
|
errors=errors,
|
|
)
|
|
|
|
async def async_display_access_token(self, _: datetime | None = None):
|
|
"""Display access token on screen."""
|
|
assert self.client is not None
|
|
with contextlib.suppress(AccessTokenError, SessionIdError):
|
|
await self.hass.async_add_executor_job(
|
|
self.client._get_session_id # noqa: SLF001
|
|
)
|
|
|
|
@callback
|
|
def async_remove(self):
|
|
"""Terminate Access token display if flow is removed."""
|
|
self.async_stop_display_access_token()
|
|
|
|
def async_stop_display_access_token(self):
|
|
"""Stop Access token request if running."""
|
|
if self._track_interval is not None:
|
|
self._track_interval()
|
|
self._track_interval = None
|
|
|
|
async def async_create_device(self) -> ConfigFlowResult:
|
|
"""Create LG Netcast TV Device from config."""
|
|
assert self.client
|
|
|
|
return self.async_create_entry(
|
|
title=self.device_config[CONF_NAME], data=self.device_config
|
|
)
|