"""The Nvidia Shield integration. Phase 1 architecture (docs/MVP_PLAN.md §2): the entry owns a single ShieldConnectionManager stored in ``entry.runtime_data`` — the persistent ADB socket and all ADB-derived state live there, not on the entities. Entities are thin callback-views over the manager's StateStore. ADB is optional: a failed/absent ADB connection never blocks setup (no ConfigEntryNotReady) — the manager retries in the background, or stays passive until the user pairs via the Pair ADB button. """ import logging import os from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import HomeAssistant from .adb_session import AdbMediaSessionPoller from .connection import ShieldConnectionManager from .const import CONF_ADB_ENABLED, DOMAIN from .helpers import resolve_adb_config _LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.MEDIA_PLAYER, Platform.SENSOR, Platform.BUTTON] def _build_poller_factory(hass: HomeAssistant, entry: ConfigEntry): """Poller factory when ADB is configured AND usable today, else None. Returning None leaves the manager passive: the entry still sets up, the entities simply see no ADB state. The Pair ADB button / reconfigure triggers a reload that re-evaluates this. """ if not entry.options.get(CONF_ADB_ENABLED, False): return None adb = resolve_adb_config(hass, entry) if adb is None: _LOGGER.warning( "ADB enabled but no host resolvable for entry %s — continuing " "without ADB state.", entry.title, ) return None # Only auto-connect when a key already exists — an ADB handshake against # an unpaired device is pointless noise (pairing happens via the Pair ADB # button / config flow, untouched). key_path_ready = await hass.async_add_executor_job( os.path.exists, adb.key_path ) if not key_path_ready: _LOGGER.info( "ADB key not found for entry %s — skipping auto-connect. " "Use the Pair ADB button or reconfigure the integration.", entry.title, ) return None return lambda: AdbMediaSessionPoller(adb.host, adb.port, adb.key_path) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Nvidia Shield from a config entry.""" hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = entry manager = ShieldConnectionManager( hass, entry, poller_factory=_build_poller_factory(hass, entry) ) entry.runtime_data = manager await manager.async_start() # Close the persistent socket on Home Assistant stop as well as unload. async def _close_on_hass_stop(_event) -> None: await manager.async_stop() entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close_on_hass_stop) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(_async_update_listener)) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: manager = getattr(entry, "runtime_data", None) if manager is not None: await manager.async_stop() entry.runtime_data = None hass.data[DOMAIN].pop(entry.entry_id) return unload_ok async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id)