From 3d026ae60e59f481a0b1ac1107be0ada1e6653e3 Mon Sep 17 00:00:00 2001 From: James Mackay Date: Fri, 9 Oct 2026 05:55:07 +0100 Subject: [PATCH] =?UTF-8?q?feat(config=5Fflow):=20digit-delay=20(0.05?= =?UTF-8?q?=E2=80=931.0=20s,=20invalid=20input=20error)=20+=20optional=20s?= =?UTF-8?q?end-enter=20options=20UI=20consumed=20by=20the=20=C2=A77=20chan?= =?UTF-8?q?nel=20path;=20strings/README=20updated=20(P5-2.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++++++ .../nvidia_shield/config_flow.py | 21 +++++++++++++++++++ custom_components/nvidia_shield/strings.json | 11 +++++++--- .../nvidia_shield/translations/en.json | 11 +++++++--- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bb57b1e..8b46f3b 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,14 @@ TV Remote entity, the Nvidia Remote Protocol (PIN pairing), or ADB media-session mode (ADB enabled + host). The `no_state_source` form error fires only when none of the three is present; ADB-only setups are valid. +### Channel entry options + +The options flow exposes the §7 channel behavior the integration replicates: +the inter-digit **delay** (0.05–1.0 s, default 0.1) and the optional +**ENTER press** after the digits (off by default — TiviMate's validated +behavior). Both are consumed by `play_media` with +`media_content_type: channel`. + ### Raw `adb_command` service (opt-in) When enabled in the integration's options (`adb_command_enabled`, **default diff --git a/custom_components/nvidia_shield/config_flow.py b/custom_components/nvidia_shield/config_flow.py index f2851a7..8ebc4f4 100644 --- a/custom_components/nvidia_shield/config_flow.py +++ b/custom_components/nvidia_shield/config_flow.py @@ -13,6 +13,7 @@ from .adb_session import ensure_adb_key from .connection import ShieldConnectionManager from .const import ( ATTR_MEDIA_DAG_INPUT_MAP, + CHANNEL_DIGIT_DELAY, CONF_ADB_COMMAND_ENABLED, CONF_ADB_ENABLED, CONF_ADB_HOST, @@ -20,6 +21,8 @@ from .const import ( CONF_ADB_STORE_APP_INFO, CONF_ANDROIDTV_ENTITY, CONF_APP_NAME_MAP, + CONF_CHANNEL_DIGIT_DELAY, + CONF_CHANNEL_SEND_ENTER, CONF_JELLYFIN_ENTITY, CONF_PAIRING_CODE, CONF_POWER_ENTITY, @@ -497,6 +500,16 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow): """Single-page options.""" errors = {} if user_input is not None: + # P5-2: validate the digit delay (0.05–1.0 s, §7 window) + delay = user_input.pop(CONF_CHANNEL_DIGIT_DELAY, None) + if delay is not None: + try: + delay = float(delay) + except (TypeError, ValueError): + errors["base"] = "invalid_digit_delay" + delay = None + if delay is not None and not (0.05 <= delay <= 1.0): + errors["base"] = "invalid_digit_delay" app_map_text = user_input.pop(CONF_APP_NAME_MAP, None) if app_map_text is None: app_map = self._options.get(CONF_APP_NAME_MAP, {}) @@ -507,6 +520,8 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow): if app_map is None: errors["base"] = "invalid_app_map" if not errors: + if delay is not None: + self._options[CONF_CHANNEL_DIGIT_DELAY] = delay self._options.update(user_input) self._options[CONF_APP_NAME_MAP] = app_map return self.async_create_entry(title="", data=self._options) @@ -537,6 +552,12 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow): recv_key = vol.Optional(CONF_RECEIVER_ENTITY, default=recv) schema_dict[recv_key] = sel["receiver"] + # P5-2: channel digit behavior (the user-validated §7 flow) + current_delay = self._options.get(CONF_CHANNEL_DIGIT_DELAY, CHANNEL_DIGIT_DELAY) + schema_dict[vol.Optional(CONF_CHANNEL_DIGIT_DELAY, default=current_delay)] = str + current_enter = self._options.get(CONF_CHANNEL_SEND_ENTER, False) + schema_dict[vol.Optional(CONF_CHANNEL_SEND_ENTER, default=current_enter)] = bool + # P4-2a: the raw adb_command service is opt-in (default off) current_flag = self._options.get(CONF_ADB_COMMAND_ENABLED, False) schema_dict[vol.Optional(CONF_ADB_COMMAND_ENABLED, default=current_flag)] = bool diff --git a/custom_components/nvidia_shield/strings.json b/custom_components/nvidia_shield/strings.json index 4624fb6..f01a742 100644 --- a/custom_components/nvidia_shield/strings.json +++ b/custom_components/nvidia_shield/strings.json @@ -55,7 +55,8 @@ "protocol_invalid_code": "Pairing has not started or the code is empty. Click Submit to retry.", "protocol_pair_failed": "The Shield rejected that pairing code. Click Submit to try again.", "no_state_source": "No state source selected. Provide at least one of: the Android TV Remote entity, the Nvidia Remote Protocol pairing, or ADB media-session mode (enable ADB and set the ADB Host).", - "invalid_app_map": "Each line must be package=name with a non-empty package (no spaces) and a non-empty name \u2014 e.g. ar.tvplayer.tv=TiviMate." + "invalid_app_map": "Each line must be package=name with a non-empty package (no spaces) and a non-empty name \u2014 e.g. ar.tvplayer.tv=TiviMate.", + "invalid_digit_delay": "The digit delay must be between 0.05 and 1.0 seconds." }, "abort": { "already_configured": "This Shield entity is already configured.", @@ -79,7 +80,9 @@ "adb_host": "ADB Host", "adb_port": "ADB Port", "app_name_map": "Apps name/icon map", - "adb_command_enabled": "Enable raw ADB shell service" + "adb_command_enabled": "Enable raw ADB shell service", + "channel_digit_delay": "Channel digit delay (seconds)", + "channel_send_enter": "Send ENTER after channel digits" }, "data_description": { "jellyfin_entity": "The Jellyfin media_player for this device. Session stream info is read from the HA Jellyfin integration's existing coordinator \u2014 no extra config needed.", @@ -91,7 +94,9 @@ "adb_host": "IP address for ADB connection. Leave blank to auto-detect from the Android TV Remote integration.", "adb_port": "ADB port (default 5555).", "app_name_map": "One entry per line, package=name (e.g. ar.tvplayer.tv=TiviMate). The friendly name resolves after the Android TV Remote entity and the protocol push; icons follow the media image lookup by name. Leave blank to clear.", - "adb_command_enabled": "Adds the nvidia_shield.adb_command service (raw ADB shell over the persistent connection). Security-relevant: leave off unless you need it \u2014 it is the same opt-in posture as the core androidtv integration." + "adb_command_enabled": "Adds the nvidia_shield.adb_command service (raw ADB shell over the persistent connection). Security-relevant: leave off unless you need it \u2014 it is the same opt-in posture as the core androidtv integration.", + "channel_digit_delay": "Pause between the channel digits entered via play_media(channel) \u2014 0.05 to 1.0 seconds (default 0.1, matching the validated behavior).", + "channel_send_enter": "Optionally presses ENTER after the last digit (protocol key, then the ADB keyevent fallback). Off by default \u2014 matches TiviMate's validated behavior." } } } diff --git a/custom_components/nvidia_shield/translations/en.json b/custom_components/nvidia_shield/translations/en.json index 4624fb6..f01a742 100644 --- a/custom_components/nvidia_shield/translations/en.json +++ b/custom_components/nvidia_shield/translations/en.json @@ -55,7 +55,8 @@ "protocol_invalid_code": "Pairing has not started or the code is empty. Click Submit to retry.", "protocol_pair_failed": "The Shield rejected that pairing code. Click Submit to try again.", "no_state_source": "No state source selected. Provide at least one of: the Android TV Remote entity, the Nvidia Remote Protocol pairing, or ADB media-session mode (enable ADB and set the ADB Host).", - "invalid_app_map": "Each line must be package=name with a non-empty package (no spaces) and a non-empty name \u2014 e.g. ar.tvplayer.tv=TiviMate." + "invalid_app_map": "Each line must be package=name with a non-empty package (no spaces) and a non-empty name \u2014 e.g. ar.tvplayer.tv=TiviMate.", + "invalid_digit_delay": "The digit delay must be between 0.05 and 1.0 seconds." }, "abort": { "already_configured": "This Shield entity is already configured.", @@ -79,7 +80,9 @@ "adb_host": "ADB Host", "adb_port": "ADB Port", "app_name_map": "Apps name/icon map", - "adb_command_enabled": "Enable raw ADB shell service" + "adb_command_enabled": "Enable raw ADB shell service", + "channel_digit_delay": "Channel digit delay (seconds)", + "channel_send_enter": "Send ENTER after channel digits" }, "data_description": { "jellyfin_entity": "The Jellyfin media_player for this device. Session stream info is read from the HA Jellyfin integration's existing coordinator \u2014 no extra config needed.", @@ -91,7 +94,9 @@ "adb_host": "IP address for ADB connection. Leave blank to auto-detect from the Android TV Remote integration.", "adb_port": "ADB port (default 5555).", "app_name_map": "One entry per line, package=name (e.g. ar.tvplayer.tv=TiviMate). The friendly name resolves after the Android TV Remote entity and the protocol push; icons follow the media image lookup by name. Leave blank to clear.", - "adb_command_enabled": "Adds the nvidia_shield.adb_command service (raw ADB shell over the persistent connection). Security-relevant: leave off unless you need it \u2014 it is the same opt-in posture as the core androidtv integration." + "adb_command_enabled": "Adds the nvidia_shield.adb_command service (raw ADB shell over the persistent connection). Security-relevant: leave off unless you need it \u2014 it is the same opt-in posture as the core androidtv integration.", + "channel_digit_delay": "Pause between the channel digits entered via play_media(channel) \u2014 0.05 to 1.0 seconds (default 0.1, matching the validated behavior).", + "channel_send_enter": "Optionally presses ENTER after the last digit (protocol key, then the ADB keyevent fallback). Off by default \u2014 matches TiviMate's validated behavior." } } }