Files
denon-bluetooth/custom_components/denon_bt/config_flow.py
T
ckoch a5d2865eb2 Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:59:47 -04:00

107 lines
3.5 KiB
Python

"""Config flow for Denon AVR Bluetooth integration."""
from __future__ import annotations
import logging
import re
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.data_entry_flow import FlowResult
from .const import DOMAIN
from .protocol import find_denon_devices
_LOGGER = logging.getLogger(__name__)
_MAC_RE = re.compile(r"^([0-9A-F]{2}:){5}[0-9A-F]{2}$", re.IGNORECASE)
MANUAL_SCHEMA = vol.Schema(
{
vol.Required("mac"): str,
vol.Optional("name", default="Denon AVR-S540BT"): str,
}
)
class DenonBTConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Denon AVR Bluetooth."""
VERSION = 1
def __init__(self) -> None:
self._discovered: list[dict[str, Any]] = []
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Entry point — try D-Bus discovery, fall back to manual."""
self._discovered = await self.hass.async_add_executor_job(
find_denon_devices
)
if self._discovered:
return await self.async_step_select()
return await self.async_step_manual()
# ── Device selection ─────────────────────────────────────────────────────
async def async_step_select(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Let the user pick a discovered device or go manual."""
if user_input is not None:
chosen = user_input["device"]
if chosen == "_manual":
return await self.async_step_manual()
for dev in self._discovered:
if dev["mac"] == chosen:
await self.async_set_unique_id(dev["mac"].upper())
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=dev["name"],
data={"mac": dev["mac"], "name": dev["name"]},
)
options = {
dev["mac"]: f"{dev['name']} ({dev['mac']})"
for dev in self._discovered
}
options["_manual"] = "Enter MAC address manually\u2026"
return self.async_show_form(
step_id="select",
data_schema=vol.Schema(
{vol.Required("device"): vol.In(options)}
),
)
# ── Manual MAC entry ─────────────────────────────────────────────────────
async def async_step_manual(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle manual MAC address entry."""
errors: dict[str, str] = {}
if user_input is not None:
mac = user_input["mac"].strip().upper()
name = user_input.get("name", "Denon AVR")
if not _MAC_RE.match(mac):
errors["base"] = "invalid_mac"
else:
await self.async_set_unique_id(mac)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=name,
data={"mac": mac, "name": name},
)
return self.async_show_form(
step_id="manual",
data_schema=MANUAL_SCHEMA,
errors=errors,
)