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, ""),
|
||||
|
||||
@@ -73,7 +73,7 @@ class NvidiaShieldCurrentAppSensor(NvidiaShieldSensorBase):
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
super().__init__(hass, entry)
|
||||
self._androidtv_entity = entry.data[CONF_ANDROIDTV_ENTITY]
|
||||
self._androidtv_entity = entry.data.get(CONF_ANDROIDTV_ENTITY, "")
|
||||
self._attr_unique_id = f"{DOMAIN}_{entry.entry_id}_current_app"
|
||||
|
||||
def _manager(self) -> ShieldConnectionManager | None:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"description": "Set up your NVIDIA Shield. Link the Android TV remote, Jellyfin, receiver, and power monitoring.",
|
||||
"data": {
|
||||
"name": "Device Name",
|
||||
"androidtv_entity": "Android TV Remote",
|
||||
"androidtv_entity": "The media_player entity from the Android TV Remote integration. Optional for standalone setups that pair with the Nvidia Remote Protocol instead.",
|
||||
"jellyfin_entity": "Jellyfin Media Player",
|
||||
"receiver_entity": "Receiver",
|
||||
"receiver_input": "Receiver Input",
|
||||
@@ -53,7 +53,8 @@
|
||||
"adb_no_host": "Could not determine the Shield's IP address. Set the ADB Host field on the previous step.",
|
||||
"protocol_no_host": "Could not determine the Shield's IP address. Set the Protocol Host or ADB Host field.",
|
||||
"protocol_invalid_code": "Pairing has not started or the code is empty. Click Submit to retry.",
|
||||
"protocol_pair_failed": "The Shield rejected that pairing code. Click Submit to try again."
|
||||
"protocol_pair_failed": "The Shield rejected that pairing code. Click Submit to try again.",
|
||||
"no_state_source": "No state source selected. Link the Android TV Remote entity or enable the Nvidia Remote Protocol pairing \u2014 the integration needs at least one source of power/app state."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "This Shield entity is already configured.",
|
||||
@@ -77,7 +78,7 @@
|
||||
"adb_port": "ADB Port"
|
||||
},
|
||||
"data_description": {
|
||||
"jellyfin_entity": "The Jellyfin media_player for this device. Session stream info is read from the HA Jellyfin integration's existing coordinator — no extra config needed.",
|
||||
"jellyfin_entity": "The Jellyfin media_player for this device. Session stream info is read from the HA Jellyfin integration's existing coordinator \u2014 no extra config needed.",
|
||||
"receiver_entity": "The receiver this device is connected to.",
|
||||
"receiver_input": "Which input on the receiver.",
|
||||
"power_entity": "Smart plug switch for reliable on/off detection.",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"description": "Set up your NVIDIA Shield. Link the Android TV remote, Jellyfin, receiver, and power monitoring.",
|
||||
"data": {
|
||||
"name": "Device Name",
|
||||
"androidtv_entity": "Android TV Remote",
|
||||
"androidtv_entity": "The media_player entity from the Android TV Remote integration. Optional for standalone setups that pair with the Nvidia Remote Protocol instead.",
|
||||
"jellyfin_entity": "Jellyfin Media Player",
|
||||
"receiver_entity": "Receiver",
|
||||
"receiver_input": "Receiver Input",
|
||||
@@ -53,7 +53,8 @@
|
||||
"adb_no_host": "Could not determine the Shield's IP address. Set the ADB Host field on the previous step.",
|
||||
"protocol_no_host": "Could not determine the Shield's IP address. Set the Protocol Host or ADB Host field.",
|
||||
"protocol_invalid_code": "Pairing has not started or the code is empty. Click Submit to retry.",
|
||||
"protocol_pair_failed": "The Shield rejected that pairing code. Click Submit to try again."
|
||||
"protocol_pair_failed": "The Shield rejected that pairing code. Click Submit to try again.",
|
||||
"no_state_source": "No state source selected. Link the Android TV Remote entity or enable the Nvidia Remote Protocol pairing \u2014 the integration needs at least one source of power/app state."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "This Shield entity is already configured.",
|
||||
@@ -77,7 +78,7 @@
|
||||
"adb_port": "ADB Port"
|
||||
},
|
||||
"data_description": {
|
||||
"jellyfin_entity": "The Jellyfin media_player for this device. Session stream info is read from the HA Jellyfin integration's existing coordinator — no extra config needed.",
|
||||
"jellyfin_entity": "The Jellyfin media_player for this device. Session stream info is read from the HA Jellyfin integration's existing coordinator \u2014 no extra config needed.",
|
||||
"receiver_entity": "The receiver this device is connected to.",
|
||||
"receiver_input": "Which input on the receiver.",
|
||||
"power_entity": "Smart plug switch for reliable on/off detection.",
|
||||
|
||||
Reference in new issue
Block a user