feat(config)!: migrate entries to v3 — one address, no androidtv key (t41)

ENTRY_VERSION = 3, declared once in const.py and reused as the flow's VERSION so
the stored shape and the migration cannot drift.

v2 → v3 mirrors the two shape changes:
* the duplicated address fields collapse into CONF_HOST — the protocol host
  wins, else the ADB host (both name the same Shield), reading options first and
  data second so a hand-written seed is covered too;
* the androidtv-entity key is dropped from data AND options with a logged note
  naming the dropped entity (the old value would only mislead now that nothing
  reads it).

A v1 entry still travels the whole way in one call (the v1→v2 protocol defaults
are applied, then the v3 collapse). Downgrades (version > 3) are rejected so HA
can offer a rollback, and the current version is a no-op.

Existing entries carrying the entity key keep working until the migration runs:
nothing reads it anymore (the c1 commit), which is exactly why the migration can
be a plain removal.

Test note: my first version of the version-parity pin called a bare
entity_stublib.install() and leaked the shallow stub set into the entity-module
loader of later tests (STATE_PAUSED/entity_available import failures). It now
uses the file's scoped harness, like every other test there.

Verified: 316 passed / 2 skipped, 317 / 1 with androidtvremote2, ruff clean,
drift engine exit 0 on HEAD (36 call arguments — one more awaited/checked call).
This commit is contained in:
James Mackay committed 2026-10-11 06:19:36 +01:00
1 parent 75ebeb40b0
commit 81f70585cd
5 files changed
+187 -46

No files matched your search

+52 -13
View File
@@ -24,11 +24,14 @@ from .const import (
CONF_ADB_ENABLED,
CONF_ADB_HOST,
CONF_ADB_PORT,
CONF_ANDROIDTV_ENTITY,
CONF_HOST,
CONF_PROTOCOL_ENABLED,
CONF_PROTOCOL_HOST,
DEFAULT_ADB_PORT,
DEFAULT_NAME,
DOMAIN,
ENTRY_VERSION,
SERVICE_ADB_COMMAND,
)
from .helpers import (
@@ -206,26 +209,62 @@ async def _build_protocol_settings(hass: HomeAssistant, entry: ConfigEntry):
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate v1 entries to v2 (adds the remote-protocol fields).
"""Migrate an older config entry to the current version.
ADB options carry over untouched; the ADB-host discovery fallback keeps
serving as the protocol host until pairing populates it. Downgrades
(version > 2) are rejected so HA offers a rollback.
v1 → v2 added the remote-protocol fields (ADB options carried over
untouched). v2 → v3 (t41) mirrors the two changes in the stored shape:
* the duplicated address fields collapse into the single CONF_HOST — the
protocol host wins, else the ADB host, since both name the same Shield;
* the androidtv-entity key goes. This integration owns the whole stack now:
the entity it used to configure is neither a state source nor an app-name
or power tier, so keeping the value would only mislead.
Downgrades (version > ENTRY_VERSION) are rejected so HA offers a rollback.
"""
if config_entry.version > 2:
if config_entry.version > ENTRY_VERSION:
return False
if config_entry.version >= ENTRY_VERSION:
return True
options = {**config_entry.options}
data = {**config_entry.data}
if config_entry.version < 2:
options = {**config_entry.options}
options.setdefault(CONF_PROTOCOL_ENABLED, False)
# 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
if not options.get(CONF_HOST):
options[CONF_HOST] = (
options.get(CONF_PROTOCOL_HOST)
or options.get(CONF_ADB_HOST)
or data.get(CONF_PROTOCOL_HOST)
or data.get(CONF_ADB_HOST)
or ""
)
dropped_entity = options.pop(CONF_ANDROIDTV_ENTITY, None)
dropped_entity = data.pop(CONF_ANDROIDTV_ENTITY, None) or dropped_entity
for legacy_key in (CONF_PROTOCOL_HOST, CONF_ADB_HOST):
options.pop(legacy_key, None)
data.pop(legacy_key, None)
hass.config_entries.async_update_entry(
config_entry, data=data, options=options, version=ENTRY_VERSION
)
if dropped_entity:
_LOGGER.info(
"Migrated %s config entry to version 2", config_entry.title
"%s: config entry migrated to version %d — dropped the androidtv "
"entity %s (this integration reads the Shield directly; the entry "
"needs no external media_player)",
config_entry.title,
ENTRY_VERSION,
dropped_entity,
)
else:
_LOGGER.info(
"Migrated %s config entry to version %d",
config_entry.title,
ENTRY_VERSION,
)
return True