core/homeassistant/components/webostv/config_flow.py

198 lines
7.1 KiB
Python

"""Config flow to configure webostv component."""
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
from urllib.parse import urlparse
from aiowebostv import WebOsTvPairError
import voluptuous as vol
from homeassistant.components import ssdp
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST, CONF_NAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow, FlowResult
from homeassistant.helpers import config_validation as cv
from . import async_control_connect, update_client_key
from .const import CONF_SOURCES, DEFAULT_NAME, DOMAIN, WEBOSTV_EXCEPTIONS
from .helpers import async_get_sources
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
},
extra=vol.ALLOW_EXTRA,
)
_LOGGER = logging.getLogger(__name__)
class FlowHandler(ConfigFlow, domain=DOMAIN):
"""WebosTV configuration flow."""
VERSION = 1
def __init__(self) -> None:
"""Initialize workflow."""
self._host: str = ""
self._name: str = ""
self._uuid: str | None = None
self._entry: ConfigEntry | None = None
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input is not None:
self._host = user_input[CONF_HOST]
self._name = user_input[CONF_NAME]
return await self.async_step_pairing()
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
@callback
def _async_check_configured_entry(self) -> None:
"""Check if entry is configured, update unique_id if needed."""
for entry in self._async_current_entries(include_ignore=False):
if entry.data[CONF_HOST] != self._host:
continue
if self._uuid and not entry.unique_id:
_LOGGER.debug(
"Updating unique_id for host %s, unique_id: %s",
self._host,
self._uuid,
)
self.hass.config_entries.async_update_entry(entry, unique_id=self._uuid)
raise AbortFlow("already_configured")
async def async_step_pairing(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Display pairing form."""
self._async_check_configured_entry()
self.context[CONF_HOST] = self._host
self.context["title_placeholders"] = {"name": self._name}
errors = {}
if user_input is not None:
try:
client = await async_control_connect(self._host, None)
except WebOsTvPairError:
return self.async_abort(reason="error_pairing")
except WEBOSTV_EXCEPTIONS:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(
client.hello_info["deviceUUID"], raise_on_progress=False
)
self._abort_if_unique_id_configured({CONF_HOST: self._host})
data = {CONF_HOST: self._host, CONF_CLIENT_SECRET: client.client_key}
return self.async_create_entry(title=self._name, data=data)
return self.async_show_form(step_id="pairing", errors=errors)
async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
"""Handle a flow initialized by discovery."""
assert discovery_info.ssdp_location
host = urlparse(discovery_info.ssdp_location).hostname
assert host
self._host = host
self._name = discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, DEFAULT_NAME)
uuid = discovery_info.upnp[ssdp.ATTR_UPNP_UDN]
assert uuid
if uuid.startswith("uuid:"):
uuid = uuid[5:]
await self.async_set_unique_id(uuid)
self._abort_if_unique_id_configured({CONF_HOST: self._host})
for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_HOST) == self._host:
return self.async_abort(reason="already_in_progress")
self._uuid = uuid
return await self.async_step_pairing()
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Perform reauth upon an WebOsTvPairError."""
self._host = entry_data[CONF_HOST]
self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Dialog that informs the user that reauth is required."""
assert self._entry is not None
if user_input is not None:
try:
client = await async_control_connect(self._host, None)
except WebOsTvPairError:
return self.async_abort(reason="error_pairing")
except WEBOSTV_EXCEPTIONS:
return self.async_abort(reason="reauth_unsuccessful")
update_client_key(self.hass, self._entry, client)
await self.hass.config_entries.async_reload(self._entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(step_id="reauth_confirm")
class OptionsFlowHandler(OptionsFlow):
"""Handle options."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
self.options = config_entry.options
self.host = config_entry.data[CONF_HOST]
self.key = config_entry.data[CONF_CLIENT_SECRET]
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
errors = {}
if user_input is not None:
options_input = {CONF_SOURCES: user_input[CONF_SOURCES]}
return self.async_create_entry(title="", data=options_input)
# Get sources
sources_list = await async_get_sources(self.host, self.key)
if not sources_list:
errors["base"] = "cannot_retrieve"
sources = [s for s in self.options.get(CONF_SOURCES, []) if s in sources_list]
if not sources:
sources = sources_list
options_schema = vol.Schema(
{
vol.Optional(
CONF_SOURCES,
description={"suggested_value": sources},
): cv.multi_select({source: source for source in sources_list}),
}
)
return self.async_show_form(
step_id="init", data_schema=options_schema, errors=errors
)