a5d2865eb2
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
107 lines
3.5 KiB
Python
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,
|
|
)
|