Force MQTT pushall re-sync after a report gap (fixes #14)

The printer publishes partial deltas most of the time; the accumulator
merges each one onto whatever state it already has. When the printer
powers off, our cloud MQTT connection stays up (it's a persistent
session to Bambu's broker, not a socket to the printer), so nothing
signals the outage. When the printer reconnects and resumes its normal
partial updates, those deltas get merged onto the stale pre-outage
state — fields the delta doesn't mention (e.g. nozzle_temp) stay frozen
at their old values. Sending any command happens to trigger a full
report from the printer, which is why toggling the light "fixes" it.

Track the time of the last message per BambuPrinter instance. When a
new message arrives after a gap longer than
BAMBU_RUN_MQTT_RESYNC_GAP_SECONDS (default 90s), request a pushall
before processing it, forcing a complete state refresh instead of
trusting the partial delta to overwrite stale fields.
This commit is contained in:
RNL
2026-08-02 23:14:03 +10:00
parent 6f19560842
commit 6f281a0cab
2 changed files with 33 additions and 0 deletions

View File

@@ -81,5 +81,12 @@ class _Settings:
def CLOUD_SYNC_DAYS(self): def CLOUD_SYNC_DAYS(self):
return get_setting("BAMBU_RUN_CLOUD_SYNC_DAYS", 30) return get_setting("BAMBU_RUN_CLOUD_SYNC_DAYS", 30)
# Seconds of silence on the MQTT report topic that, once broken by a new
# message, is treated as "the printer was probably offline" and triggers
# a pushall re-sync instead of trusting the partial delta to fill in stale
# fields left over from before the gap.
@property
def MQTT_RESYNC_GAP_SECONDS(self):
return get_setting("BAMBU_RUN_MQTT_RESYNC_GAP_SECONDS", 90)
app_settings = _Settings() app_settings = _Settings()

View File

@@ -21,6 +21,7 @@ import os
import platform import platform
import sys import sys
import select import select
import time
from contextlib import contextmanager from contextlib import contextmanager
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
@@ -809,6 +810,7 @@ class BambuPrinter:
self._accumulator = PrinterStateAccumulator() self._accumulator = PrinterStateAccumulator()
self._connected = False self._connected = False
self._devices: List[Dict[str, Any]] = [] self._devices: List[Dict[str, Any]] = []
self._last_message_at: Optional[float] = None
def _get_fresh_token(self, verification_code_timeout: int = 300) -> str: def _get_fresh_token(self, verification_code_timeout: int = 300) -> str:
"""Get a fresh token using credentials.""" """Get a fresh token using credentials."""
@@ -914,6 +916,30 @@ class BambuPrinter:
"""Internal MQTT message handler""" """Internal MQTT message handler"""
if not data: if not data:
return return
# The printer publishes partial deltas most of the time; the accumulator
# merges them onto whatever it already has. If the printer went offline
# (power cycle) and just reconnected, the first delta after the gap would
# otherwise be merged onto stale pre-outage state, leaving fields like
# nozzle_temp frozen at their last value until some unrelated full report
# happens to refresh them. Detect the gap and force a full pushall so the
# accumulator gets a clean, complete state instead.
now = time.time()
if (
self._last_message_at is not None
and now - self._last_message_at > app_settings.MQTT_RESYNC_GAP_SECONDS
and self._mqtt is not None
):
try:
self._mqtt.request_full_status()
logger.info(
"MQTT report gap of %.0fs detected for %s; requested full status re-sync",
now - self._last_message_at, device_id,
)
except Exception as e:
logger.warning("Full status re-sync request failed (non-fatal): %s", e)
self._last_message_at = now
state = self._accumulator.update(data) state = self._accumulator.update(data)
if self._on_update: if self._on_update:
self._on_update(state) self._on_update(state)