feat(config_flow): zeroconf discovery step — pre-filled user step from the discovery address, duplicate-MAC abort, IP-change host update + reload for known entries (P4-1a)

This commit is contained in:
James Mackay committed 2026-10-08 10:57:31 +01:00
1 parent 8b4fea9a5c
commit 1dbb695cec
1 file changed
+61 -4
+61 -4
View File
@@ -75,10 +75,14 @@ def _find_jellyfin_entity(hass, device_name: str) -> str | None:
return None
def _user_schema_dict(sel) -> dict:
"""User-step schema — the androidtv entity is optional (standalone)."""
def _user_schema_dict(sel, defaults: dict | None = None) -> dict:
"""User-step schema — the androidtv entity is optional (standalone).
``defaults`` pre-fills name/hosts from a zeroconf discovery event.
"""
defaults = defaults or {}
schema_dict = {
vol.Required("name", default=DEFAULT_NAME): str,
vol.Required("name", default=defaults.get("name", DEFAULT_NAME)): str,
vol.Optional(CONF_ANDROIDTV_ENTITY): sel["androidtv"],
vol.Optional(CONF_JELLYFIN_ENTITY): sel["media_player"],
vol.Optional(CONF_RECEIVER_ENTITY): sel["receiver"],
@@ -96,6 +100,9 @@ def _user_schema_dict(sel) -> dict:
schema_dict[vol.Optional(CONF_ADB_PORT, default=DEFAULT_ADB_PORT)] = int
schema_dict[vol.Optional(CONF_PROTOCOL_ENABLED, default=False)] = bool
schema_dict[vol.Optional(CONF_PROTOCOL_HOST)] = str
if "host" in defaults:
schema_dict[vol.Optional(CONF_ADB_HOST, default=defaults["host"])] = str
schema_dict[vol.Optional(CONF_PROTOCOL_HOST, default=defaults["host"])] = str
return schema_dict
@@ -126,6 +133,9 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._user_data = {}
self._protocol_client = None
self._reauth_entry = None
# Zeroconf discovery state (P4-1)
self._discovered_host: str | None = None
self._discovered_name: str | None = None
async def async_step_user(self, user_input=None):
"""Step 1: Basic setup.
@@ -205,7 +215,13 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
sel = _build_selectors()
schema_dict = _user_schema_dict(sel)
defaults = {}
if self._discovered_host:
defaults = {
"name": self._discovered_name or DEFAULT_NAME,
"host": self._discovered_host,
}
schema_dict = _user_schema_dict(sel, defaults)
return self.async_show_form(
step_id="user",
@@ -213,6 +229,47 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_zeroconf(self, discovery_info):
"""Zeroconf discovery of the Android TV remote service (P4-1).
Core androidtv_remote pattern, simplified: the discovery address
becomes the pre-filled protocol/ADB host on the user step; a known
MAC (from the service properties) aborts duplicate setup and
updates the existing entry's hosts on an IP change.
"""
host = str(
getattr(discovery_info, "ip_address", None)
or getattr(discovery_info, "host", None)
or ""
).strip()
if not host:
return self.async_abort(reason="zeroconf_no_host")
self._discovered_host = host
props = getattr(discovery_info, "properties", None) or {}
self._discovered_name = props.get("name")
mac = props.get("mac") or props.get("unique_id")
if mac:
await self.async_set_unique_id(mac)
for entry in self._async_current_entries():
if getattr(entry, "unique_id", None) != mac:
continue
# Known device — update hosts on IP change (core pattern)
options = {
**entry.options,
CONF_PROTOCOL_HOST: host,
CONF_ADB_HOST: host,
}
if options != entry.options:
self.hass.config_entries.async_update_entry(entry, options=options)
self.hass.config_entries.async_schedule_reload(entry.entry_id)
return self.async_abort(reason="already_configured")
self._abort_if_unique_id_configured()
# First-time setup: pre-filled user step
return await self.async_step_user()
def _create_entry(self):
"""Create the config entry from stored user data."""
d = self._user_data