refactor(config): one Shield IP field for both transports (t41)

The two address fields were the same Shield spelled twice, and every bug in the
zeroconf/IP-change path came from keeping them in sync. One field now:

* const.CONF_HOST replaces CONF_ADB_HOST / CONF_PROTOCOL_HOST in the setup form,
  the options page, the pairing forms, the reconfigure/reauth seeding and the
  description copy. ADB port 5555 stays the only port field;
* helpers.entry_host(entry) is the single resolver (options first, then data);
  resolve_adb_config and the protocol settings both go through it, so both
  transports read the same value at call time (the t32 F1 factory re-read keeps
  working: the NEXT built poller sees the new address);
* helpers.seed_host / seed_adb_enabled own the YAML seed's shape rule. A block
  may use `host` or the legacy protocol_host/adb_host keys — t41 keeps the old
  spelling working — and t32 F4's outcome is unchanged: a protocol-only block
  leaves ADB off, an address (or an explicit adb_enabled) turns it on;
* the zeroconf IP-change path collapses the t21-F2 per-host dance into one rule:
  the discovery address IS the entry's address, so the entry is updated when it
  differs and left alone when it matches. The blank-self-heal and
  deliberately-different-ADB-host cases are gone with the duplication — a
  second address field cannot express them anymore, and the tests now pin that
  (previously "an explicit override is never clobbered");
* the options page exposes the protocol toggle it never had (the page could turn
  ADB off but not the protocol);
* button.py (granted to this task) reads the entry's own address instead of
  another integration's entity.

The v3 entry migration that maps existing adb_host/protocol_host onto CONF_HOST
and drops the androidtv key lands in the next commit; HA runs migrations before
setup, so an existing entry is never read with the old shape.

Verified: 311 passed / 2 skipped, 312 / 1 with androidtvremote2, ruff clean,
drift engine exit 0 on HEAD. The UI-copy contract tests (every rendered field
has a label in strings.json AND translations/en.json, key trees identical, host
errors name the field) drove the string edits.
This commit is contained in:
James Mackay committed 2026-10-11 06:18:22 +01:00
1 parent 86aa2827a2
commit 75ebeb40b0
12 files changed
+302 -276

No files matched your search

+21 -15
View File
@@ -24,14 +24,20 @@ from .const import (
CONF_ADB_ENABLED,
CONF_ADB_HOST,
CONF_ADB_PORT,
CONF_HOST,
CONF_PROTOCOL_ENABLED,
CONF_PROTOCOL_HOST,
DEFAULT_ADB_PORT,
DEFAULT_NAME,
DOMAIN,
SERVICE_ADB_COMMAND,
)
from .helpers import protocol_cert_paths, resolve_adb_config
from .helpers import (
entry_host,
protocol_cert_paths,
resolve_adb_config,
seed_adb_enabled,
seed_host,
)
_LOGGER = logging.getLogger(__name__)
@@ -49,9 +55,9 @@ async def async_setup(hass: HomeAssistant, config) -> bool:
"""
domain_config = (config or {}).get(DOMAIN)
for block in _yaml_seed_blocks(domain_config or []):
if not block.get(CONF_ADB_HOST) and not block.get(CONF_PROTOCOL_HOST):
if not seed_host(block):
_LOGGER.warning(
"nvidia_shield YAML block without protocol/adb host ignored: %s",
"nvidia_shield YAML block without an address ignored: %s",
block.get("name", "<unnamed>"),
)
continue
@@ -83,15 +89,13 @@ def _yaml_seed_blocks(domain_config) -> list[dict]:
continue
name = block.get("name") or DEFAULT_NAME
# t32 F4 (review rider): the seed honors the block's shape — a
# protocol-only block does NOT force ADB on; ADB comes only when the
# block carries an adb host (interactive cosmetics stay in the flow).
# protocol-only block does NOT force ADB on. t41: the address is ONE
# field (`host`, or a legacy protocol_host/adb_host).
seed = {
"name": name,
CONF_ADB_ENABLED: bool(block.get(CONF_ADB_HOST)),
CONF_ADB_HOST: str(block.get(CONF_ADB_HOST) or ""),
CONF_ADB_ENABLED: seed_adb_enabled(block),
CONF_HOST: seed_host(block),
CONF_ADB_PORT: int(block.get(CONF_ADB_PORT) or DEFAULT_ADB_PORT),
# protocol fields are prefill hints ONLY — pairing stays interactive
CONF_PROTOCOL_HOST: str(block.get(CONF_PROTOCOL_HOST) or ""),
}
if block.get("unique_id"):
seed["unique_id"] = str(block["unique_id"])
@@ -177,14 +181,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def _build_protocol_settings(hass: HomeAssistant, entry: ConfigEntry):
"""Manager protocol_settings when the transport is enabled & resolvable.
The address is the entry's own (t41): the protocol field, else the ADB
field — both name the same Shield. Nothing is derived from another
integration.
The address is the entry's own single field (t41). Nothing is derived from
another integration.
"""
if not entry.options.get(CONF_PROTOCOL_ENABLED, False):
return None
host = entry.options.get(CONF_PROTOCOL_HOST) or entry.options.get(CONF_ADB_HOST)
host = entry_host(entry)
if not host:
_LOGGER.info(
"Protocol enabled for entry %s but no host resolvable — continuing "
@@ -214,7 +217,10 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
if config_entry.version < 2:
options = {**config_entry.options}
options.setdefault(CONF_PROTOCOL_ENABLED, False)
options.setdefault(CONF_PROTOCOL_HOST, "")
# t41: the entry's address is the single CONF_HOST field; a v1 entry
# carried it under the legacy adb_host key.
if not options.get(CONF_HOST):
options[CONF_HOST] = options.get(CONF_ADB_HOST) or ""
hass.config_entries.async_update_entry(
config_entry, options=options, version=2
)