Backoff MQTT failures all the way up to 1hr on connection failure, and also don't multi-toast on connection error. Closes #231.

This commit is contained in:
Jack Kingsman
2026-04-28 12:00:03 -07:00
parent 2c1279eb9e
commit 761fd82da6
5 changed files with 13 additions and 8 deletions

View File

@@ -245,7 +245,7 @@ def _get_client_version() -> str:
class CommunityMqttPublisher(BaseMqttPublisher):
"""Manages the community MQTT connection and publishes raw packets."""
_backoff_max = 60
_backoff_max = 3600
_log_prefix = "Community MQTT"
_not_configured_timeout: float | None = 30

View File

@@ -27,7 +27,7 @@ class PrivateMqttSettings(Protocol):
class MqttPublisher(BaseMqttPublisher):
"""Manages an MQTT connection and publishes mesh network events."""
_backoff_max = 30
_backoff_max = 3600
_log_prefix = "MQTT"
def _is_configured(self) -> bool:

View File

@@ -65,6 +65,7 @@ class BaseMqttPublisher(ABC):
self.connected: bool = False
self.integration_name: str = ""
self._last_error: str | None = None
self._error_notified: bool = False
def set_integration_name(self, name: str) -> None:
"""Attach the configured fanout-module name for operator-facing logs."""
@@ -104,6 +105,7 @@ class BaseMqttPublisher(ABC):
self._client = None
self.connected = False
self._last_error = None
self._error_notified = False
async def restart(self, settings: object) -> None:
"""Called when settings change — stop + start."""
@@ -217,6 +219,7 @@ class BaseMqttPublisher(ABC):
self._client = client
self.connected = True
self._last_error = None
self._error_notified = False
backoff = _BACKOFF_MIN
title, detail = self._on_connected(settings)
@@ -281,9 +284,11 @@ class BaseMqttPublisher(ABC):
)
return
title, detail = self._on_error()
broadcast_error(title, detail)
_broadcast_health()
if not self._error_notified:
title, detail = self._on_error()
broadcast_error(title, detail)
_broadcast_health()
self._error_notified = True
logger.warning(
"%s connection error. This is usually transient network noise; "
"if it self-resolves, it is generally not a concern: %s "

View File

@@ -316,7 +316,7 @@ def _device_payload(
class _HaMqttPublisher(BaseMqttPublisher):
"""Thin MQTT lifecycle wrapper for the HA discovery module."""
_backoff_max = 30
_backoff_max = 3600
_log_prefix = "HA-MQTT"
def __init__(self) -> None:

View File

@@ -342,8 +342,8 @@ class TestConnectionLoop:
assert sleep_args[0] == _BACKOFF_MIN
assert sleep_args[1] == _BACKOFF_MIN * 2
assert sleep_args[2] == _BACKOFF_MIN * 4
# Fourth should be capped at _backoff_max (5*8=40 > 30)
assert sleep_args[3] == MqttPublisher._backoff_max
# Fourth is still doubling (5*8=40), not yet at _backoff_max
assert sleep_args[3] == _BACKOFF_MIN * 8
@pytest.mark.asyncio
async def test_waits_for_settings_when_unconfigured(self):