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
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _user_schema_dict(sel) -> dict:
|
def _user_schema_dict(sel, defaults: dict | None = None) -> dict:
|
||||||
"""User-step schema — the androidtv entity is optional (standalone)."""
|
"""User-step schema — the androidtv entity is optional (standalone).
|
||||||
|
|
||||||
|
``defaults`` pre-fills name/hosts from a zeroconf discovery event.
|
||||||
|
"""
|
||||||
|
defaults = defaults or {}
|
||||||
schema_dict = {
|
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_ANDROIDTV_ENTITY): sel["androidtv"],
|
||||||
vol.Optional(CONF_JELLYFIN_ENTITY): sel["media_player"],
|
vol.Optional(CONF_JELLYFIN_ENTITY): sel["media_player"],
|
||||||
vol.Optional(CONF_RECEIVER_ENTITY): sel["receiver"],
|
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_ADB_PORT, default=DEFAULT_ADB_PORT)] = int
|
||||||
schema_dict[vol.Optional(CONF_PROTOCOL_ENABLED, default=False)] = bool
|
schema_dict[vol.Optional(CONF_PROTOCOL_ENABLED, default=False)] = bool
|
||||||
schema_dict[vol.Optional(CONF_PROTOCOL_HOST)] = str
|
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
|
return schema_dict
|
||||||
|
|
||||||
|
|
||||||
@@ -126,6 +133,9 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._user_data = {}
|
self._user_data = {}
|
||||||
self._protocol_client = None
|
self._protocol_client = None
|
||||||
self._reauth_entry = 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):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Step 1: Basic setup.
|
"""Step 1: Basic setup.
|
||||||
@@ -205,7 +215,13 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
sel = _build_selectors()
|
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(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
@@ -213,6 +229,47 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors=errors,
|
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):
|
def _create_entry(self):
|
||||||
"""Create the config entry from stored user data."""
|
"""Create the config entry from stored user data."""
|
||||||
d = self._user_data
|
d = self._user_data
|
||||||
|
|||||||
Reference in new issue
Block a user