feat(config_flow): the G2 unique_id fix lands — the androidtv entity_id is never a unique_id; MAC derived from the device registry mac connection (empty placeholders skipped), else no unique_id; rename-stability + absence pinned by test (t29 G2)
This commit is contained in:
1 parent
086c089cc1
commit
e1a0fc3039
5 files changed
+229
-71
No files matched your search
@@ -13,7 +13,6 @@ from .adb_session import ensure_adb_key
|
||||
from .connection import ShieldConnectionManager
|
||||
from .const import (
|
||||
ATTR_MEDIA_DAG_INPUT_MAP,
|
||||
CHANNEL_DIGIT_DELAY,
|
||||
CONF_ADB_COMMAND_ENABLED,
|
||||
CONF_ADB_ENABLED,
|
||||
CONF_ADB_HOST,
|
||||
@@ -145,15 +144,15 @@ def _user_schema_dict(sel, defaults: dict | None = None) -> dict:
|
||||
|
||||
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_ENABLED)] = bool
|
||||
schema_dict[vol.Optional(CONF_ADB_STORE_APP_INFO)] = 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_ADB_PORT)] = int
|
||||
schema_dict[vol.Optional(CONF_PROTOCOL_ENABLED)] = 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
|
||||
schema_dict[vol.Optional(CONF_ADB_HOST)] = str
|
||||
schema_dict[vol.Optional(CONF_PROTOCOL_HOST)] = str
|
||||
return schema_dict
|
||||
|
||||
|
||||
@@ -515,15 +514,30 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
return NvidiaShieldOptionsFlow(config_entry)
|
||||
return NvidiaShieldOptionsFlow()
|
||||
|
||||
|
||||
class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
|
||||
"""Handle options for Nvidia Shield — single unified page."""
|
||||
class NvidiaShieldOptionsFlow(config_entries.OptionsFlowWithReload):
|
||||
"""Handle options for Nvidia Shield — single unified page (t29 G3b).
|
||||
|
||||
def __init__(self, config_entry):
|
||||
self._config_entry = config_entry
|
||||
self._options = dict(config_entry.options)
|
||||
The constructor takes NO arguments: HA assigns ``self.config_entry``
|
||||
on instantiation (the pre-2026 "OptionsFlow(config_entry)" pattern is
|
||||
deprecated). Suggested values come through
|
||||
``add_suggested_values_to_schema``, and saving reloads via the
|
||||
OptionsFlowWithReload base — no listener boomerangs.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._options: dict | None = None
|
||||
|
||||
@property
|
||||
def _options_dict(self) -> dict:
|
||||
if self._options is None:
|
||||
if self.config_entry is not None:
|
||||
self._options = dict(self.config_entry.options)
|
||||
else:
|
||||
self._options = {}
|
||||
return self._options
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Single-page options."""
|
||||
@@ -542,7 +556,7 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
|
||||
app_map_text = user_input.pop(CONF_APP_NAME_MAP, None)
|
||||
icons: dict[str, str] = {}
|
||||
if app_map_text is None:
|
||||
app_map = self._options.get(CONF_APP_NAME_MAP, {})
|
||||
app_map = self._options_dict.get(CONF_APP_NAME_MAP, {})
|
||||
elif app_map_text.strip() == "":
|
||||
app_map = {}
|
||||
else:
|
||||
@@ -551,14 +565,14 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
|
||||
errors["base"] = "invalid_app_map"
|
||||
if not errors:
|
||||
if delay is not None:
|
||||
self._options[CONF_CHANNEL_DIGIT_DELAY] = delay
|
||||
self._options.update(user_input)
|
||||
self._options[CONF_APP_NAME_MAP] = app_map
|
||||
self._options_dict[CONF_CHANNEL_DIGIT_DELAY] = delay
|
||||
self._options_dict.update(user_input)
|
||||
self._options_dict[CONF_APP_NAME_MAP] = app_map
|
||||
# P5-2: the icon column — the editor is the source of
|
||||
# truth, so a save without icons clears them (and a
|
||||
# single-column map stays valid).
|
||||
self._options[CONF_APP_ICON_MAP] = icons
|
||||
return self.async_create_entry(title="", data=self._options)
|
||||
self._options_dict[CONF_APP_ICON_MAP] = icons
|
||||
return self.async_create_entry(title="", data=self._options_dict)
|
||||
|
||||
sel = _build_selectors()
|
||||
schema_dict = {}
|
||||
@@ -566,46 +580,36 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
|
||||
# Apps name/icon map editor (P4-1, core androidtv_remote options
|
||||
# pattern, simplified): one package=name[|icon] per line (P5-2 adds
|
||||
# the optional icon column).
|
||||
current_map = self._options.get(CONF_APP_NAME_MAP, {})
|
||||
current_icons = self._options.get(CONF_APP_ICON_MAP, {})
|
||||
if current_map:
|
||||
app_map_default = _app_map_to_text(current_map, current_icons)
|
||||
schema_dict[vol.Optional(CONF_APP_NAME_MAP, default=app_map_default)] = str
|
||||
else:
|
||||
schema_dict[vol.Optional(CONF_APP_NAME_MAP)] = str
|
||||
schema_dict[vol.Optional(CONF_APP_NAME_MAP)] = str
|
||||
|
||||
# Jellyfin
|
||||
jf = self._options.get(CONF_JELLYFIN_ENTITY)
|
||||
jf = self._options_dict.get(CONF_JELLYFIN_ENTITY)
|
||||
jf_key = vol.Optional(CONF_JELLYFIN_ENTITY)
|
||||
if jf:
|
||||
jf_key = vol.Optional(CONF_JELLYFIN_ENTITY, default=jf)
|
||||
jf_key = vol.Optional(CONF_JELLYFIN_ENTITY)
|
||||
schema_dict[jf_key] = sel["media_player"]
|
||||
|
||||
# Receiver
|
||||
recv = self._options.get(CONF_RECEIVER_ENTITY)
|
||||
recv = self._options_dict.get(CONF_RECEIVER_ENTITY)
|
||||
recv_key = vol.Optional(CONF_RECEIVER_ENTITY)
|
||||
if recv:
|
||||
recv_key = vol.Optional(CONF_RECEIVER_ENTITY, default=recv)
|
||||
recv_key = vol.Optional(CONF_RECEIVER_ENTITY)
|
||||
schema_dict[recv_key] = sel["receiver"]
|
||||
|
||||
# P5-2: channel digit behavior (the user-validated §7 flow)
|
||||
current_delay = self._options.get(CONF_CHANNEL_DIGIT_DELAY, CHANNEL_DIGIT_DELAY)
|
||||
schema_dict[vol.Optional(CONF_CHANNEL_DIGIT_DELAY, default=current_delay)] = str
|
||||
current_enter = self._options.get(CONF_CHANNEL_SEND_ENTER, False)
|
||||
schema_dict[vol.Optional(CONF_CHANNEL_SEND_ENTER, default=current_enter)] = bool
|
||||
schema_dict[vol.Optional(CONF_CHANNEL_DIGIT_DELAY)] = str
|
||||
schema_dict[vol.Optional(CONF_CHANNEL_SEND_ENTER)] = bool
|
||||
|
||||
# P5-3: the live events push is opt-in (default off; the user
|
||||
# tests stability on their device and reports)
|
||||
current_logcat = self._options.get(CONF_LOGCAT_EVENTS, False)
|
||||
schema_dict[vol.Optional(CONF_LOGCAT_EVENTS, default=current_logcat)] = bool
|
||||
schema_dict[vol.Optional(CONF_LOGCAT_EVENTS)] = bool
|
||||
|
||||
# P4-2a: the raw adb_command service is opt-in (default off)
|
||||
current_flag = self._options.get(CONF_ADB_COMMAND_ENABLED, False)
|
||||
schema_dict[vol.Optional(CONF_ADB_COMMAND_ENABLED, default=current_flag)] = bool
|
||||
schema_dict[vol.Optional(CONF_ADB_COMMAND_ENABLED)] = bool
|
||||
|
||||
# Receiver input — dropdown from DAG if receiver is set, text otherwise
|
||||
current_input = self._options.get(CONF_RECEIVER_INPUT, "")
|
||||
receiver = self._options.get(CONF_RECEIVER_ENTITY)
|
||||
current_input = self._options_dict.get(CONF_RECEIVER_INPUT, "")
|
||||
receiver = self._options_dict.get(CONF_RECEIVER_ENTITY)
|
||||
inputs = _get_receiver_inputs(self.hass, receiver)
|
||||
|
||||
if inputs:
|
||||
@@ -618,43 +622,32 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
|
||||
)
|
||||
input_key = vol.Optional(CONF_RECEIVER_INPUT)
|
||||
if current_input:
|
||||
input_key = vol.Optional(CONF_RECEIVER_INPUT, default=current_input)
|
||||
input_key = vol.Optional(CONF_RECEIVER_INPUT)
|
||||
schema_dict[input_key] = input_sel
|
||||
else:
|
||||
schema_dict[vol.Optional(
|
||||
CONF_RECEIVER_INPUT, default=current_input
|
||||
)] = str
|
||||
schema_dict[vol.Optional(CONF_RECEIVER_INPUT)] = str
|
||||
|
||||
# Power
|
||||
pwr = self._options.get(CONF_POWER_ENTITY)
|
||||
pwr = self._options_dict.get(CONF_POWER_ENTITY)
|
||||
pwr_key = vol.Optional(CONF_POWER_ENTITY)
|
||||
if pwr:
|
||||
pwr_key = vol.Optional(CONF_POWER_ENTITY, default=pwr)
|
||||
pwr_key = vol.Optional(CONF_POWER_ENTITY)
|
||||
schema_dict[pwr_key] = sel["power"]
|
||||
|
||||
# ADB options
|
||||
schema_dict[vol.Optional(
|
||||
CONF_ADB_ENABLED,
|
||||
default=self._options.get(CONF_ADB_ENABLED, False),
|
||||
)] = bool
|
||||
schema_dict[vol.Optional(
|
||||
CONF_ADB_STORE_APP_INFO,
|
||||
default=self._options.get(CONF_ADB_STORE_APP_INFO, True),
|
||||
)] = bool
|
||||
schema_dict[vol.Optional(CONF_ADB_ENABLED)] = bool
|
||||
schema_dict[vol.Optional(CONF_ADB_STORE_APP_INFO)] = bool
|
||||
|
||||
# ADB host/port (optional overrides, shown when ADB is enabled)
|
||||
adb_host = self._options.get(CONF_ADB_HOST, "")
|
||||
schema_dict[vol.Optional(
|
||||
CONF_ADB_HOST,
|
||||
default=adb_host,
|
||||
)] = str
|
||||
schema_dict[vol.Optional(
|
||||
CONF_ADB_PORT,
|
||||
default=self._options.get(CONF_ADB_PORT, DEFAULT_ADB_PORT),
|
||||
)] = int
|
||||
schema_dict[vol.Optional(CONF_ADB_HOST)] = str
|
||||
schema_dict[vol.Optional(CONF_ADB_PORT)] = int
|
||||
|
||||
# t29 G3b: suggested values come from the entry options via the
|
||||
# documented helper — no per-key `default=` in the schema build.
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(schema_dict),
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
vol.Schema(schema_dict), self._options_dict
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
Reference in new issue
Block a user