refactor(config): one Shield IP field for both transports (t41)
The two address fields were the same Shield spelled twice, and every bug in the zeroconf/IP-change path came from keeping them in sync. One field now: * const.CONF_HOST replaces CONF_ADB_HOST / CONF_PROTOCOL_HOST in the setup form, the options page, the pairing forms, the reconfigure/reauth seeding and the description copy. ADB port 5555 stays the only port field; * helpers.entry_host(entry) is the single resolver (options first, then data); resolve_adb_config and the protocol settings both go through it, so both transports read the same value at call time (the t32 F1 factory re-read keeps working: the NEXT built poller sees the new address); * helpers.seed_host / seed_adb_enabled own the YAML seed's shape rule. A block may use `host` or the legacy protocol_host/adb_host keys — t41 keeps the old spelling working — and t32 F4's outcome is unchanged: a protocol-only block leaves ADB off, an address (or an explicit adb_enabled) turns it on; * the zeroconf IP-change path collapses the t21-F2 per-host dance into one rule: the discovery address IS the entry's address, so the entry is updated when it differs and left alone when it matches. The blank-self-heal and deliberately-different-ADB-host cases are gone with the duplication — a second address field cannot express them anymore, and the tests now pin that (previously "an explicit override is never clobbered"); * the options page exposes the protocol toggle it never had (the page could turn ADB off but not the protocol); * button.py (granted to this task) reads the entry's own address instead of another integration's entity. The v3 entry migration that maps existing adb_host/protocol_host onto CONF_HOST and drops the androidtv key lands in the next commit; HA runs migrations before setup, so an existing entry is never read with the old shape. Verified: 311 passed / 2 skipped, 312 / 1 with androidtvremote2, ruff clean, drift engine exit 0 on HEAD. The UI-copy contract tests (every rendered field has a label in strings.json AND translations/en.json, key trees identical, host errors name the field) drove the string edits.
This commit is contained in:
1 parent
86aa2827a2
commit
75ebeb40b0
12 files changed
+302
-276
No files matched your search
@@ -24,14 +24,20 @@ from .const import (
|
||||
CONF_ADB_ENABLED,
|
||||
CONF_ADB_HOST,
|
||||
CONF_ADB_PORT,
|
||||
CONF_HOST,
|
||||
CONF_PROTOCOL_ENABLED,
|
||||
CONF_PROTOCOL_HOST,
|
||||
DEFAULT_ADB_PORT,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
SERVICE_ADB_COMMAND,
|
||||
)
|
||||
from .helpers import protocol_cert_paths, resolve_adb_config
|
||||
from .helpers import (
|
||||
entry_host,
|
||||
protocol_cert_paths,
|
||||
resolve_adb_config,
|
||||
seed_adb_enabled,
|
||||
seed_host,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,9 +55,9 @@ async def async_setup(hass: HomeAssistant, config) -> bool:
|
||||
"""
|
||||
domain_config = (config or {}).get(DOMAIN)
|
||||
for block in _yaml_seed_blocks(domain_config or []):
|
||||
if not block.get(CONF_ADB_HOST) and not block.get(CONF_PROTOCOL_HOST):
|
||||
if not seed_host(block):
|
||||
_LOGGER.warning(
|
||||
"nvidia_shield YAML block without protocol/adb host ignored: %s",
|
||||
"nvidia_shield YAML block without an address ignored: %s",
|
||||
block.get("name", "<unnamed>"),
|
||||
)
|
||||
continue
|
||||
@@ -83,15 +89,13 @@ def _yaml_seed_blocks(domain_config) -> list[dict]:
|
||||
continue
|
||||
name = block.get("name") or DEFAULT_NAME
|
||||
# t32 F4 (review rider): the seed honors the block's shape — a
|
||||
# protocol-only block does NOT force ADB on; ADB comes only when the
|
||||
# block carries an adb host (interactive cosmetics stay in the flow).
|
||||
# protocol-only block does NOT force ADB on. t41: the address is ONE
|
||||
# field (`host`, or a legacy protocol_host/adb_host).
|
||||
seed = {
|
||||
"name": name,
|
||||
CONF_ADB_ENABLED: bool(block.get(CONF_ADB_HOST)),
|
||||
CONF_ADB_HOST: str(block.get(CONF_ADB_HOST) or ""),
|
||||
CONF_ADB_ENABLED: seed_adb_enabled(block),
|
||||
CONF_HOST: seed_host(block),
|
||||
CONF_ADB_PORT: int(block.get(CONF_ADB_PORT) or DEFAULT_ADB_PORT),
|
||||
# protocol fields are prefill hints ONLY — pairing stays interactive
|
||||
CONF_PROTOCOL_HOST: str(block.get(CONF_PROTOCOL_HOST) or ""),
|
||||
}
|
||||
if block.get("unique_id"):
|
||||
seed["unique_id"] = str(block["unique_id"])
|
||||
@@ -177,14 +181,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def _build_protocol_settings(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Manager protocol_settings when the transport is enabled & resolvable.
|
||||
|
||||
The address is the entry's own (t41): the protocol field, else the ADB
|
||||
field — both name the same Shield. Nothing is derived from another
|
||||
integration.
|
||||
The address is the entry's own single field (t41). Nothing is derived from
|
||||
another integration.
|
||||
"""
|
||||
if not entry.options.get(CONF_PROTOCOL_ENABLED, False):
|
||||
return None
|
||||
|
||||
host = entry.options.get(CONF_PROTOCOL_HOST) or entry.options.get(CONF_ADB_HOST)
|
||||
host = entry_host(entry)
|
||||
if not host:
|
||||
_LOGGER.info(
|
||||
"Protocol enabled for entry %s but no host resolvable — continuing "
|
||||
@@ -214,7 +217,10 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
if config_entry.version < 2:
|
||||
options = {**config_entry.options}
|
||||
options.setdefault(CONF_PROTOCOL_ENABLED, False)
|
||||
options.setdefault(CONF_PROTOCOL_HOST, "")
|
||||
# t41: the entry's address is the single CONF_HOST field; a v1 entry
|
||||
# carried it under the legacy adb_host key.
|
||||
if not options.get(CONF_HOST):
|
||||
options[CONF_HOST] = options.get(CONF_ADB_HOST) or ""
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, options=options, version=2
|
||||
)
|
||||
|
||||
@@ -11,12 +11,11 @@ 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,
|
||||
)
|
||||
from .helpers import entry_host
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,11 +53,9 @@ class NvidiaShieldPairAdbButton(ButtonEntity):
|
||||
|
||||
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
|
||||
)
|
||||
# t41: the entry's own single address — never derived from another
|
||||
# integration's entity.
|
||||
host = entry_host(self._entry)
|
||||
port = self._entry.options.get(CONF_ADB_PORT, DEFAULT_ADB_PORT)
|
||||
|
||||
if not host:
|
||||
|
||||
@@ -15,19 +15,18 @@ from .const import (
|
||||
ATTR_MEDIA_DAG_INPUT_MAP,
|
||||
CONF_ADB_COMMAND_ENABLED,
|
||||
CONF_ADB_ENABLED,
|
||||
CONF_ADB_HOST,
|
||||
CONF_ADB_PORT,
|
||||
CONF_ADB_STORE_APP_INFO,
|
||||
CONF_APP_ICON_MAP,
|
||||
CONF_APP_NAME_MAP,
|
||||
CONF_CHANNEL_DIGIT_DELAY,
|
||||
CONF_CHANNEL_SEND_ENTER,
|
||||
CONF_HOST,
|
||||
CONF_JELLYFIN_ENTITY,
|
||||
CONF_LOGCAT_EVENTS,
|
||||
CONF_PAIRING_CODE,
|
||||
CONF_POWER_ENTITY,
|
||||
CONF_PROTOCOL_ENABLED,
|
||||
CONF_PROTOCOL_HOST,
|
||||
CONF_RECEIVER_ENTITY,
|
||||
CONF_RECEIVER_INPUT,
|
||||
CONF_SKIP_PAIRING,
|
||||
@@ -35,7 +34,7 @@ from .const import (
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
)
|
||||
from .helpers import protocol_cert_paths
|
||||
from .helpers import entry_host, protocol_cert_paths, seed_adb_enabled, seed_host
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -141,9 +140,10 @@ def _host_field_key(field: str, host: str):
|
||||
def _user_schema_dict(sel, defaults: dict | None = None) -> dict:
|
||||
"""User-step schema — every field belongs to THIS integration (t41).
|
||||
|
||||
``defaults`` pre-fills name/hosts from a zeroconf discovery event (or a
|
||||
YAML seed hint); the ADB port is pre-filled with the standard 5555 so it
|
||||
reads as an advanced setting rather than required knowledge (t33 F-D).
|
||||
``defaults`` pre-fills the name and the ONE address (t41) from a zeroconf
|
||||
discovery event or a YAML seed hint; the ADB port is pre-filled with the
|
||||
standard 5555 so it reads as an advanced setting rather than required
|
||||
knowledge (t33 F-D).
|
||||
"""
|
||||
defaults = defaults or {}
|
||||
host = str(defaults.get("host") or "")
|
||||
@@ -166,10 +166,9 @@ def _user_schema_dict(sel, defaults: dict | None = None) -> dict:
|
||||
# the code's own fallback for this flag is True (async_step_user) — the
|
||||
# rendered checkbox must agree with it
|
||||
schema_dict[vol.Optional(CONF_ADB_STORE_APP_INFO, default=True)] = bool
|
||||
schema_dict[_host_field_key(CONF_ADB_HOST, host)] = str
|
||||
schema_dict[_host_field_key(CONF_HOST, host)] = str
|
||||
schema_dict[vol.Optional(CONF_ADB_PORT, default=port)] = int
|
||||
schema_dict[vol.Optional(CONF_PROTOCOL_ENABLED)] = bool
|
||||
schema_dict[_host_field_key(CONF_PROTOCOL_HOST, host)] = str
|
||||
return schema_dict
|
||||
|
||||
|
||||
@@ -251,10 +250,9 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
CONF_POWER_ENTITY: user_input.get(CONF_POWER_ENTITY, ""),
|
||||
CONF_ADB_ENABLED: user_input.get(CONF_ADB_ENABLED, False),
|
||||
CONF_ADB_STORE_APP_INFO: user_input.get(CONF_ADB_STORE_APP_INFO, True),
|
||||
CONF_ADB_HOST: user_input.get(CONF_ADB_HOST, ""),
|
||||
CONF_HOST: str(user_input.get(CONF_HOST) or "").strip(),
|
||||
CONF_ADB_PORT: user_input.get(CONF_ADB_PORT, DEFAULT_ADB_PORT),
|
||||
CONF_PROTOCOL_ENABLED: user_input.get(CONF_PROTOCOL_ENABLED, False),
|
||||
CONF_PROTOCOL_HOST: user_input.get(CONF_PROTOCOL_HOST, ""),
|
||||
}
|
||||
|
||||
# If ADB is enabled, go to pairing step
|
||||
@@ -336,22 +334,15 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
for entry in self._async_current_entries():
|
||||
if getattr(entry, "unique_id", None) != mac:
|
||||
continue
|
||||
# Known device — per-host updates (t21 F2): each host is
|
||||
# tracked independently against the discovery address.
|
||||
# - a blank (auto-detect) host self-heals from the address;
|
||||
# - a stale protocol host is the IP change and is updated;
|
||||
# - a deliberately-different ADB host (an explicit override
|
||||
# that differs from both the address and the stored
|
||||
# protocol host) is never clobbered.
|
||||
protocol_host = entry.options.get(CONF_PROTOCOL_HOST, "")
|
||||
adb_host = entry.options.get(CONF_ADB_HOST, "")
|
||||
# Known device — IP change (t21 F2, collapsed by t41): there is
|
||||
# ONE address field now, so the discovery address simply IS the
|
||||
# entry's address. The per-host dance (blank self-heal / stale
|
||||
# protocol host / deliberately different ADB host) only existed
|
||||
# because the address was stored twice; it went with the
|
||||
# duplication.
|
||||
options = dict(entry.options)
|
||||
if protocol_host != host:
|
||||
options[CONF_PROTOCOL_HOST] = host
|
||||
if not adb_host or adb_host == protocol_host:
|
||||
# blank (self-heal) or auto-synced alongside the protocol
|
||||
# host — safe to follow the device address on discovery
|
||||
options[CONF_ADB_HOST] = host
|
||||
if entry_host(entry) != host:
|
||||
options[CONF_HOST] = host
|
||||
if options != entry.options:
|
||||
# t29 G3a: the deprecated update+schedule-reload combo is
|
||||
# gone (2026.12 error) — the entry updates ONLY here; the
|
||||
@@ -396,10 +387,9 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
CONF_POWER_ENTITY: d.get(CONF_POWER_ENTITY, ""),
|
||||
CONF_ADB_ENABLED: d.get(CONF_ADB_ENABLED, False),
|
||||
CONF_ADB_STORE_APP_INFO: d.get(CONF_ADB_STORE_APP_INFO, True),
|
||||
CONF_ADB_HOST: d.get(CONF_ADB_HOST, ""),
|
||||
CONF_HOST: d.get(CONF_HOST, ""),
|
||||
CONF_ADB_PORT: d.get(CONF_ADB_PORT, DEFAULT_ADB_PORT),
|
||||
CONF_PROTOCOL_ENABLED: d.get(CONF_PROTOCOL_ENABLED, False),
|
||||
CONF_PROTOCOL_HOST: d.get(CONF_PROTOCOL_HOST, ""),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -409,9 +399,6 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
return await self.async_step_protocol_pair()
|
||||
return self._create_entry()
|
||||
|
||||
def _protocol_host(self) -> str | None:
|
||||
"""Protocol host: explicit override, else the ADB host fallback."""
|
||||
return self._user_data.get(CONF_PROTOCOL_HOST) or self._resolve_adb_host()
|
||||
|
||||
def _discard_protocol_client(self) -> None:
|
||||
"""Drop a pairing client whose session became void (host corrected)."""
|
||||
@@ -429,7 +416,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
None when no host is resolvable or ``androidtvremote2`` is missing —
|
||||
the caller turns that into an error key, never a crash.
|
||||
"""
|
||||
host = self._protocol_host()
|
||||
host = self._host()
|
||||
if not host:
|
||||
return None
|
||||
certfile, keyfile = await self.hass.async_add_executor_job(
|
||||
@@ -451,7 +438,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
async def _async_begin_protocol_pairing(self):
|
||||
"""Start a PIN pairing session: ``(client, None)`` or ``(None, error)``."""
|
||||
if not self._protocol_host():
|
||||
if not self._host():
|
||||
return None, "protocol_no_host"
|
||||
client = await self._start_protocol_pairing()
|
||||
if client is None:
|
||||
@@ -470,7 +457,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PAIRING_CODE): str,
|
||||
_host_field_key(CONF_PROTOCOL_HOST, self._protocol_host() or ""): str,
|
||||
_host_field_key(CONF_HOST, self._host()): str,
|
||||
vol.Optional(CONF_SKIP_PAIRING, default=False): bool,
|
||||
}
|
||||
),
|
||||
@@ -530,12 +517,12 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if user_input.get(CONF_SKIP_PAIRING):
|
||||
return await self._async_skip_protocol_pairing()
|
||||
|
||||
typed_host = str(user_input.get(CONF_PROTOCOL_HOST) or "").strip()
|
||||
if typed_host and typed_host != (self._protocol_host() or ""):
|
||||
typed_host = str(user_input.get(CONF_HOST) or "").strip()
|
||||
if typed_host and typed_host != self._host():
|
||||
# A host typed HERE is authoritative — the user step is gone, so
|
||||
# the session started against the old address is dropped and the
|
||||
# Shield is asked for a NEW code at the corrected address (t35 F1).
|
||||
self._user_data[CONF_PROTOCOL_HOST] = typed_host
|
||||
self._user_data[CONF_HOST] = typed_host
|
||||
self._discard_protocol_client()
|
||||
|
||||
code = str(user_input.get(CONF_PAIRING_CODE, "") or "").strip()
|
||||
@@ -584,19 +571,17 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""
|
||||
block = dict(data or {})
|
||||
name = block.get("name") or DEFAULT_NAME
|
||||
# the block's address comes from `host`, or from the legacy keys
|
||||
block_host = seed_host(block)
|
||||
seed_unique_id = block.get("unique_id")
|
||||
if not seed_unique_id:
|
||||
host = block.get(CONF_ADB_HOST) or block.get(CONF_PROTOCOL_HOST)
|
||||
seed_unique_id = f"yaml:{name}:{host or 'nohost'}"
|
||||
seed_unique_id = f"yaml:{name}:{block_host or 'nohost'}"
|
||||
|
||||
# duplicate guards
|
||||
yhost = block.get(CONF_ADB_HOST) or block.get(CONF_PROTOCOL_HOST)
|
||||
for existing in self._async_current_entries():
|
||||
ehost = existing.options.get(CONF_PROTOCOL_HOST) or existing.options.get(
|
||||
CONF_ADB_HOST
|
||||
)
|
||||
ehost = entry_host(existing)
|
||||
if existing.unique_id == seed_unique_id or (
|
||||
yhost and ehost == yhost
|
||||
block_host and ehost == block_host
|
||||
):
|
||||
_LOGGER.info(
|
||||
"nvidia_shield YAML seed for %r matches an existing "
|
||||
@@ -612,18 +597,16 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
# block actually carries an adb host.
|
||||
self._user_data = {
|
||||
"name": name,
|
||||
CONF_ADB_ENABLED: bool(block.get(CONF_ADB_HOST)),
|
||||
CONF_ADB_HOST: block.get(CONF_ADB_HOST, ""),
|
||||
CONF_ADB_PORT: block.get(CONF_ADB_PORT, DEFAULT_ADB_PORT),
|
||||
# protocol prefill hints ONLY (pairing stays interactive)
|
||||
CONF_PROTOCOL_HOST: block.get(CONF_PROTOCOL_HOST, ""),
|
||||
# t32 F4's shape rule on the t41 keys: an address names the Shield,
|
||||
# and an explicit adb_enabled says otherwise
|
||||
CONF_ADB_ENABLED: seed_adb_enabled(block),
|
||||
CONF_HOST: block_host,
|
||||
CONF_ADB_PORT: int(block.get(CONF_ADB_PORT) or DEFAULT_ADB_PORT),
|
||||
}
|
||||
# t33 F-A: the seed host feeds the same user-step prefill as a
|
||||
# zeroconf discovery, but WITHOUT the discovery announcement — the
|
||||
# address came from configuration.yaml, not from the network.
|
||||
self._seed_host = self._user_data[CONF_PROTOCOL_HOST] or self._user_data[
|
||||
CONF_ADB_HOST
|
||||
]
|
||||
self._seed_host = block_host
|
||||
return await self.async_step_user(None)
|
||||
|
||||
async def async_step_reauth(self, entry_data):
|
||||
@@ -644,7 +627,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._user_data = {
|
||||
"name": entry.title,
|
||||
CONF_PROTOCOL_ENABLED: True,
|
||||
CONF_PROTOCOL_HOST: entry.options.get(CONF_PROTOCOL_HOST, ""),
|
||||
CONF_HOST: entry_host(entry),
|
||||
}
|
||||
return await self.async_step_protocol_pair() # first visit re-pairs
|
||||
return self.async_show_form(
|
||||
@@ -671,10 +654,9 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._user_data = {
|
||||
"name": entry.title,
|
||||
CONF_ADB_ENABLED: entry.options.get(CONF_ADB_ENABLED, False),
|
||||
CONF_ADB_HOST: entry.options.get(CONF_ADB_HOST, ""),
|
||||
CONF_HOST: entry_host(entry),
|
||||
CONF_ADB_PORT: entry.options.get(CONF_ADB_PORT, DEFAULT_ADB_PORT),
|
||||
CONF_PROTOCOL_ENABLED: True,
|
||||
CONF_PROTOCOL_HOST: entry.options.get(CONF_PROTOCOL_HOST, ""),
|
||||
}
|
||||
return await self.async_step_protocol_pair()
|
||||
|
||||
@@ -691,7 +673,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"options": {
|
||||
**entry.options,
|
||||
CONF_PROTOCOL_ENABLED: True,
|
||||
CONF_PROTOCOL_HOST: self._protocol_host() or "",
|
||||
CONF_HOST: self._host(),
|
||||
},
|
||||
}
|
||||
if mac:
|
||||
@@ -714,19 +696,25 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
options={
|
||||
**entry.options,
|
||||
CONF_PROTOCOL_ENABLED: True,
|
||||
CONF_PROTOCOL_HOST: self._user_data.get(CONF_PROTOCOL_HOST, ""),
|
||||
CONF_HOST: self._host(),
|
||||
},
|
||||
)
|
||||
self._reauth_entry = None
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
def _resolve_adb_host(self) -> str | None:
|
||||
"""The address to use for ADB: the ADB field, else the protocol field."""
|
||||
return (
|
||||
self._user_data.get(CONF_ADB_HOST)
|
||||
or self._user_data.get(CONF_PROTOCOL_HOST)
|
||||
or None
|
||||
)
|
||||
def _host(self) -> str:
|
||||
"""The Shield's address as this flow knows it (t41: ONE field).
|
||||
|
||||
What the user typed (or a seed carried) lives in ``_user_data``; the
|
||||
discovery/seed hints stay as fallbacks for a form that was rendered with
|
||||
a pre-filled field and submitted unchanged.
|
||||
"""
|
||||
return str(
|
||||
self._user_data.get(CONF_HOST)
|
||||
or self._discovered_host
|
||||
or self._seed_host
|
||||
or ""
|
||||
).strip()
|
||||
|
||||
def _show_adb_pair_form(self, errors=None):
|
||||
"""adb_pair form: the address/port (fixable here) and the skip escape.
|
||||
@@ -739,7 +727,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
step_id="adb_pair",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
_host_field_key(CONF_ADB_HOST, self._resolve_adb_host() or ""): str,
|
||||
_host_field_key(CONF_HOST, self._host()): str,
|
||||
vol.Optional(
|
||||
CONF_ADB_PORT,
|
||||
default=self._user_data.get(
|
||||
@@ -770,10 +758,10 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
# re-adding the integration.
|
||||
return await self._async_after_pairing()
|
||||
|
||||
typed_host = str(user_input.get(CONF_ADB_HOST) or "").strip()
|
||||
typed_host = str(user_input.get(CONF_HOST) or "").strip()
|
||||
if typed_host:
|
||||
# a host typed HERE is authoritative (the user step is gone)
|
||||
self._user_data[CONF_ADB_HOST] = typed_host
|
||||
self._user_data[CONF_HOST] = typed_host
|
||||
|
||||
port = user_input.get(CONF_ADB_PORT, DEFAULT_ADB_PORT)
|
||||
try:
|
||||
@@ -782,7 +770,7 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
port = DEFAULT_ADB_PORT
|
||||
self._user_data[CONF_ADB_PORT] = port
|
||||
|
||||
host = self._resolve_adb_host()
|
||||
host = self._host()
|
||||
if not host:
|
||||
errors["base"] = "adb_no_host"
|
||||
else:
|
||||
@@ -923,12 +911,14 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlowWithReload):
|
||||
pwr_key = vol.Optional(CONF_POWER_ENTITY)
|
||||
schema_dict[pwr_key] = sel["power"]
|
||||
|
||||
# ADB options
|
||||
# Transport toggles (t41: both transports are ours; the skip paths and
|
||||
# this page are the way to turn either one off)
|
||||
schema_dict[vol.Optional(CONF_ADB_ENABLED)] = bool
|
||||
schema_dict[vol.Optional(CONF_PROTOCOL_ENABLED)] = bool
|
||||
schema_dict[vol.Optional(CONF_ADB_STORE_APP_INFO)] = bool
|
||||
|
||||
# ADB host/port (optional overrides, shown when ADB is enabled)
|
||||
schema_dict[vol.Optional(CONF_ADB_HOST)] = str
|
||||
# The ONE address field (t41), plus the ADB port
|
||||
schema_dict[vol.Optional(CONF_HOST)] = str
|
||||
schema_dict[vol.Optional(CONF_ADB_PORT)] = int
|
||||
|
||||
# t29 G3b: suggested values come from the entry options via the
|
||||
|
||||
@@ -12,6 +12,16 @@ from .ha_adb_common.const import ( # noqa: F401
|
||||
|
||||
DOMAIN = "nvidia_shield"
|
||||
|
||||
# t41: the INTEGRATION's address field — one Shield IP for the whole stack
|
||||
# (protocol + ADB). The two keys below it are LEGACY: a YAML seed may still
|
||||
# carry them and the v3 entry migration maps them onto CONF_HOST, but no
|
||||
# runtime code reads them.
|
||||
CONF_HOST = "host"
|
||||
CONF_ADB_HOST = "adb_host"
|
||||
CONF_PROTOCOL_HOST = "protocol_host"
|
||||
|
||||
# t41 legacy: dropped from entries by the v3 migration (the androidtv entity
|
||||
# path it configured is gone), never read at runtime.
|
||||
CONF_ANDROIDTV_ENTITY = "androidtv_entity"
|
||||
CONF_JELLYFIN_ENTITY = "jellyfin_entity"
|
||||
CONF_RECEIVER_ENTITY = "receiver_entity"
|
||||
@@ -19,12 +29,10 @@ CONF_RECEIVER_INPUT = "receiver_input"
|
||||
CONF_POWER_ENTITY = "power_entity"
|
||||
CONF_ADB_ENABLED = "adb_enabled"
|
||||
CONF_ADB_STORE_APP_INFO = "adb_store_app_info"
|
||||
CONF_ADB_HOST = "adb_host"
|
||||
CONF_ADB_PORT = "adb_port"
|
||||
|
||||
# Remote-protocol transport (androidtvremote2) — Phase 2
|
||||
CONF_PROTOCOL_ENABLED = "protocol_enabled"
|
||||
CONF_PROTOCOL_HOST = "protocol_host"
|
||||
CONF_PAIRING_CODE = "pairing_code"
|
||||
# t33 F-B: the "continue without pairing" escape offered on BOTH pairing
|
||||
# steps — the entry still finishes (passive mode) instead of stranding the
|
||||
|
||||
@@ -6,8 +6,10 @@ from dataclasses import dataclass
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
CONF_ADB_ENABLED,
|
||||
CONF_ADB_HOST,
|
||||
CONF_ADB_PORT,
|
||||
CONF_HOST,
|
||||
CONF_PROTOCOL_HOST,
|
||||
DEFAULT_ADB_PORT,
|
||||
DOMAIN,
|
||||
@@ -41,14 +43,51 @@ def protocol_cert_paths(hass: HomeAssistant) -> tuple[str, str]:
|
||||
return os.path.join(base, "client.pem"), os.path.join(base, "client.key")
|
||||
|
||||
|
||||
def entry_host(entry) -> str:
|
||||
"""The Shield's address for an entry (t41: ONE field).
|
||||
|
||||
``options`` is the source of truth; ``data`` is read as a fallback so an
|
||||
entry created by an older flow (or a hand-written seed) still resolves.
|
||||
"""
|
||||
options = getattr(entry, "options", None) or {}
|
||||
data = getattr(entry, "data", None) or {}
|
||||
return str(options.get(CONF_HOST) or data.get(CONF_HOST) or "").strip()
|
||||
|
||||
|
||||
def seed_host(block: dict) -> str:
|
||||
"""The address a YAML seed block carries (t41).
|
||||
|
||||
``host`` is the current key; the legacy ``protocol_host`` / ``adb_host``
|
||||
stay accepted so an existing configuration.yaml keeps seeding, protocol
|
||||
first (that transport is the one that cannot fall back to anything else).
|
||||
"""
|
||||
for key in (CONF_HOST, CONF_PROTOCOL_HOST, CONF_ADB_HOST):
|
||||
value = str((block or {}).get(key) or "").strip()
|
||||
if value:
|
||||
return value
|
||||
return ""
|
||||
|
||||
|
||||
def seed_adb_enabled(block: dict) -> bool:
|
||||
"""Does a seed block ask for ADB? (t32 F4's shape rule, t41's keys).
|
||||
|
||||
An explicit ``adb_enabled`` wins. Otherwise an address that names the Shield
|
||||
— the current ``host`` key or the legacy ``adb_host`` — turns ADB on, while
|
||||
a protocol-only block leaves it off.
|
||||
"""
|
||||
block = block or {}
|
||||
if CONF_ADB_ENABLED in block:
|
||||
return bool(block[CONF_ADB_ENABLED])
|
||||
return bool(block.get(CONF_HOST) or block.get(CONF_ADB_HOST))
|
||||
|
||||
|
||||
def resolve_adb_config(hass: HomeAssistant, entry) -> AdbConfig | None:
|
||||
"""Resolve host/port/key_path for the entry's ADB connection, or None.
|
||||
|
||||
Priority: the entry's own address fields (t41: no other integration is
|
||||
consulted) — the ADB field first, then the protocol field, since both
|
||||
describe the same Shield.
|
||||
Priority: the entry's own address (t41: no other integration is consulted)
|
||||
and its ADB port option.
|
||||
"""
|
||||
host = entry.options.get(CONF_ADB_HOST) or entry.options.get(CONF_PROTOCOL_HOST)
|
||||
host = entry_host(entry)
|
||||
if not host:
|
||||
return None
|
||||
port = int(entry.options.get(CONF_ADB_PORT, DEFAULT_ADB_PORT))
|
||||
|
||||
@@ -3,45 +3,41 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "NVIDIA Shield",
|
||||
"description": "{discovery_note}Set up your NVIDIA Shield. Everything here is optional except one state source \u2014 an Android TV Remote entity, the Nvidia Remote Protocol, or ADB. Link the devices and services you want to use.",
|
||||
"description": "{discovery_note}Set up your NVIDIA Shield. Everything here is optional except one state source \u2014 the Nvidia Remote Protocol or ADB. Link the devices and services you want to use.",
|
||||
"data": {
|
||||
"name": "Name",
|
||||
"androidtv_entity": "Android TV Remote entity",
|
||||
"jellyfin_entity": "Jellyfin media player",
|
||||
"receiver_entity": "Receiver",
|
||||
"receiver_input": "Receiver input",
|
||||
"power_entity": "Power switch",
|
||||
"adb_enabled": "Read state over ADB",
|
||||
"adb_store_app_info": "Show title and artist",
|
||||
"adb_host": "ADB host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"adb_port": "ADB port",
|
||||
"protocol_enabled": "Add the Nvidia Remote Protocol",
|
||||
"protocol_host": "Protocol host (IP address)"
|
||||
"protocol_enabled": "Add the Nvidia Remote Protocol"
|
||||
},
|
||||
"data_description": {
|
||||
"androidtv_entity": "The media_player entity from the Android TV Remote integration. Leave blank if you use the Nvidia Remote Protocol or ADB instead.",
|
||||
"jellyfin_entity": "The Jellyfin media_player for this Shield. Leave blank to detect it automatically.",
|
||||
"receiver_entity": "The receiver or soundbar this Shield plays through.",
|
||||
"receiver_input": "The receiver input the Shield uses, for example 'Shield'. Leave blank if you are not sure.",
|
||||
"power_entity": "A smart plug switch, if you want reliable on/off detection.",
|
||||
"adb_enabled": "Reads now-playing information over ADB from any app. Requires Developer options > Network debugging on the Shield.",
|
||||
"adb_store_app_info": "Shows the title and artist from non-Jellyfin apps. Turn off to show only the app name (privacy mode).",
|
||||
"adb_host": "The Shield's IP address. Pre-filled when the Shield was discovered, or when an Android TV Remote entity is set \u2014 change it only if you need a different address.",
|
||||
"host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it only if you need a different address.",
|
||||
"adb_port": "The ADB port. The standard value is 5555 \u2014 change it only if ADB uses a custom port on your network.",
|
||||
"protocol_enabled": "Adds the all-in-one remote-protocol transport (power, availability, app push and volume readout). It needs a one-time PIN pairing right after this step.",
|
||||
"protocol_host": "The Shield's IP address for the remote protocol. Pre-filled when the Shield was discovered \u2014 leave blank to use the ADB host."
|
||||
"protocol_enabled": "Adds the all-in-one remote-protocol transport (power, availability, app push and volume readout). It needs a one-time PIN pairing right after this step."
|
||||
}
|
||||
},
|
||||
"adb_pair": {
|
||||
"title": "Allow ADB access",
|
||||
"description": "On the Shield, open Settings > System > Developer options and turn on Network debugging (it may be called ADB debugging). Then select Submit \u2014 the Shield shows an Allow prompt; approve it. Nothing happened? Check the address below and select Submit again. Not now? Turn on 'Continue without ADB pairing' and select Submit.",
|
||||
"data": {
|
||||
"adb_host": "ADB host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"adb_port": "ADB port",
|
||||
"skip_pairing": "Continue without ADB pairing"
|
||||
},
|
||||
"data_description": {
|
||||
"adb_host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it only if the Shield moved or you gave it a different address.",
|
||||
"host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it only if the Shield moved or you gave it a different address.",
|
||||
"adb_port": "The standard ADB port is 5555.",
|
||||
"skip_pairing": "Finishes setup now and leaves ADB unpaired. The Pair ADB button stays available on the device page, so you can pair later."
|
||||
}
|
||||
@@ -51,12 +47,12 @@
|
||||
"description": "While this step is open the Shield shows a pairing code. Type it below and select Submit. If no code appears on the Shield, check the IP address below and select Submit again \u2014 pairing restarts and the Shield shows a new code. To set up without the remote protocol, turn on 'Continue without protocol pairing'.",
|
||||
"data": {
|
||||
"pairing_code": "Pairing code shown on the Shield",
|
||||
"protocol_host": "Protocol host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"skip_pairing": "Continue without protocol pairing"
|
||||
},
|
||||
"data_description": {
|
||||
"pairing_code": "The code the Shield displays while this step is open.",
|
||||
"protocol_host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it if the Shield moved.",
|
||||
"host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it if the Shield moved.",
|
||||
"skip_pairing": "Finishes setup with the remote protocol off \u2014 it stays off until you pair it. Pair later from this integration's entry menu: choose Reconfigure."
|
||||
}
|
||||
},
|
||||
@@ -67,11 +63,11 @@
|
||||
},
|
||||
"error": {
|
||||
"adb_failed": "The Shield did not accept the ADB connection. Make sure Network debugging is on, approve the Allow prompt on the Shield, then select Submit to try again.",
|
||||
"adb_no_host": "No IP address for the Shield yet. Fill in the ADB host field above (a discovered Shield, or one linked to an Android TV Remote entity, is pre-filled automatically).",
|
||||
"protocol_no_host": "No IP address for the Shield yet. Fill in the protocol host field above (a discovered Shield, or one linked to an Android TV Remote entity, is pre-filled automatically).",
|
||||
"adb_no_host": "No IP address for the Shield yet. Fill in the host field above (a discovered Shield is pre-filled automatically).",
|
||||
"protocol_no_host": "No IP address for the Shield yet. Fill in the host field above (a discovered Shield is pre-filled automatically).",
|
||||
"protocol_pair_failed": "The Shield rejected that pairing code. Check the code on the Shield and select Submit to try again.",
|
||||
"protocol_unavailable": "The androidtvremote2 library is not installed, so the remote protocol cannot pair. Install it and restart Home Assistant, or turn on 'Continue without protocol pairing' to finish setup anyway.",
|
||||
"no_state_source": "No state source selected. Choose at least one: an Android TV Remote entity, 'Add the Nvidia Remote Protocol', or 'Read state over ADB'.",
|
||||
"no_state_source": "No state source selected. Choose at least one: 'Add the Nvidia Remote Protocol' or 'Read state over ADB'.",
|
||||
"invalid_app_map": "Each line must be package=name with a non-empty package (no spaces) and a non-empty name \u2014 for example ar.tvplayer.tv=TiviMate.",
|
||||
"invalid_digit_delay": "The digit delay must be between 0.05 and 1.0 seconds."
|
||||
},
|
||||
@@ -101,8 +97,9 @@
|
||||
"receiver_input": "Receiver input",
|
||||
"power_entity": "Power switch",
|
||||
"adb_enabled": "Read state over ADB",
|
||||
"protocol_enabled": "Add the Nvidia Remote Protocol",
|
||||
"adb_store_app_info": "Show title and artist",
|
||||
"adb_host": "ADB host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"adb_port": "ADB port",
|
||||
"app_name_map": "App names and icons",
|
||||
"adb_command_enabled": "Enable the adb_command service",
|
||||
@@ -117,7 +114,8 @@
|
||||
"power_entity": "A smart plug switch, if you want reliable on/off detection.",
|
||||
"adb_enabled": "Reads now-playing information over ADB from any app. Requires Developer options > Network debugging on the Shield.",
|
||||
"adb_store_app_info": "Shows the title and artist from non-Jellyfin apps. Turn off to show only the app name (privacy mode).",
|
||||
"adb_host": "The Shield's IP address. Leave blank to use the Android TV Remote entity's address.",
|
||||
"host": "The Shield's IP address. Change it only if the Shield moved or you gave it a different address.",
|
||||
"protocol_enabled": "Adds the all-in-one remote-protocol transport (power, availability, app push and volume readout). Turning it on needs a one-time PIN pairing \u2014 use Reconfigure on the entry to pair.",
|
||||
"adb_port": "The ADB port. The standard value is 5555.",
|
||||
"app_name_map": "One entry per line, package=name (for example ar.tvplayer.tv=TiviMate), with an optional |icon after the name. Leave blank to clear.",
|
||||
"adb_command_enabled": "Adds the nvidia_shield.adb_command service (a raw ADB shell command over the persistent connection). Security-relevant: leave it off unless you need it \u2014 the same opt-in posture as the core androidtv integration.",
|
||||
|
||||
@@ -3,45 +3,41 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "NVIDIA Shield",
|
||||
"description": "{discovery_note}Set up your NVIDIA Shield. Everything here is optional except one state source \u2014 an Android TV Remote entity, the Nvidia Remote Protocol, or ADB. Link the devices and services you want to use.",
|
||||
"description": "{discovery_note}Set up your NVIDIA Shield. Everything here is optional except one state source \u2014 the Nvidia Remote Protocol or ADB. Link the devices and services you want to use.",
|
||||
"data": {
|
||||
"name": "Name",
|
||||
"androidtv_entity": "Android TV Remote entity",
|
||||
"jellyfin_entity": "Jellyfin media player",
|
||||
"receiver_entity": "Receiver",
|
||||
"receiver_input": "Receiver input",
|
||||
"power_entity": "Power switch",
|
||||
"adb_enabled": "Read state over ADB",
|
||||
"adb_store_app_info": "Show title and artist",
|
||||
"adb_host": "ADB host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"adb_port": "ADB port",
|
||||
"protocol_enabled": "Add the Nvidia Remote Protocol",
|
||||
"protocol_host": "Protocol host (IP address)"
|
||||
"protocol_enabled": "Add the Nvidia Remote Protocol"
|
||||
},
|
||||
"data_description": {
|
||||
"androidtv_entity": "The media_player entity from the Android TV Remote integration. Leave blank if you use the Nvidia Remote Protocol or ADB instead.",
|
||||
"jellyfin_entity": "The Jellyfin media_player for this Shield. Leave blank to detect it automatically.",
|
||||
"receiver_entity": "The receiver or soundbar this Shield plays through.",
|
||||
"receiver_input": "The receiver input the Shield uses, for example 'Shield'. Leave blank if you are not sure.",
|
||||
"power_entity": "A smart plug switch, if you want reliable on/off detection.",
|
||||
"adb_enabled": "Reads now-playing information over ADB from any app. Requires Developer options > Network debugging on the Shield.",
|
||||
"adb_store_app_info": "Shows the title and artist from non-Jellyfin apps. Turn off to show only the app name (privacy mode).",
|
||||
"adb_host": "The Shield's IP address. Pre-filled when the Shield was discovered, or when an Android TV Remote entity is set \u2014 change it only if you need a different address.",
|
||||
"host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it only if you need a different address.",
|
||||
"adb_port": "The ADB port. The standard value is 5555 \u2014 change it only if ADB uses a custom port on your network.",
|
||||
"protocol_enabled": "Adds the all-in-one remote-protocol transport (power, availability, app push and volume readout). It needs a one-time PIN pairing right after this step.",
|
||||
"protocol_host": "The Shield's IP address for the remote protocol. Pre-filled when the Shield was discovered \u2014 leave blank to use the ADB host."
|
||||
"protocol_enabled": "Adds the all-in-one remote-protocol transport (power, availability, app push and volume readout). It needs a one-time PIN pairing right after this step."
|
||||
}
|
||||
},
|
||||
"adb_pair": {
|
||||
"title": "Allow ADB access",
|
||||
"description": "On the Shield, open Settings > System > Developer options and turn on Network debugging (it may be called ADB debugging). Then select Submit \u2014 the Shield shows an Allow prompt; approve it. Nothing happened? Check the address below and select Submit again. Not now? Turn on 'Continue without ADB pairing' and select Submit.",
|
||||
"data": {
|
||||
"adb_host": "ADB host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"adb_port": "ADB port",
|
||||
"skip_pairing": "Continue without ADB pairing"
|
||||
},
|
||||
"data_description": {
|
||||
"adb_host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it only if the Shield moved or you gave it a different address.",
|
||||
"host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it only if the Shield moved or you gave it a different address.",
|
||||
"adb_port": "The standard ADB port is 5555.",
|
||||
"skip_pairing": "Finishes setup now and leaves ADB unpaired. The Pair ADB button stays available on the device page, so you can pair later."
|
||||
}
|
||||
@@ -51,12 +47,12 @@
|
||||
"description": "While this step is open the Shield shows a pairing code. Type it below and select Submit. If no code appears on the Shield, check the IP address below and select Submit again \u2014 pairing restarts and the Shield shows a new code. To set up without the remote protocol, turn on 'Continue without protocol pairing'.",
|
||||
"data": {
|
||||
"pairing_code": "Pairing code shown on the Shield",
|
||||
"protocol_host": "Protocol host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"skip_pairing": "Continue without protocol pairing"
|
||||
},
|
||||
"data_description": {
|
||||
"pairing_code": "The code the Shield displays while this step is open.",
|
||||
"protocol_host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it if the Shield moved.",
|
||||
"host": "The Shield's IP address. Pre-filled when the Shield was discovered \u2014 change it if the Shield moved.",
|
||||
"skip_pairing": "Finishes setup with the remote protocol off \u2014 it stays off until you pair it. Pair later from this integration's entry menu: choose Reconfigure."
|
||||
}
|
||||
},
|
||||
@@ -67,11 +63,11 @@
|
||||
},
|
||||
"error": {
|
||||
"adb_failed": "The Shield did not accept the ADB connection. Make sure Network debugging is on, approve the Allow prompt on the Shield, then select Submit to try again.",
|
||||
"adb_no_host": "No IP address for the Shield yet. Fill in the ADB host field above (a discovered Shield, or one linked to an Android TV Remote entity, is pre-filled automatically).",
|
||||
"protocol_no_host": "No IP address for the Shield yet. Fill in the protocol host field above (a discovered Shield, or one linked to an Android TV Remote entity, is pre-filled automatically).",
|
||||
"adb_no_host": "No IP address for the Shield yet. Fill in the host field above (a discovered Shield is pre-filled automatically).",
|
||||
"protocol_no_host": "No IP address for the Shield yet. Fill in the host field above (a discovered Shield is pre-filled automatically).",
|
||||
"protocol_pair_failed": "The Shield rejected that pairing code. Check the code on the Shield and select Submit to try again.",
|
||||
"protocol_unavailable": "The androidtvremote2 library is not installed, so the remote protocol cannot pair. Install it and restart Home Assistant, or turn on 'Continue without protocol pairing' to finish setup anyway.",
|
||||
"no_state_source": "No state source selected. Choose at least one: an Android TV Remote entity, 'Add the Nvidia Remote Protocol', or 'Read state over ADB'.",
|
||||
"no_state_source": "No state source selected. Choose at least one: 'Add the Nvidia Remote Protocol' or 'Read state over ADB'.",
|
||||
"invalid_app_map": "Each line must be package=name with a non-empty package (no spaces) and a non-empty name \u2014 for example ar.tvplayer.tv=TiviMate.",
|
||||
"invalid_digit_delay": "The digit delay must be between 0.05 and 1.0 seconds."
|
||||
},
|
||||
@@ -101,8 +97,9 @@
|
||||
"receiver_input": "Receiver input",
|
||||
"power_entity": "Power switch",
|
||||
"adb_enabled": "Read state over ADB",
|
||||
"protocol_enabled": "Add the Nvidia Remote Protocol",
|
||||
"adb_store_app_info": "Show title and artist",
|
||||
"adb_host": "ADB host (IP address)",
|
||||
"host": "The Shield's IP address",
|
||||
"adb_port": "ADB port",
|
||||
"app_name_map": "App names and icons",
|
||||
"adb_command_enabled": "Enable the adb_command service",
|
||||
@@ -117,7 +114,8 @@
|
||||
"power_entity": "A smart plug switch, if you want reliable on/off detection.",
|
||||
"adb_enabled": "Reads now-playing information over ADB from any app. Requires Developer options > Network debugging on the Shield.",
|
||||
"adb_store_app_info": "Shows the title and artist from non-Jellyfin apps. Turn off to show only the app name (privacy mode).",
|
||||
"adb_host": "The Shield's IP address. Leave blank to use the Android TV Remote entity's address.",
|
||||
"host": "The Shield's IP address. Change it only if the Shield moved or you gave it a different address.",
|
||||
"protocol_enabled": "Adds the all-in-one remote-protocol transport (power, availability, app push and volume readout). Turning it on needs a one-time PIN pairing \u2014 use Reconfigure on the entry to pair.",
|
||||
"adb_port": "The ADB port. The standard value is 5555.",
|
||||
"app_name_map": "One entry per line, package=name (for example ar.tvplayer.tv=TiviMate), with an optional |icon after the name. Leave blank to clear.",
|
||||
"adb_command_enabled": "Adds the nvidia_shield.adb_command service (a raw ADB shell command over the persistent connection). Security-relevant: leave it off unless you need it \u2014 the same opt-in posture as the core androidtv integration.",
|
||||
|
||||
Reference in new issue
Block a user