Files
media-player-mixed/haos-integration/custom_components/remote_media_player/config_flow.py
alexei.dolgolyov e26df64e4b Refactor project into two standalone components
Split monorepo into separate units for future independent repositories:
- media-server/: Standalone FastAPI server with own README, requirements,
  config example, and CLAUDE.md
- haos-integration/: HACS-ready Home Assistant integration with hacs.json,
  own README, and CLAUDE.md

Both components now have their own .gitignore files and can be easily
extracted into separate repositories.

Also adds custom icon support for scripts configuration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:36:23 +03:00

225 lines
6.7 KiB
Python

"""Config flow for Remote Media Player integration."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import selector
from .api_client import (
MediaServerClient,
MediaServerConnectionError,
MediaServerAuthError,
)
from .const import (
DOMAIN,
CONF_TOKEN,
CONF_POLL_INTERVAL,
DEFAULT_PORT,
DEFAULT_POLL_INTERVAL,
DEFAULT_NAME,
)
_LOGGER = logging.getLogger(__name__)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Args:
hass: Home Assistant instance
data: User input data
Returns:
Validated data with title
Raises:
CannotConnect: If connection fails
InvalidAuth: If authentication fails
"""
client = MediaServerClient(
host=data[CONF_HOST],
port=data[CONF_PORT],
token=data[CONF_TOKEN],
)
try:
health = await client.get_health()
# Try authenticated endpoint
await client.get_status()
except MediaServerConnectionError as err:
await client.close()
raise CannotConnect(str(err)) from err
except MediaServerAuthError as err:
await client.close()
raise InvalidAuth(str(err)) from err
finally:
await client.close()
# Return info to store in the config entry
return {
"title": data.get(CONF_NAME, DEFAULT_NAME),
"platform": health.get("platform", "Unknown"),
}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Remote Media Player."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step.
Args:
user_input: User provided configuration
Returns:
Flow result
"""
errors: dict[str, str] = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
# Check if already configured
await self.async_set_unique_id(
f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=info["title"],
data=user_input,
)
# Show configuration form
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
),
vol.Required(CONF_PORT, default=DEFAULT_PORT): selector.NumberSelector(
selector.NumberSelectorConfig(
min=1,
max=65535,
mode=selector.NumberSelectorMode.BOX,
)
),
vol.Required(CONF_TOKEN): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.PASSWORD
)
),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
),
vol.Optional(
CONF_POLL_INTERVAL, default=DEFAULT_POLL_INTERVAL
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=1,
max=60,
step=1,
unit_of_measurement="seconds",
mode=selector.NumberSelectorMode.SLIDER,
)
),
}
),
errors=errors,
)
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow.
Args:
config_entry: Config entry
Returns:
Options flow handler
"""
return OptionsFlowHandler(config_entry)
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle options flow for Remote Media Player."""
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow.
Args:
config_entry: Config entry
"""
self._config_entry = config_entry
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options.
Args:
user_input: User provided options
Returns:
Flow result
"""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
CONF_POLL_INTERVAL,
default=self._config_entry.options.get(
CONF_POLL_INTERVAL,
self._config_entry.data.get(
CONF_POLL_INTERVAL, DEFAULT_POLL_INTERVAL
),
),
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=1,
max=60,
step=1,
unit_of_measurement="seconds",
mode=selector.NumberSelectorMode.SLIDER,
)
),
}
),
)
class CannotConnect(Exception):
"""Error to indicate we cannot connect."""
class InvalidAuth(Exception):
"""Error to indicate there is invalid auth."""