feat(adb_command): selectable target, strict resolution, and a real response (t14)
The user's live run could not select his Shield for
nvidia_shield.adb_command, so he ran his diagnostics through the deprecated
androidtv integration instead. Measured against the installed HA (2026.10.1),
the cause is a contract, not a bug in the handler:
* ServiceRegistry.async_register has NO target parameter, so a target cannot be
declared at registration;
* the frontend renders the target picker from the service DESCRIPTION, which
HA builds from services.yaml or from the runtime async_set_service_schema —
and the runtime description WINS.
So async_setup now registers with supports_response=OPTIONAL and publishes
ADB_COMMAND_SERVICE_DESCRIPTION (target: {entity: [{domain: media_player}]},
fields, prose mirrored in strings.json/translations/en.json and pinned against
both by a test). No services.yaml is needed, and none is added.
The target resolution is strict, because the failure mode of guessing is a raw
shell command on the wrong device: exactly one PARTICIPANT, told apart as
no_target (nothing selected), unsupported_target (not a participant / option
off / another integration's entity) and ambiguous_target (two selected — sent
to neither). Accepts what HA actually sends: entity_id as text or as the LIST a
picked target produces, and device_id (an automation targeting the DEVICE),
expanded only through this integration's own registry entries and only to media
players. The output now comes back in the response under adb_response — the key
the user's captures read — while an unreachable Shield stays adb_unavailable,
an error rather than an empty response, and a call that does not ask for a
response is unchanged.
Evidence: 12 real-HA tests (new tests/test_adb_command_real_ha.py) drive the
real service registry, the real get_services websocket payload (the exact JSON
bytes the browser parses: target present, response optional, both fields) and
real async_call for every shape above. t14's CI gate globs tests/test_*real_ha.py
so a new real-HA file cannot skip silently; the skip/empty-collection guards stay
and were executed. The real-HA probe also caught async_entries_for_device being
a MODULE-LEVEL helper (not a registry method) that the stub had hidden, so the
drift gate's owner map now covers async_register, async_set_service_schema and
async_entries_for_device, with the call-argument floor raised 44 -> 52.
Verify: suites 681/4 and 682/3; real-HA 38 + the new file 12 (50 with the glob);
ruff clean; drift exit 0 (54 call arguments).
This commit is contained in:
1 parent
8a4e72b80a
commit
6aa3b6bfdd
13 files changed
+1087
-56
No files matched your search
@@ -21,6 +21,7 @@ from homeassistant.core import HomeAssistant
|
||||
from .adb_session import AdbMediaSessionPoller
|
||||
from .connection import ShieldConnectionManager
|
||||
from .const import (
|
||||
ADB_COMMAND_SERVICE_DESCRIPTION,
|
||||
CONF_ADB_ENABLED,
|
||||
CONF_ADB_HOST,
|
||||
CONF_ADB_PORT,
|
||||
@@ -80,14 +81,35 @@ async def async_setup(hass: HomeAssistant, config) -> bool:
|
||||
data=block,
|
||||
)
|
||||
)
|
||||
# t29 G8 (kept here across the t30 splice): the action registers ALWAYS
|
||||
# t29 G8 (kept here across the t30 splice): the action registers ALWAYS.
|
||||
# The two APIs the target-bearing registration needs are imported WITH that
|
||||
# registration, next to the handler they belong to (t14): importing the package —
|
||||
# which every config-flow harness does via
|
||||
# ``custom_components.nvidia_shield.config_flow`` — must not drag in the service
|
||||
# registration surface for a domain that harness never sets up.
|
||||
from homeassistant.core import SupportsResponse
|
||||
from homeassistant.helpers.service import async_set_service_schema
|
||||
|
||||
from .media_player import _ADB_COMMAND_SCHEMA, _async_adb_command_service
|
||||
|
||||
# t14: the action must be SELECTABLE for the user's Shield. Measured on HA
|
||||
# 2026.10.1: `async_register` has no `target` parameter, so the UI's target
|
||||
# picker comes from the service DESCRIPTION — the runtime
|
||||
# `async_set_service_schema` (which wins over a services.yaml we do not ship)
|
||||
# carries `target: {entity: [{domain: media_player}]}`, so the picker offers the
|
||||
# Shield's media player entity. `supports_response=OPTIONAL` is registered FIRST
|
||||
# because async_set_service_schema reads `hass.services.supports_response()` to
|
||||
# publish the response flag, and the handler returns the command's output under
|
||||
# ADB_COMMAND_RESPONSE_KEY.
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_ADB_COMMAND,
|
||||
_async_adb_command_service,
|
||||
schema=_ADB_COMMAND_SCHEMA,
|
||||
supports_response=SupportsResponse.OPTIONAL,
|
||||
)
|
||||
async_set_service_schema(
|
||||
hass, DOMAIN, SERVICE_ADB_COMMAND, ADB_COMMAND_SERVICE_DESCRIPTION
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
Reference in new issue
Block a user