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:
1 parent
a65c893339
commit
d35b61078d
5 files changed
+359
-32
No files matched your search
@@ -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, ""),
|
||||
|
||||
Reference in new issue
Block a user