diff --git a/custom_components/nvidia_shield/config_flow.py b/custom_components/nvidia_shield/config_flow.py index 963513b..f2851a7 100644 --- a/custom_components/nvidia_shield/config_flow.py +++ b/custom_components/nvidia_shield/config_flow.py @@ -280,21 +280,25 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): for entry in self._async_current_entries(): if getattr(entry, "unique_id", None) != mac: continue - # Known device — update hosts only on a real IP change - # (core androidtv_remote pattern, simplified): an explicitly - # configured host that moved to the discovery address. + # 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, "") - if (protocol_host and protocol_host != host) or ( - adb_host and adb_host != host - ): + 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 options != entry.options: self.hass.config_entries.async_update_entry( - entry, - options={ - **entry.options, - CONF_PROTOCOL_HOST: host, - CONF_ADB_HOST: host, - }, + entry, options=options ) self.hass.config_entries.async_schedule_reload(entry.entry_id) return self.async_abort(reason="already_configured") diff --git a/tests/test_standalone_setup.py b/tests/test_standalone_setup.py index 778ce9c..fba19e5 100644 --- a/tests/test_standalone_setup.py +++ b/tests/test_standalone_setup.py @@ -436,6 +436,34 @@ class TestZeroconfDiscovery: hass = FakeHass(str(tmp_path)) flow.hass = hass + # both hosts already hold the discovered address: nothing changes + entry = SimpleNamespace( + unique_id="aa:bb:cc:dd:ee:ff", + options={ + CONF_PROTOCOL_ENABLED: True, + CONF_PROTOCOL_HOST: "192.168.1.50", + "adb_host": "192.168.1.50", + }, + entry_id="e1", + title="Shield", + ) + sys.modules[__name__]._zc_holder.entries[:] = [entry] + + result = await flow.async_step_zeroconf( + self._discovery(props={"mac": "aa:bb:cc:dd:ee:ff"}) + ) + + assert result == {"type": "abort", "reason": "already_configured"} + assert hass.config_entries.updating_entry_calls == [] # no IP change + assert hass.config_entries.reloaded_entry_ids == [] + + async def test_blank_adb_host_self_heals_from_the_discovery_address(self, tmp_path): + """F2: a blank (auto-detect) adb host follows a same-IP rediscovery.""" + module = _form_module() + flow = module.NvidiaShieldConfigFlow() + hass = FakeHass(str(tmp_path)) + flow.hass = hass + entry = SimpleNamespace( unique_id="aa:bb:cc:dd:ee:ff", options={CONF_PROTOCOL_ENABLED: True, CONF_PROTOCOL_HOST: "192.168.1.50"}, @@ -449,8 +477,40 @@ class TestZeroconfDiscovery: ) assert result == {"type": "abort", "reason": "already_configured"} - assert hass.config_entries.updating_entry_calls == [] # no IP change - assert hass.config_entries.reloaded_entry_ids == [] + # only the adb host was filled (self-heal), protocol already current: + update = hass.config_entries.updating_entry_calls[0][1]["options"] + assert update["adb_host"] == "192.168.1.50" + assert update["protocol_host"] == "192.168.1.50" + assert hass.config_entries.reloaded_entry_ids == ["e1"] + + async def test_deliberately_different_adb_host_is_never_clobbered(self, tmp_path): + """F2: an explicit ADB override survives a protocol-host IP change.""" + module = _form_module() + flow = module.NvidiaShieldConfigFlow() + hass = FakeHass(str(tmp_path)) + flow.hass = hass + + entry = SimpleNamespace( + unique_id="aa:bb:cc:dd:ee:ff", + options={ + CONF_PROTOCOL_ENABLED: True, + CONF_PROTOCOL_HOST: "10.0.0.1", # stale (IP moved) + "adb_host": "10.0.0.9", # deliberate ADB override + }, + entry_id="e1", + title="Shield", + ) + sys.modules[__name__]._zc_holder.entries[:] = [entry] + + result = await flow.async_step_zeroconf( + self._discovery(props={"mac": "aa:bb:cc:dd:ee:ff"}) + ) + + assert result == {"type": "abort", "reason": "already_configured"} + update = hass.config_entries.updating_entry_calls[0][1]["options"] + assert update["protocol_host"] == "192.168.1.50" # protocol followed + assert update["adb_host"] == "10.0.0.9" # override untouched + assert hass.config_entries.reloaded_entry_ids == ["e1"] class TestAppsMapOptionsEditor: