diff --git a/BlocksScreen/lib/klipper_message_filter.py b/BlocksScreen/lib/klipper_message_filter.py index db7c22aa..89f5811d 100644 --- a/BlocksScreen/lib/klipper_message_filter.py +++ b/BlocksScreen/lib/klipper_message_filter.py @@ -285,18 +285,26 @@ def _re(pattern: str) -> Callable[[str], bool]: hint="Replace the empty spool", severity=Severity.ERROR, ), + # Same clog/tangle word Happy Hare reports and Mainsail's FlowGuard meter shows. MessageRule( source=MessageSource.GCODE_ERROR, - matcher=_sub("flowguard detected a clog"), + matcher=_sub("clog/tangle has been detected"), + display="Filament Clog or Tangle", + hint="Check the nozzle and filament path", + severity=Severity.ERROR, + ), + MessageRule( + source=MessageSource.GCODE_ERROR, + matcher=_re(r"flowguard detected a clog|a clog has been detected"), display="Filament Clog Detected", - hint="Check the extruder for a clog", + hint="Check the nozzle and extruder", severity=Severity.ERROR, ), MessageRule( source=MessageSource.GCODE_ERROR, - matcher=_sub("flowguard detected a tangle"), + matcher=_re(r"flowguard detected a tangle|a tangle has been detected"), display="Filament Tangle Detected", - hint="Clear the tangle", + hint="Check the spool and the feed into the MMU", severity=Severity.ERROR, ), # ── Gcode errors — heater / temperature ─────────────────────────────────── diff --git a/BlocksScreen/lib/moonrakerComm.py b/BlocksScreen/lib/moonrakerComm.py index fe505152..bb2e3144 100644 --- a/BlocksScreen/lib/moonrakerComm.py +++ b/BlocksScreen/lib/moonrakerComm.py @@ -57,7 +57,6 @@ def __init__(self, parent: QtCore.QObject) -> None: self.daemon = True self.connected = False - self.disconnected = False self._reconnect_count = 0 self._klippy_state: str | None = None self.callback_table: dict = {} @@ -185,10 +184,8 @@ def on_error(self, *args) -> None: """Websocket error callback""" # First argument is ws second is error message _error = args[1] if len(args) == 2 else args[0] - logger.error("Websocket error, disconnected: %s", _error) - with self._state_lock: - self.connected = False - self.disconnected = True + # Raising callbacks land here with the socket open; real drops end in on_close. + logger.error("Websocket error: %s", _error) def on_close(self, *args) -> None: """Websocket on close callback diff --git a/tests/lib/test_klipper_message_filter.py b/tests/lib/test_klipper_message_filter.py index 3597e99d..e51ba4b2 100644 --- a/tests/lib/test_klipper_message_filter.py +++ b/tests/lib/test_klipper_message_filter.py @@ -265,10 +265,33 @@ def test_re_no_match() -> None: ), ( MessageSource.GCODE_ERROR, - "FlowGuard detected a tangle.\nReason for trip: Tension stuck", + "FlowGuard detected a tangle.\nReason for trip: Tension stuck after " + "38.20 mm motion and 20.00 mm relief " + "(triggering parameter: flowguard_max_relief)", "Filament Tangle Detected", Severity.ERROR, ), + ( + MessageSource.GCODE_ERROR, + "MMU issue detected. Print will be paused\nReason: A tangle has been " + "detected on filament_tension and requires manual intervention", + "Filament Tangle Detected", + Severity.ERROR, + ), + ( + MessageSource.GCODE_ERROR, + "MMU issue detected. Print will be paused\nReason: A clog has been " + "detected on filament_compression and requires manual intervention", + "Filament Clog Detected", + Severity.ERROR, + ), + ( + MessageSource.GCODE_ERROR, + "MMU issue: A clog/tangle has been detected and requires manual " + "intervention", + "Filament Clog or Tangle", + Severity.ERROR, + ), # Gcode Echo ( MessageSource.GCODE_ECHO, diff --git a/tests/lib/test_moonraker_comm_unit.py b/tests/lib/test_moonraker_comm_unit.py index 044c022b..6ac45ce1 100644 --- a/tests/lib/test_moonraker_comm_unit.py +++ b/tests/lib/test_moonraker_comm_unit.py @@ -266,6 +266,14 @@ def close_then_fail(_packet): qtbot.wait(20) assert got == [{"error": {"code": -1, "message": "disconnected"}}] + def test_callback_error_keeps_socket_usable(self, ws): + """A raising callback reaches on_error but the socket stays open.""" + ws.connected = True + ws.ws = MagicMock() + ws.on_error(ws.ws, AttributeError("'FilamentTab' has no '_material_filter'")) + assert ws.send_request("server.info") is True + ws.ws.close.assert_not_called() + def test_send_while_disconnected_never_calls_back(self, ws, qtbot): callback = MagicMock() assert ws.send_request("server.info", callback=callback) is False