The user ruling: this integration REPLACES both old integrations, so nothing may depend on an androidtv/androidtv_remote entity anymore. Removed: * the setup-form field + its entity selector (CONF_ANDROIDTV_ENTITY is no longer offered, only read by the migration that drops it); * the entity-derived unique_id derivation (_unique_id_from_atv_device, t29 G2) and, with it, every registry lookup for another integration's devices — the user step now sets NO unique_id at all; the protocol MAC learned during pairing is the identity; * the Jellyfin auto-detect's second tier (the atv entity's friendly_name) — it now uses the entry name the user typed or the device name from discovery; * the app-name and app-id tiers (media_player.app_name/app_id + the shared resolve_app_name chain, the Current App sensor, the sensor's state-change listener and its `_androidtv_state` helper) — the manager's store is the only source; * the "transitional external entity" command tier in every path: `_cmd` and the pan-media_player service call are gone, and so is the fallback in _media_cmd, _remote_key_cmd, the §7 channel-digit sequence and the intent self-heal retry (an intent no transport served now surrenders to the truth instead of delegating); * the entity-derived power signal (`_on_source_change`'s "device off clears the intent" check no longer watches another integration's entity; the power LADDER — protocol is_on → ADB wake → power switch — is unchanged). turn_on/turn_off are native now: they send the protocol POWER key and, like the remote entity, send nothing when the known state already matches (no superfluous toggle). An ADB-only entry (protocol skipped) has no power write path — the manager owns the socket and exposes no device-keyevent, which is what the old delegation used to cover. helpers.discover_adb_host — the one helper that derived a host from another integration's config entry — is deleted; button.py (granted to this task) reads the entry's own address fields instead of the entity, so the Pair ADB button keeps working. Present-day entries that still carry the key are unaffected (nothing reads it); the v3 migration that drops it lands with the host unification. Verified: 309 passed / 2 skipped, 310 / 1 with androidtvremote2, ruff clean, drift engine exit 0 on HEAD (13 modules, 58 imports, 35 call arguments, 6 awaited HA calls) and 16 passed in the real-HA drift suite. The stub harness's legacy service recorder became a watchdog: any HA service call from the media player now fails the routing tests instead of being the expected outcome.
91 lines
2.9 KiB
Python
91 lines
2.9 KiB
Python
"""Button entities for NVIDIA Shield."""
|
|
|
|
import logging
|
|
|
|
from homeassistant.components.button import ButtonEntity
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from .adb_session import AdbMediaSessionPoller, ensure_adb_key
|
|
from .const import (
|
|
CONF_ADB_ENABLED,
|
|
CONF_ADB_HOST,
|
|
CONF_ADB_PORT,
|
|
CONF_PROTOCOL_HOST,
|
|
DEFAULT_ADB_PORT,
|
|
DOMAIN,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
if entry.options.get(CONF_ADB_ENABLED, False):
|
|
async_add_entities([NvidiaShieldPairAdbButton(hass, entry)])
|
|
|
|
|
|
class NvidiaShieldPairAdbButton(ButtonEntity):
|
|
"""Button to initiate or re-pair the ADB connection."""
|
|
|
|
_attr_has_entity_name = True
|
|
_attr_name = "Pair ADB"
|
|
_attr_entity_category = EntityCategory.CONFIG
|
|
_attr_icon = "mdi:link-variant"
|
|
|
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
self.hass = hass
|
|
self._entry = entry
|
|
self._attr_unique_id = f"{DOMAIN}_{entry.entry_id}_pair_adb"
|
|
|
|
@property
|
|
def device_info(self) -> DeviceInfo:
|
|
return DeviceInfo(
|
|
identifiers={(DOMAIN, self._entry.entry_id)},
|
|
name=self._entry.title,
|
|
manufacturer="NVIDIA",
|
|
model="Shield TV",
|
|
)
|
|
|
|
async def async_press(self) -> None:
|
|
"""Attempt ADB connection. The Shield will show a pairing prompt."""
|
|
# t41: the address is ours — never derived from another integration's
|
|
# entity. The ADB field leads, then the protocol field (same Shield).
|
|
host = self._entry.options.get(CONF_ADB_HOST) or self._entry.options.get(
|
|
CONF_PROTOCOL_HOST
|
|
)
|
|
port = self._entry.options.get(CONF_ADB_PORT, DEFAULT_ADB_PORT)
|
|
|
|
if not host:
|
|
_LOGGER.error("Could not determine ADB host. Set it in integration options.")
|
|
return
|
|
|
|
key_path = await self.hass.async_add_executor_job(
|
|
ensure_adb_key, self.hass.config.config_dir
|
|
)
|
|
|
|
_LOGGER.info(
|
|
"Attempting ADB pairing with %s:%d — approve the prompt on the Shield",
|
|
host,
|
|
port,
|
|
)
|
|
|
|
poller = AdbMediaSessionPoller(host, port, key_path)
|
|
if await poller.connect():
|
|
_LOGGER.info("ADB pairing successful with %s", host)
|
|
await poller.disconnect()
|
|
# Reload to start polling
|
|
await self.hass.config_entries.async_reload(self._entry.entry_id)
|
|
else:
|
|
_LOGGER.error(
|
|
"ADB pairing failed with %s. Make sure Network debugging is enabled "
|
|
"and you approved the prompt on the Shield.",
|
|
host,
|
|
)
|
|
await poller.disconnect()
|