# NVIDIA Shield Home Assistant integration for the NVIDIA Shield TV — ADB media session tracking for now-playing state from any app, plus Google remote-protocol commands, Jellyfin rich metadata, and power monitoring. This is a **Shield-only fork** of [ltomes/ha-nvidia-shield](https://github.com/ltomes/ha-nvidia-shield) (early 0.1.x), rebuilt around a different architecture: one integration, one device, ADB as the state source, and no dependency on separate `androidtv` / `androidtv_remote` integrations. > **Status:** early fork, actively rebuilt. Releases are date-based (`v2026.10.08`). > Expect rough edges until the MVP phases land. The build-out plan is in > [`docs/MVP_PLAN.md`](docs/MVP_PLAN.md); device research is in > [`docs/RESEARCH_SHIELD.md`](docs/RESEARCH_SHIELD.md). ## What we're building (the fork's goals) The fork exists to make a single integration own the Shield end-to-end: 1. **Event-driven ADB media session tracking as the primary playback state source** — `dumpsys media_session` over a persistent connection, not screen capture, not 10-second polling. 2. **Fallback state for apps without media sessions** — e.g. TiviMate publishes none at all; foreground-app detection (`mResumedActivity`) + last-known-state + the "audio playback" hint from the same session dump close that gap. 3. **All-in-one device** — absorb the Google remote protocol (`androidtvremote2`): a `remote` entity alongside the media player, `play_media` with `media_content_type: channel` for TiviMate channel-by-number, absolute volume, focused-app push events. ADB stays the playback-state source; ADB pairing stays intact. 4. **Retire the surrounding glue** — the `mediasession2mqtt` bridge, universal media player templates, and external remote entity reading all become native integration behavior. Phases 0–4 with files, risks, and definitions of done live in [`docs/MVP_PLAN.md`](docs/MVP_PLAN.md). ## Current features (inherited from upstream 0.1.x) - **ADB Media Session** — now-playing from any app that publishes a session (Spotify, Netflix, Plex, Wholphin, …) - **Jellyfin rich metadata** — title, series/episode, position, stream details (resolution, codec, HDR, audio format) via the HA Jellyfin integration's cache - **Power monitoring** — smart plug for reliable on/off with auto-discovered sibling sensors (watts, voltage, current) - **Intent tracking** — commands reflect in HA state immediately, self-heal against actual device state - **Privacy mode** — show only app name without media details for non-Jellyfin apps - **Media DAG attributes** — `media_dag_*` attributes for media-zone style orchestration Changes inherited from upstream still apply in 0.1.x code: polling is timer-based, the ADB connection lives inside the media player entity, and fallback app state arrives in Phase 3. ## State ladder and fallback semantics (Phase 3) The media player state is decided top-down; each tier serves only when the ones above it are silent. Both transports live in the entry's `ShieldConnectionManager` (`connection.py`) — ADB is the state source, the remote protocol is the fast push/command path: 1. **Plug off** — the power switch says `off` → `off`. 2. **Protocol power push** (`is_on`) — `False` → `off`; `None` falls through. 3. **ADB wake** (exact `mWakefulness` parse) — asleep → `idle` (standby). 4. **Intent** — a just-sent command's target state, self-healing against reality within 30 s (retry goes over the transport that served it). 5. **Jellyfin** — `playing` / `paused` while a Jellyfin session is active. 6. **ADB media session** — `playing` / `paused` for apps that publish a session (foreground app preferred). 7. **Foreground fallback** — an app is open (`current_app` protocol push, then the ADB `mResumedActivity` parse) but publishes no media session → `on`. Its identity comes from the TTL-capped last-known-state cache, the last channel entered, or the zero-cost "audio playback" hint (`media recently active`) — never resurrecting `playing` past the 60 s state TTL. 8. **On** — powered, awake, nothing more specific. TiviMate (`ar.tvplayer.tv`) is the documented no-session case: it publishes no MediaSession on Shield Android 11 (RESEARCH_SHIELD R2), so tiers 6–7 plus the audio hint are what keep it visible. **Standalone setup** accepts any one of the three state sources — the Android TV Remote entity, the Nvidia Remote Protocol (PIN pairing), or ADB media-session mode (ADB enabled + host). The `no_state_source` form error fires only when none of the three is present; ADB-only setups are valid. ### Channel entry options The options flow exposes the §7 channel behavior the integration replicates: the inter-digit **delay** (0.05–1.0 s, default 0.1) and the optional **ENTER press** after the digits (off by default — TiviMate's validated behavior). Both are consumed by `play_media` with `media_content_type: channel`. ### Raw `adb_command` service (opt-in) When enabled in the integration's options (`adb_command_enabled`, **default off**), the integration registers the `nvidia_shield.adb_command` service: send a raw ADB shell command to the entry's persistent connection (`{"command": "your-command", "entity_id": "media_player."}` — the entity is optional when exactly one entry provides the service). This is the same security posture as the Home Assistant core `androidtv` integration: a documented, opt-in escape hatch for debugging and automation, registered at setup and unregistered when the last participating entity unloads. It rejects (logs a warning, sends nothing) when the ADB connection is down. ### Migration from mediasession2mqtt + universal-player glue The `mediasession2mqtt` bridge sensors and the universal media player YAML templates the fork replaces are retired by the ladder above: the media player entity already carries the DAG attributes (`media_dag_running_apps` with foreground/last-known enrichment, `media_dag_now_playing` with its source tag) that media-zone orchestration consumed from those sensors. See the appendix in [`docs/RESEARCH_SHIELD.md`](docs/RESEARCH_SHIELD.md) for the retirement mapping and the universal-player glue it replaces. ## Releases - **Date-based versioning**: `vYYYY.MM.DD` (e.g. `v2026.10.08`). - Pushes to `master` on [Gitea](https://git.notexpectedyet.com/notexpectedyet/ha-nvidia-shield) run the release workflow (`.github/workflows/release.yml`): - A plain push publishes the current day's release (same-day pushes are deduped). - Bumping `version` in `custom_components/nvidia_shield/manifest.json` forces that specific version's release before the date fallback kicks in. - Releases land on Gitea with the tag and a `nvidia_shield.zip` asset for manual installs. (No automatic mirroring — see the HACS note below.) ## Installation ### HACS HACS only works with public GitHub repositories, so the Gitea repo cannot be added to HACS directly. If you set up a public GitHub mirror of the releases, add it as a custom repository: 1. In HACS, open the overflow menu (⋮) → **Custom repositories**. 2. Paste `https://github.com/notexpectedyet/ha-nvidia-shield`, pick type **Integration**, click **Add**. 3. Find **NVIDIA Shield** in the HACS list and click **Download**. 4. **Restart Home Assistant.** ### Manual Copy `custom_components/nvidia_shield` to your HA `config/custom_components/` directory, restart, then add the integration from **Settings → Devices & Services**. ## Configuration 1. Settings > Devices & Services > Add Integration > "NVIDIA Shield" 2. Select the Android TV Remote entity for your Shield 3. Enable ADB if you want now-playing from all apps 4. If ADB is enabled, the next step will pair — approve the prompt on the Shield 5. Optionally configure Jellyfin, receiver, power switch ### ADB Pairing ADB is paired during setup via a dedicated config flow step. If you need to re-pair later (e.g. after factory reset), use the **"Pair ADB"** button on the device page in HA. Requirements: Settings > Developer options > Network debugging enabled on the Shield. ### Options - **Jellyfin Media Player** — auto-detected or manually selected - **Receiver + Input** — for media-zone coordination - **Power Switch** — smart plug entity - **ADB Host/Port** — override auto-detection if needed ## Entities | Entity | Type | Description | |--------|------|-------------| | Media Player | `media_player` | Main entity with playback controls and metadata | | Current App | `sensor` | Currently running app | | Now Playing | `sensor` | Formatted now-playing string | | Pair ADB | `button` | Initiate or re-pair ADB connection | ## Development - **Repository:** [Gitea — git.notexpectedyet.com/notexpectedyet/ha-nvidia-shield](https://git.notexpectedyet.com/notexpectedyet/ha-nvidia-shield), branch `master` - **CI/CD:** Gitea Actions; every push to `master` releases (see `.gitea/workflows/release.yml`). Auth uses the job-provided `GITEA_TOKEN` — no extra secrets required. - **Shared ADB helpers** are vendored at `custom_components/nvidia_shield/ha_adb_common/` (trimmed in this fork); dev extras in `pyproject.toml` (`uv sync --extra dev`). ## License MIT