Downgrade a lone poll-failure reconnect to info (issue #119)

Samsung's firmware occasionally drops the DTLS session briefly --
normal appliance-side behavior per the README's "Known device
behavior" section -- so the coordinator recovering from that on its
own doesn't need a WARNING. Only escalate once reconnects pile up
within a trailing 60s window (5+), matching the README's own "more
than a handful per minute" definition of an actually broken
connection.
This commit is contained in:
Marc Billow
2026-07-27 13:40:32 +00:00
parent f62b4aeec8
commit 2c8a765036
+30 -1
View File
@@ -85,6 +85,17 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
_OBSERVE_GRACE_PERIOD_S: float = GRACE_PERIOD_S
_RECONNECT_PAUSE_S: float = 5.0
# A single reconnect is normal appliance-side behavior (see the
# README's "Known device behavior" section) -- Samsung's firmware
# drops the DTLS session briefly every now and then, and the
# coordinator recovering from that on its own isn't something a user
# needs to see at WARNING. Only escalate once reconnects pile up
# within a short window, matching the README's own "more than a
# handful per minute" definition of an actually-broken connection
# (issue #119).
_RECONNECT_WARN_WINDOW_S: float = 60.0
_RECONNECT_WARN_THRESHOLD: int = 5
# A block-level ACK timeout on the summary GET doesn't prove the
# session is dead (see _poll_once) — require this many in a row
# before treating it as one. A single slow transfer on an otherwise
@@ -136,6 +147,7 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self.one_ui_version: str = ''
self._consecutive_poll_timeouts = 0
self._unbound_hrefs: list[str] = []
self._reconnect_times: list[float] = []
# ------------------------------------------------------------------
# Session management (all blocking — must run in executor)
@@ -444,6 +456,17 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self._consecutive_poll_timeouts += 1
return self._consecutive_poll_timeouts < self._POLL_TIMEOUT_LIMIT
def _reconnect_is_frequent(self) -> bool:
"""True once this cycle's reconnect is the Nth within the trailing
warn window -- see _RECONNECT_WARN_WINDOW_S/_RECONNECT_WARN_THRESHOLD."""
now = time.monotonic()
self._reconnect_times = [
t for t in self._reconnect_times
if now - t < self._RECONNECT_WARN_WINDOW_S
]
self._reconnect_times.append(now)
return len(self._reconnect_times) >= self._RECONNECT_WARN_THRESHOLD
# ------------------------------------------------------------------
# DataUpdateCoordinator hook
# ------------------------------------------------------------------
@@ -470,7 +493,13 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self._consecutive_poll_timeouts = 0
# One reconnect attempt — pause briefly so the device can
# clean up its DTLS session state before we knock again.
self._log.warning("poll failed, reconnecting: %s", e)
# A lone reconnect is routine (see the README's "Known
# device behavior" section); only warn once they're piling
# up within the trailing window.
if self._reconnect_is_frequent():
self._log.warning("poll failed, reconnecting: %s", e)
else:
self._log.info("poll failed, reconnecting: %s", e)
await self.hass.async_add_executor_job(self._close_session)
await asyncio.sleep(self._RECONNECT_PAUSE_S)
try: