t61 F1 (BLOCKER) + F2 + F3 + F5, all reproduced against a real FlowManager
before fixing. The OPTIONS flow was verified good and is untouched by this
commit; the SETUP flow was broken in three independent ways.
F1 — `_show_setup_menu` passed `errors=` to `async_show_menu`, which has no
such parameter (HA 2026.10.1: step_id / menu_options / sort /
description_placeholders). Every path that went near the menu therefore
raised `TypeError` the moment the first form was submitted or ADB pairing
was skipped. The path map, driven end to end: FOUR of the five ordinary
paths died before create_entry ("ADB skip", "ADB paired + protocol off",
"ADB skip + protocol skip", "pair ADB"); only "protocol only" survived, and
it never showed the menu — so a new entry could only be created on that one
path and the sub-screens were unreachable.
F2 — the menu's rows broke the rule this repo documents: HA dispatches a row
to `async_step_<row id>` AND dispatches a submitted form by its OWN step_id,
so a row named `adb` with a handler called `async_step_adb_pair` is an
UnknownStep, and a row named `receiver` rendering `step_id="setup_receiver"`
is a form that cannot be submitted. The rows are now their handler names
(`receiver`, `connection`, `adb_pair`, `protocol_pair`, `finish`) and the
two sub-screens render their row id. There is also a `_paired_adb` guard
now, so a completed ADB pairing stops offering that row — the rule the
protocol row already followed.
F3 — both menus keyed their row labels under a bare `menu`, which core does
not read (56 core strings files use `menu_options`), so every row fell back
to its raw id in the browser. Renamed in both strings files.
F5 — the per-command script merge is field-wise now: a `script_<command>`
key that is present and empty removes that command, one with a value sets
it, and a key the submission does not carry is untouched — the same rule as
every other option. The wholesale rebuild depended on the page always being
complete, so a partial submission silently wiped the commands it omitted.
THE SYSTEMIC HALF (F1), closed as asked: `ha_api_drift.HA_CALL_OWNERS` now
covers the flow-display APIs (`async_show_menu`, `async_show_progress`,
`async_show_progress_done`, plus the existing form/abort/entry owners), so
the drift engine resolves their REAL signatures and flags an unknown
keyword. `tests/test_ha_api_drift.py` carries a pre-fix source using
`errors=` to prove the new owner is non-vacuous. Every harness stub now
mirrors the real signature (no `errors` on a menu) and a stub-level test
pins that parameter set, so a stub cannot go back to accepting anything.
On the pre-fix tree the drift engine reports exactly this:
config_flow.py:898: unknown-kwarg: async_show_menu() has no 'errors'
parameter; real signature: (self, *, step_id=None, menu_options, sort,
description_placeholders) -> _FlowResultT
TESTS: tests/test_config_flow_real_ha.py gains a SETUP section that drives
the real flow — the first form, the ADB skip, the pairing guard, EVERY menu
row (dispatch AND re-submittable step_id), an accepted sub-screen
submission, a full connection+receiver+finish walk merging into one entry,
the menu's pairing note, and the five-path map as a parametrised test.
11 of those fail on the pre-fix tree; the drift engine failure above is the
12th. The stub suite additionally pins the menu stub's signature and the
`menu_options` key in both strings files.
Also in this commit (t57 F4): the real-HA file claimed entity-valued pages
could not be driven there, which was false (it has always submitted
`switch.soundbar`). The claim is gone and the scripts page is driven through
SET (two scripts selected, real entity ids) → CLEAR (one selector emptied)
→ SAVE → REOPEN, so the SET half is pinned in the real flow and not only
against the selector objects.
319 lines
19 KiB
JSON
319 lines
19 KiB
JSON
{
|
|
"config": {
|
|
"step": {
|
|
"user": {
|
|
"title": "NVIDIA Shield",
|
|
"description": "{discovery_note}Set up your NVIDIA Shield. Everything here is optional except one state source — the Nvidia Remote Protocol or ADB. Link the devices and services you want to use.",
|
|
"data": {
|
|
"name": "Name",
|
|
"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",
|
|
"host": "The Shield's IP address",
|
|
"adb_port": "ADB port",
|
|
"protocol_enabled": "Add the Nvidia Remote Protocol"
|
|
},
|
|
"data_description": {
|
|
"jellyfin_entity": "The Jellyfin media_player for this Shield. Leave blank to detect it automatically.",
|
|
"receiver_entity": "The receiver, soundbar or speaker switch this Shield plays through. Leave blank if there is none.",
|
|
"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).",
|
|
"host": "The Shield's IP address. Pre-filled when the Shield was discovered — change it only if you need a different address.",
|
|
"adb_port": "The ADB port. The standard value is 5555 — 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."
|
|
}
|
|
},
|
|
"menu": {
|
|
"title": "Finish setup",
|
|
"description": "Pair the transports and check anything else before the entry is created. {pairing}.",
|
|
"data": {
|
|
"host": "The Shield's IP address",
|
|
"adb_port": "ADB port",
|
|
"adb_enabled": "Read state over ADB",
|
|
"protocol_enabled": "Add the Nvidia Remote Protocol"
|
|
},
|
|
"data_description": {
|
|
"host": "The Shield's IP address. Pre-filled when the Shield was discovered — change it only if you need a different address.",
|
|
"adb_port": "The ADB port. The standard value is 5555 — change it only if ADB uses a custom port on your network.",
|
|
"adb_enabled": "Reads now-playing information over ADB from any app. Requires Developer options > Network debugging on the Shield.",
|
|
"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."
|
|
},
|
|
"menu_options": {
|
|
"receiver": "Receiver",
|
|
"connection": "Connection",
|
|
"adb_pair": "Set up ADB",
|
|
"protocol_pair": "Pair the remote protocol",
|
|
"finish": "Create the entry"
|
|
}
|
|
},
|
|
"receiver": {
|
|
"title": "Receiver",
|
|
"description": "The receiver or soundbar this Shield plays through — a media_player, or a plain switch.",
|
|
"data": {
|
|
"receiver_entity": "Receiver",
|
|
"receiver_input": "Receiver input"
|
|
},
|
|
"data_description": {
|
|
"receiver_entity": "The receiver, soundbar or speaker switch this Shield plays through. Leave blank if there is none.",
|
|
"receiver_input": "The receiver input the Shield uses, for example 'Shield'. Leave blank if you are not sure."
|
|
}
|
|
},
|
|
"connection": {
|
|
"title": "Connection",
|
|
"description": "The Shield's address, the ADB port and which transports this entry uses.",
|
|
"data": {
|
|
"host": "The Shield's IP address",
|
|
"adb_port": "ADB port",
|
|
"adb_enabled": "Read state over ADB",
|
|
"protocol_enabled": "Add the Nvidia Remote Protocol"
|
|
},
|
|
"data_description": {
|
|
"host": "The Shield's IP address. Pre-filled when the Shield was discovered — change it only if you need a different address.",
|
|
"adb_port": "The ADB port. The standard value is 5555 — change it only if ADB uses a custom port on your network.",
|
|
"adb_enabled": "Reads now-playing information over ADB from any app. Requires Developer options > Network debugging on the Shield.",
|
|
"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 — 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": {
|
|
"host": "The Shield's IP address",
|
|
"adb_port": "ADB port",
|
|
"skip_pairing": "Continue without ADB pairing"
|
|
},
|
|
"data_description": {
|
|
"host": "The Shield's IP address. Pre-filled when the Shield was discovered — 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."
|
|
}
|
|
},
|
|
"protocol_pair": {
|
|
"title": "Pair the remote protocol",
|
|
"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 — 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",
|
|
"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.",
|
|
"host": "The Shield's IP address. Pre-filled when the Shield was discovered — change it if the Shield moved.",
|
|
"skip_pairing": "Finishes setup with the remote protocol off — it stays off until you pair it. Pair later from this integration's entry menu: choose Reconfigure."
|
|
}
|
|
},
|
|
"reauth_confirm": {
|
|
"title": "Re-pair Nvidia Shield",
|
|
"description": "The Shield no longer trusts the saved pairing certificate, usually after a factory reset. Select Submit to pair again — the Shield then shows a code."
|
|
}
|
|
},
|
|
"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 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.",
|
|
"protocol_unreachable": "The Shield did not answer at that address. Check the address above and that the Shield is awake, then select Submit to try again — or turn on 'Continue without protocol pairing' to finish setup without it.",
|
|
"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 — for example ar.tvplayer.tv=TiviMate.",
|
|
"invalid_digit_delay": "The digit delay must be between 0.05 and 1.0 seconds."
|
|
},
|
|
"abort": {
|
|
"already_configured": "This Shield is already set up.",
|
|
"reauth_successful": "Re-paired successfully.",
|
|
"reauth_skipped": "Pairing skipped. The saved certificate is still not trusted, so the remote protocol stays unavailable until you pair again.",
|
|
"reauth_entry_missing": "This entry no longer exists.",
|
|
"reconfigure_skipped": "Nothing changed — the remote protocol stays off until you pair it. Choose Reconfigure from this integration's entry menu when you are ready.",
|
|
"reconfigure_entry_missing": "This entry no longer exists, so there is nothing to reconfigure.",
|
|
"zeroconf_no_host": "The discovery event carried no usable address.",
|
|
"yaml_seed_only": {},
|
|
"yaml_seed": {
|
|
"title": "YAML succeeded earlier",
|
|
"message": "The configuration.yaml nvidia_shield block is seed-only: it starts the standard interactive flow. Removing the YAML block creates or deletes nothing."
|
|
}
|
|
}
|
|
},
|
|
"options": {
|
|
"step": {
|
|
"init": {
|
|
"title": "NVIDIA Shield settings",
|
|
"description": "Settings for this Shield. Saving reloads the integration so the change takes effect. {discovery}.",
|
|
"data": {
|
|
"jellyfin_entity": "Jellyfin media player",
|
|
"receiver_entity": "Receiver",
|
|
"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",
|
|
"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",
|
|
"channel_digit_delay": "Channel digit delay (seconds)",
|
|
"channel_send_enter": "Send ENTER after channel digits",
|
|
"script_turn_on": "Turn on script",
|
|
"script_turn_off": "Turn off script",
|
|
"script_volume_up": "Volume up script",
|
|
"script_volume_down": "Volume down script",
|
|
"script_volume_set": "Volume set script",
|
|
"script_volume_mute": "Mute script",
|
|
"script_select_source": "Select source script",
|
|
"logcat_events": "Instant app detection (logcat stream)",
|
|
"remote_command_script": "Remote command script"
|
|
},
|
|
"data_description": {
|
|
"jellyfin_entity": "The Jellyfin media_player for this Shield. Stream details come from the HA Jellyfin integration — no extra setup needed.",
|
|
"receiver_entity": "The receiver or soundbar this Shield plays through.",
|
|
"receiver_input": "The receiver input the Shield uses, for example 'Shield'.",
|
|
"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).",
|
|
"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 — 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 — the same opt-in posture as the core androidtv integration.",
|
|
"channel_digit_delay": "Pause between the channel digits entered via play_media(channel) — 0.05 to 1.0 seconds (default 0.1, the tested value).",
|
|
"channel_send_enter": "Presses ENTER after the last digit (protocol key first, then the ADB keyevent fallback). Off by default, matching TiviMate's tested behavior.",
|
|
"logcat_events": "Detects app changes instantly through a dedicated logcat stream (AdbDevice.streaming_shell). Off by default — polling keeps the state correct either way, and the integration falls back to polling if the stream keeps failing.",
|
|
"remote_command_script": "One script for remote.send_command. It replaces the key path when set and receives the call's own parameters as variables: command (the list of key commands), num_repeats, delay_secs and hold_secs."
|
|
},
|
|
"menu_options": {
|
|
"connection": "Main setup",
|
|
"receiver": "Receiver",
|
|
"apps": "Apps",
|
|
"discover": "Discover installed apps",
|
|
"scripts": "Scripts",
|
|
"finish": "Save and close"
|
|
}
|
|
},
|
|
"connection": {
|
|
"title": "Main setup",
|
|
"description": "The Shield's address, the transports it is read over, and the devices this entry links to. Changes here are kept when you save.",
|
|
"data": {
|
|
"host": "The Shield's IP address",
|
|
"adb_port": "ADB port",
|
|
"adb_enabled": "Read state over ADB",
|
|
"adb_store_app_info": "Show title and artist",
|
|
"protocol_enabled": "Add the Nvidia Remote Protocol",
|
|
"logcat_events": "Watch app launches over logcat",
|
|
"adb_command_enabled": "Allow the adb_command action",
|
|
"jellyfin_entity": "Jellyfin media player",
|
|
"power_entity": "Power switch"
|
|
},
|
|
"data_description": {
|
|
"host": "The Shield's IP address. Change it only if the Shield moved.",
|
|
"adb_port": "The ADB port. The standard value is 5555 — change it only if ADB uses a custom port on your network.",
|
|
"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).",
|
|
"protocol_enabled": "The all-in-one remote-protocol transport (power, availability, app push and volume readout). It needs the one-time PIN pairing from setup.",
|
|
"logcat_events": "Uses a second ADB stream to notice app launches the moment they happen. Leave off unless you want the faster app switch.",
|
|
"adb_command_enabled": "Exposes the adb_command action for this entry's media player. Off by default; it runs arbitrary shell commands on the Shield.",
|
|
"jellyfin_entity": "The Jellyfin media_player for this Shield. Leave blank to detect it automatically.",
|
|
"power_entity": "A smart plug switch, if you want reliable on/off detection."
|
|
}
|
|
},
|
|
"receiver": {
|
|
"title": "Receiver",
|
|
"description": "The receiver or soundbar this Shield plays through — a media_player, or a plain switch for a soundbar that only turns on and off.",
|
|
"data": {
|
|
"receiver_entity": "Receiver",
|
|
"receiver_input": "Receiver input"
|
|
},
|
|
"data_description": {
|
|
"receiver_entity": "The receiver, soundbar or speaker switch this Shield plays through. Leave blank if there is none.",
|
|
"receiver_input": "The receiver input the Shield uses, for example 'Shield'. Leave blank if you are not sure."
|
|
}
|
|
},
|
|
"apps": {
|
|
"title": "Apps",
|
|
"description": "One app per line: the package id, an equals sign, the name to show, and optionally a pipe and an icon (a picture URL or a camera entity id). Blank uses the built-in list.",
|
|
"data": {
|
|
"app_name_map": "App names and icons"
|
|
},
|
|
"data_description": {
|
|
"app_name_map": "One row per app, for example ar.tvplayer.tv=TiviMate|http://192.168.1.10/tivi.png. The name is what the source list, the Current App sensor and the media player show."
|
|
}
|
|
},
|
|
"scripts": {
|
|
"title": "Scripts",
|
|
"description": "Optional: run a Home Assistant script instead of the built-in command, per command. Clear a row to go back to the built-in path.",
|
|
"data": {
|
|
"remote_command_script": "Script for remote.send_command",
|
|
"script_turn_on": "turn_on script",
|
|
"script_turn_off": "turn_off script",
|
|
"script_volume_up": "volume_up script",
|
|
"script_volume_down": "volume_down script",
|
|
"script_volume_set": "volume_set script",
|
|
"script_volume_mute": "volume_mute script",
|
|
"script_select_source": "select_source script",
|
|
"channel_digit_delay": "Delay between channel digits (seconds)",
|
|
"channel_send_enter": "Send ENTER after channel digits"
|
|
},
|
|
"data_description": {
|
|
"remote_command_script": "Replaces the whole remote.send_command path for this Shield. Leave blank to use the built-in key presses.",
|
|
"channel_digit_delay": "The gap between the digits of a channel number, 0.05 to 1.0 seconds. Default 0.1.",
|
|
"channel_send_enter": "Turn on if your channel app needs ENTER after the digits."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"services": {
|
|
"adb_command": {
|
|
"name": "ADB command",
|
|
"description": "Send a raw ADB shell command to the persistent connection of an Nvidia Shield entry (option-gated use).",
|
|
"fields": {
|
|
"command": {
|
|
"name": "Command",
|
|
"description": "The shell command to run on the device."
|
|
},
|
|
"entity_id": {
|
|
"name": "Entity",
|
|
"description": "The Nvidia Shield media player to target."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"exceptions": {
|
|
"invalid_command": {
|
|
"title": "Empty command",
|
|
"message": "The command field is empty — nothing was sent."
|
|
},
|
|
"unsupported_target": {
|
|
"title": "Unsupported entity for adb_command",
|
|
"message": "{entity_id} is not an adb_command participant (its entry must enable the adb_command_enabled option) — command not sent."
|
|
},
|
|
"adb_unavailable": {
|
|
"title": "ADB unavailable",
|
|
"message": "The ADB connection of {entity_id} is down — command not sent."
|
|
},
|
|
"unknown_source": {
|
|
"title": "Unknown source",
|
|
"message": "'{source}' is not one of this Shield's apps. Use a name from the source list — the app names from the Apps map, or the built-in list when no map is set."
|
|
},
|
|
"command_script_failed": {
|
|
"title": "Command script failed",
|
|
"message": "The script configured for {action} ({script}) failed: {error}"
|
|
}
|
|
},
|
|
"entity": {
|
|
"sensor": {
|
|
"current_app": {
|
|
"name": "Current app"
|
|
},
|
|
"now_playing": {
|
|
"name": "Now playing"
|
|
},
|
|
"installed_apps": {
|
|
"name": "Installed apps"
|
|
}
|
|
}
|
|
}
|
|
}
|