fix(availability): F3 — any usable transport keeps the entities up (t40)

Reported from a live install: with ADB enabled but unreachable and a paired,
healthy protocol transport, the media player went unavailable even though the
protocol still served power/app/volume.

The old rule was ``manager.connected or manager.awake is not None or not
manager.available`` — i.e. "a configured ADB manager that is down ⇒ the whole
entity is down" (t5-F2), which ignored transport B entirely. The ladder is now:

* protocol transport wired and not reported unreachable — usable;
* or ADB connected — usable;
* or fresh wake data (the poller has spoken) — usable;
* or a passive manager (no ADB configured at all) — usable, because nothing to
  transport is not a broken transport.

Unavailable only when a CONFIGURED transport is actively down and no usable data
exists. One implementation, ``entity_available`` (media_player.py), is used by the
media player, the remote (whose ladder previously duplicated a weaker version of
this rule) and the diagnostics payload.

Diagnostics grew ``transport.entity_available`` plus ``adb.configured`` so a
support dump answers "why is my entity unavailable?" — the ladder inputs and its
verdict are now in the same place.

Pinned by tests: the exact user case (ADB configured+down, protocol up ⇒
available), all-down (⇒ unavailable), wake-data rescue, connected ADB without a
protocol, the passive manager, a configured-but-down transport with no data, the
remote following the same ladder, and the diagnostics verdict.

Verified: 310 passed / 2 skipped, 311 passed / 1 skipped (androidtvremote2),
16 passed in the HA drift suite, ruff clean.
This commit is contained in:
James Mackay committed 2026-10-11 06:02:46 +01:00
1 parent dd23d01a88
commit 60d884b76a
4 files changed
+144 -17

No files matched your search

+7 -9
View File
@@ -25,6 +25,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .connection import ShieldConnectionManager
from .const import DOMAIN, PROTOCOL_REMOTE_KEYS
from .media_player import entity_available
_START_PREFIX = "START_LONG:"
_END_PREFIX = "END_LONG:"
@@ -93,17 +94,14 @@ class NvidiaShieldRemoteEntity(RemoteEntity):
@property
def available(self) -> bool:
"""Unavailable while the transport is active but unpaired/down.
"""Available while ANY usable transport can serve a command (t40).
A passive manager (no protocol transport) keeps the entity available:
nothing to transport is not a broken one.
The same ladder as the media player (``entity_available``): protocol
wired+available, ADB connected, fresh wake data or a passive manager all
count as usable, and a passive manager stays available — nothing to
transport is not a broken one.
"""
manager = self._manager_of()
if not isinstance(manager, ShieldConnectionManager):
return True
if not manager.protocol_active:
return True
return manager.is_available is not False
return entity_available(self._manager_of())
@property
def activity(self) -> str | None: