From c24e291017c1e81fa26d5d11fd8e3b86c807a306 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Sun, 12 Apr 2026 14:59:41 -0700 Subject: [PATCH] Destroy old discovery topics when the radio key changes --- app/fanout/mqtt_ha.py | 6 +++++- tests/test_mqtt_ha.py | 50 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/app/fanout/mqtt_ha.py b/app/fanout/mqtt_ha.py index 094c651..635f1f3 100644 --- a/app/fanout/mqtt_ha.py +++ b/app/fanout/mqtt_ha.py @@ -519,12 +519,16 @@ class MqttHaModule(FanoutModule): if key_changed: old_key = self._radio_key + old_topics = list(self._discovery_topics) + if old_topics: + await self._clear_retained_topics(old_topics) + self._discovery_topics.clear() self._radio_key = pub_key self._radio_name = new_name # Remove stale discovery entries from the old identity (e.g. # "unknown" placeholder from before the radio key was known), # then re-publish with the real identity. - if old_key is not None: + if old_key is not None and not old_topics: await self._clear_retained_topics( [t for t, _ in _radio_discovery_configs(self._prefix, old_key, "")] ) diff --git a/tests/test_mqtt_ha.py b/tests/test_mqtt_ha.py index d8973fc..c96ff0f 100644 --- a/tests/test_mqtt_ha.py +++ b/tests/test_mqtt_ha.py @@ -286,6 +286,56 @@ class TestMqttHaHealth: assert mod._radio_key == "aabbccddeeff" assert mod._radio_name == "MyRadio" + @pytest.mark.asyncio + async def test_on_health_key_change_clears_all_existing_discovery_topics(self): + mod = MqttHaModule("test", _base_config()) + mod._publisher = MagicMock() + mod._publisher.connected = True + mod._publisher.publish = AsyncMock() + mod._radio_key = "aabbccddeeff" + mod._radio_name = "OldRadio" + mod._discovery_topics = [ + "homeassistant/sensor/meshcore_aabbccddeeff/noise_floor/config", + "homeassistant/event/meshcore_aabbccddeeff/messages/config", + "homeassistant/device_tracker/meshcore_ccdd11223344/config", + "homeassistant/sensor/meshcore_eeff11223344/battery_voltage/config", + ] + + mod._clear_retained_topics = AsyncMock() + + async def publish_discovery_side_effect(): + assert mod._discovery_topics == [] + mod._discovery_topics = [ + "homeassistant/sensor/meshcore_112233445566/noise_floor/config", + "homeassistant/event/meshcore_112233445566/messages/config", + ] + + mod._publish_discovery = AsyncMock(side_effect=publish_discovery_side_effect) + + await mod.on_health( + { + "connected": True, + "public_key": "112233445566", + "name": "NewRadio", + } + ) + + mod._clear_retained_topics.assert_awaited_once_with( + [ + "homeassistant/sensor/meshcore_aabbccddeeff/noise_floor/config", + "homeassistant/event/meshcore_aabbccddeeff/messages/config", + "homeassistant/device_tracker/meshcore_ccdd11223344/config", + "homeassistant/sensor/meshcore_eeff11223344/battery_voltage/config", + ] + ) + mod._publish_discovery.assert_awaited_once() + assert mod._radio_key == "112233445566" + assert mod._radio_name == "NewRadio" + assert mod._discovery_topics == [ + "homeassistant/sensor/meshcore_112233445566/noise_floor/config", + "homeassistant/event/meshcore_112233445566/messages/config", + ] + class TestMqttHaLifecycle: @pytest.mark.asyncio