From ce90f59f8e7ba103c6130ea51053bc1ede06d2a8 Mon Sep 17 00:00:00 2001 From: James Mackay Date: Thu, 8 Oct 2026 10:52:44 +0100 Subject: [PATCH] =?UTF-8?q?fix(config=5Fflow):=20no=5Fstate=5Fsource=20fir?= =?UTF-8?q?es=20only=20when=20none=20of=20the=20three=20sources=20is=20pre?= =?UTF-8?q?sent=20=E2=80=94=20ADB-only=20standalone=20is=20valid;=20string?= =?UTF-8?q?s/docs=20name=20all=20three=20(F2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++ .../nvidia_shield/config_flow.py | 17 +++++++--- custom_components/nvidia_shield/strings.json | 2 +- .../nvidia_shield/translations/en.json | 2 +- tests/test_standalone_setup.py | 34 +++++++++++++++++++ 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f9e5812..1a066ee 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,11 @@ TiviMate (`ar.tvplayer.tv`) is the documented no-session case: it publishes no MediaSession on Shield Android 11 (RESEARCH_SHIELD R2), so tiers 6–7 plus the audio hint are what keep it visible. +**Standalone setup** accepts any one of the three state sources — the Android +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. + ### Migration from mediasession2mqtt + universal-player glue The `mediasession2mqtt` bridge sensors and the universal media player YAML diff --git a/custom_components/nvidia_shield/config_flow.py b/custom_components/nvidia_shield/config_flow.py index f94c6f0..5ace53b 100644 --- a/custom_components/nvidia_shield/config_flow.py +++ b/custom_components/nvidia_shield/config_flow.py @@ -128,15 +128,24 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._reauth_entry = None async def async_step_user(self, user_input=None): - """Step 1: Basic setup.""" + """Step 1: Basic setup. + + Standalone setup (Phase 3): any one of the three state sources is + enough — the Android TV Remote entity, the Nvidia Remote Protocol + (pairing), or ADB media-session mode (CONF_ADB_ENABLED). The + ``no_state_source`` error fires only when none of them is present. + """ errors = {} if user_input is not None: - # Standalone setup (Phase 3): the external androidtv media_player - # is optional — the protocol push can be the only state source. androidtv_entity = user_input.get(CONF_ANDROIDTV_ENTITY) - if not androidtv_entity and not user_input.get(CONF_PROTOCOL_ENABLED): + has_source = ( + bool(androidtv_entity) + or bool(user_input.get(CONF_PROTOCOL_ENABLED)) + or bool(user_input.get(CONF_ADB_ENABLED)) + ) + if not has_source: errors["base"] = "no_state_source" sel = _build_selectors() return self.async_show_form( diff --git a/custom_components/nvidia_shield/strings.json b/custom_components/nvidia_shield/strings.json index d1cc3c6..0287a29 100644 --- a/custom_components/nvidia_shield/strings.json +++ b/custom_components/nvidia_shield/strings.json @@ -54,7 +54,7 @@ "protocol_no_host": "Could not determine the Shield's IP address. Set the Protocol Host or ADB Host field.", "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. Link the Android TV Remote entity or enable the Nvidia Remote Protocol pairing \u2014 the integration needs at least one source of power/app state." + "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)." }, "abort": { "already_configured": "This Shield entity is already configured.", diff --git a/custom_components/nvidia_shield/translations/en.json b/custom_components/nvidia_shield/translations/en.json index d1cc3c6..0287a29 100644 --- a/custom_components/nvidia_shield/translations/en.json +++ b/custom_components/nvidia_shield/translations/en.json @@ -54,7 +54,7 @@ "protocol_no_host": "Could not determine the Shield's IP address. Set the Protocol Host or ADB Host field.", "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. Link the Android TV Remote entity or enable the Nvidia Remote Protocol pairing \u2014 the integration needs at least one source of power/app state." + "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)." }, "abort": { "already_configured": "This Shield entity is already configured.", diff --git a/tests/test_standalone_setup.py b/tests/test_standalone_setup.py index de38599..64590da 100644 --- a/tests/test_standalone_setup.py +++ b/tests/test_standalone_setup.py @@ -256,6 +256,40 @@ class TestStandaloneValidation: assert CONF_ANDROIDTV_ENTITY not in result["data"] +class TestAdbOnlyStandalone: + async def test_adb_only_standalone_is_valid(self, tmp_path): + """F2: ADB media-session mode is a third accepted state source.""" + module = _form_module() + flow = module.NvidiaShieldConfigFlow() + flow.hass = FakeHass(str(tmp_path)) + + result = await flow.async_step_user( + _user_input( + **{ + CONF_ANDROIDTV_ENTITY: "", + CONF_PROTOCOL_ENABLED: False, + "adb_enabled": True, + } + ) + ) + + # routed into ADB pairing — no no_state_source error + assert result["type"] == "form" + assert result["step_id"] == "adb_pair" + assert result["errors"] == {} + + async def test_error_still_fires_with_no_source_at_all(self, tmp_path): + module = _form_module() + flow = module.NvidiaShieldConfigFlow() + flow.hass = FakeHass(str(tmp_path)) + + result = await flow.async_step_user( + _user_input(**{CONF_ANDROIDTV_ENTITY: "", "adb_enabled": False}) + ) + + assert result["errors"] == {"base": "no_state_source"} + + class TestMigrationKeepsOrAllowsAbsence: async def test_v1_to_v2_keeps_the_androidtv_field(self, tmp_path): module = _entry_module()