From a65c8933391f5134fea2eace3b5e01c5d6f1c17f Mon Sep 17 00:00:00 2001 From: James Mackay Date: Thu, 8 Oct 2026 10:12:40 +0100 Subject: [PATCH] refactor(connection): public protocol_active accessor replaces the remote entity's private reach-in; media_player module docstring describes the Phase 2 architecture (F7) --- custom_components/nvidia_shield/connection.py | 9 ++++++++ .../nvidia_shield/media_player.py | 22 +++++++++++-------- custom_components/nvidia_shield/remote.py | 2 +- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/custom_components/nvidia_shield/connection.py b/custom_components/nvidia_shield/connection.py index ac42aaa..c153ed3 100644 --- a/custom_components/nvidia_shield/connection.py +++ b/custom_components/nvidia_shield/connection.py @@ -347,6 +347,15 @@ class ShieldConnectionManager: """Visible recent-task apps, ordered by recency.""" return self.store.get(FIELD_RUNNING_APPS, []) + @property + def protocol_active(self) -> bool: + """True when the remote-protocol transport is wired for this entry. + + Entities use this instead of reaching into ``_protocol``: a passive + manager means "no transport", not "broken transport". + """ + return self._protocol is not None + @property def is_on(self) -> bool | None: """Protocol power push (device reports on/off state).""" diff --git a/custom_components/nvidia_shield/media_player.py b/custom_components/nvidia_shield/media_player.py index 066535a..6b0980d 100644 --- a/custom_components/nvidia_shield/media_player.py +++ b/custom_components/nvidia_shield/media_player.py @@ -1,19 +1,23 @@ """Media player entity for Nvidia Shield. -State architecture: - - Power/availability: androidtv_remote (local push, fast) - - Playback state: Jellyfin (polling, slow 10-30s) - - Metadata: Jellyfin attributes - - ALL commands: androidtv_remote (Jellyfin is read-only) +Phase 2 architecture (docs/MVP_PLAN.md §2): the entry's ShieldConnectionManager +(connection.py) owns both transports — ADB (adb_shell, state source) and the +absorbed remote protocol (androidtvremote2, local push) — plus their StateStore; +this entity is a thin callback-view over it. -ADB connection/polling lives in the entry's ShieldConnectionManager -(connection.py); this entity is a thin callback-view over its StateStore. +State architecture: + - Power ladder: protocol is_on push → ADB wake (exact mWakefulness) → plug off + - Playback state: Jellyfin (polling, slow 10-30s) or ADB media session + - Metadata: Jellyfin attributes + - Commands: protocol key push → ADB keyevent → external androidtv_remote + (the last tier is transitional; removed in Phase 4) Intent tracking bridges the gap between instant commands and slow Jellyfin polling: 1. User presses pause → intent recorded → UI shows paused immediately - 2. Command sent to androidtv_remote → Shield pauses on screen + 2. Command sent over the serving transport → Shield pauses on screen 3. 10-30s later Jellyfin confirms paused → intent cleared - 4. If Jellyfin doesn't confirm in 30s → retry once → if still no → surrender to truth + 4. If Jellyfin doesn't confirm in 30s → retry once on the serving + transport (protocol/ADB stamped on the intent) → surrender to truth """ import asyncio diff --git a/custom_components/nvidia_shield/remote.py b/custom_components/nvidia_shield/remote.py index cd25a31..6f7d8b3 100644 --- a/custom_components/nvidia_shield/remote.py +++ b/custom_components/nvidia_shield/remote.py @@ -101,7 +101,7 @@ class NvidiaShieldRemoteEntity(RemoteEntity): manager = self._manager_of() if not isinstance(manager, ShieldConnectionManager): return True - if manager._protocol is None: + if not manager.protocol_active: return True return manager.is_available is not False