From 17bd181efd936d02f6aab28920f933c2736cadde Mon Sep 17 00:00:00 2001 From: Giovanni Condello Date: Wed, 24 Jun 2026 23:01:01 +0200 Subject: [PATCH 1/2] fix(ha): emit retain as JSON boolean in discovery payloads str(retain).lower() produced the Python strings "true"/"false" which json.dumps serialized as JSON strings. Home Assistant's MQTT discovery schema expects a boolean; the string caused schema validation to reject the retain flag, so command topics for number/select entities were not retained on the broker and gateway settings were lost after an HA restart. --- src/integrations/home_assistant/base.py | 6 +++--- tests/integrations/home_assistant/test_discovery_retain.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/integrations/home_assistant/base.py b/src/integrations/home_assistant/base.py index 322fa60..8f12dbe 100644 --- a/src/integrations/home_assistant/base.py +++ b/src/integrations/home_assistant/base.py @@ -55,7 +55,7 @@ def _publish_select( "command_topic": self._get_command_topic(topic), "value_template": value_template, "command_template": command_template, - "retain": str(retain).lower(), + "retain": retain, "options": options, "enabled_by_default": enabled, } @@ -87,7 +87,7 @@ def _publish_text( "command_topic": self._get_command_topic(topic), "value_template": value_template, "command_template": command_template, - "retain": str(retain).lower(), + "retain": retain, "enabled_by_default": enabled, } if min_value is not None: @@ -153,7 +153,7 @@ def _publish_number( "state_topic": self._get_state_topic(topic), "command_topic": self._get_command_topic(topic), "value_template": value_template, - "retain": str(retain).lower(), + "retain": retain, "mode": mode, "min": min_value, "max": max_value, diff --git a/tests/integrations/home_assistant/test_discovery_retain.py b/tests/integrations/home_assistant/test_discovery_retain.py index 45fa789..f56731f 100644 --- a/tests/integrations/home_assistant/test_discovery_retain.py +++ b/tests/integrations/home_assistant/test_discovery_retain.py @@ -104,7 +104,7 @@ def test_required_entities_have_retain_true(self) -> None: assert payload is not None, ( f"No writable HA discovery payload found for topic {topic}" ) - assert payload.get("retain") == "true", ( + assert payload.get("retain") is True, ( f"Expected retain=true for {topic}, got {payload.get('retain')!r}" ) @@ -117,6 +117,6 @@ def test_non_retained_entities_keep_retain_false(self) -> None: payload = _payload_for_state_topic_suffix(payloads, topic) if payload is None: continue # entity not published for this vehicle config - assert payload.get("retain") in ("false", None), ( + assert payload.get("retain") in (False, None), ( f"Expected retain!=true for {topic}, got {payload.get('retain')!r}" ) From 11af44847856d2c5825cf0673232a6451a191fc4 Mon Sep 17 00:00:00 2001 From: Giovanni Condello Date: Wed, 24 Jun 2026 23:01:16 +0200 Subject: [PATCH 2/2] style: apply ruff formatting --- src/publisher/mqtt_publisher.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/publisher/mqtt_publisher.py b/src/publisher/mqtt_publisher.py index 7f4b71a..b6de247 100644 --- a/src/publisher/mqtt_publisher.py +++ b/src/publisher/mqtt_publisher.py @@ -133,7 +133,9 @@ async def __run_loop(self) -> None: reconnect_interval, ) await asyncio.sleep(reconnect_interval) - reconnect_interval = min(reconnect_interval * 2, _RECONNECT_INTERVAL_MAX) + reconnect_interval = min( + reconnect_interval * 2, _RECONNECT_INTERVAL_MAX + ) except aiomqtt.MqttError: LOG.warning( "Connection to %s:%s lost; Reconnecting in %d seconds ...", @@ -142,7 +144,9 @@ async def __run_loop(self) -> None: reconnect_interval, ) await asyncio.sleep(reconnect_interval) - reconnect_interval = min(reconnect_interval * 2, _RECONNECT_INTERVAL_MAX) + reconnect_interval = min( + reconnect_interval * 2, _RECONNECT_INTERVAL_MAX + ) except asyncio.exceptions.CancelledError: LOG.debug("MQTT publisher loop cancelled") raise