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:
1 parent
8b4fea9a5c
commit
1dbb695cec
1 file changed
+61
-4
@@ -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
|
||||
|
||||
Reference in new issue
Block a user