feat(config_flow): standalone setup — androidtv entity optional with no_state_source validation, entry data omits the field for standalone entries, sensor tolerates its absence; migration keeps v1 field / v2 absence valid (P3-1a,d)

This commit is contained in:
James Mackay committed 2026-10-08 10:28:56 +01:00
1 parent a65c893339
commit d35b61078d
5 files changed
+359 -32

No files matched your search

+50 -25
View File
@@ -75,6 +75,30 @@ 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)."""
schema_dict = {
vol.Required("name", default=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"],
}
# If there are any receivers with DAG input maps, show a text field
# for receiver input (will be a dropdown in options once receiver is set)
schema_dict[vol.Optional(CONF_RECEIVER_INPUT)] = str
schema_dict[vol.Optional(CONF_POWER_ENTITY)] = sel["power"]
schema_dict[vol.Optional(CONF_ADB_ENABLED, default=False)] = bool
schema_dict[vol.Optional(CONF_ADB_STORE_APP_INFO, default=True)] = bool
schema_dict[vol.Optional(CONF_ADB_HOST)] = str
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
return schema_dict
def _build_selectors():
"""Build reusable selector instances."""
return {
@@ -108,16 +132,32 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
androidtv_entity = user_input[CONF_ANDROIDTV_ENTITY]
# Standalone setup (Phase 3): the external androidtv media_player
# is optional — the protocol push can be the only state source.
androidtv_entity = user_input.get(CONF_ANDROIDTV_ENTITY)
await self.async_set_unique_id(androidtv_entity)
self._abort_if_unique_id_configured()
if not androidtv_entity and not user_input.get(CONF_PROTOCOL_ENABLED):
errors["base"] = "no_state_source"
sel = _build_selectors()
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(_user_schema_dict(sel)),
errors=errors,
)
if androidtv_entity:
await self.async_set_unique_id(androidtv_entity)
self._abort_if_unique_id_configured()
# Auto-detect Jellyfin if not manually specified
jellyfin = user_input.get(CONF_JELLYFIN_ENTITY)
if not jellyfin:
device_name = user_input.get("name", DEFAULT_NAME)
atv_state = self.hass.states.get(androidtv_entity)
atv_state = (
self.hass.states.get(androidtv_entity)
if androidtv_entity
else None
)
atv_name = (atv_state.attributes.get("friendly_name", "") if atv_state else "")
for name in [device_name, atv_name]:
@@ -131,7 +171,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._user_data = {
"name": user_input.get("name", DEFAULT_NAME),
CONF_ANDROIDTV_ENTITY: androidtv_entity,
CONF_ANDROIDTV_ENTITY: androidtv_entity or "",
CONF_JELLYFIN_ENTITY: jellyfin or "",
CONF_RECEIVER_ENTITY: user_input.get(CONF_RECEIVER_ENTITY, ""),
CONF_RECEIVER_INPUT: user_input.get(CONF_RECEIVER_INPUT, ""),
@@ -156,25 +196,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
sel = _build_selectors()
schema_dict = {
vol.Required("name", default=DEFAULT_NAME): str,
vol.Required(CONF_ANDROIDTV_ENTITY): sel["androidtv"],
vol.Optional(CONF_JELLYFIN_ENTITY): sel["media_player"],
vol.Optional(CONF_RECEIVER_ENTITY): sel["receiver"],
}
# If there are any receivers with DAG input maps, show a text field
# for receiver input (will be a dropdown in options once receiver is set)
schema_dict[vol.Optional(CONF_RECEIVER_INPUT)] = str
schema_dict[vol.Optional(CONF_POWER_ENTITY)] = sel["power"]
schema_dict[vol.Optional(CONF_ADB_ENABLED, default=False)] = bool
schema_dict[vol.Optional(CONF_ADB_STORE_APP_INFO, default=True)] = bool
schema_dict[vol.Optional(CONF_ADB_HOST)] = str
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
schema_dict = _user_schema_dict(sel)
return self.async_show_form(
step_id="user",
@@ -185,9 +207,12 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def _create_entry(self):
"""Create the config entry from stored user data."""
d = self._user_data
entry_data = {}
if d.get(CONF_ANDROIDTV_ENTITY):
entry_data[CONF_ANDROIDTV_ENTITY] = d[CONF_ANDROIDTV_ENTITY]
return self.async_create_entry(
title=d["name"],
data={CONF_ANDROIDTV_ENTITY: d[CONF_ANDROIDTV_ENTITY]},
data=entry_data,
options={
CONF_JELLYFIN_ENTITY: d.get(CONF_JELLYFIN_ENTITY, ""),
CONF_RECEIVER_ENTITY: d.get(CONF_RECEIVER_ENTITY, ""),