Просмотр исходного кода

fix(notifications): defer first-layer photo until printer is actually printing (#1837)

P1S and other Bambu printers tick layer_num during the pre-print calibration
sequence (homing -> auto bed leveling -> bed-surface scan -> nozzle clean ->
purge / wipe), so a bare `2 <= layer_num <= 5` gate fires the first-layer
notification minutes before the first real extrusion. The attached photo
shows a lowered bed, parked toolhead, and a clean plate -- exactly the
state during PREPARE, not after layer 1.

The reporter's log timeline made it explicit:
- 13:54:27  PRINT START detected
- 14:10:13  [SNAPSHOT] Capturing fresh frame  (notification fires here)
- 14:44:28  gcode_state: RUNNING (debug log, only visible because they
            enabled debug logging mid-print)

So the notification went out ~30 minutes before the print actually started.

Fix in main.py:6043 -- the on_layer_change first-layer block now requires
both:

  - state.state == "RUNNING" (gcode_state is RUNNING, not PREPARE)
  - state.mc_print_sub_stage in (None, 0)
    (0 = "Printing" in the canonical STAGE_NAMES at bambu_mqtt.py:376;
    None preserved as a no-opinion fall-through for any firmware that
    doesn't push the sub-stage so unknown-firmware installs keep
    their existing behaviour)

_first_layer_notified is only set after the gate passes, so calibration
ticks are non-consuming -- the next on_layer_change edge fires the
notification once the printer is actually printing.

The trigger window widens from [2, 5] to [2, 10] so that if calibration
consumed several layer_num slots before RUNNING, the deferred edge
still falls inside. The RUNNING + sub-stage gate ensures we don't fire
on a stale layer count.
maziggy 2 месяцев назад
Родитель
Сommit
b5a2f56cca
1 измененных файлов с 17 добавлено и 4 удалено
  1. 17 4
      backend/app/main.py

+ 17 - 4
backend/app/main.py

@@ -6040,8 +6040,23 @@ async def lifespan(app: FastAPI):
 
         await tl_layer_change(printer_id, layer_num)
 
-        # First layer complete notification (layer_num >= 2 means layer 1 is done)
-        if 2 <= layer_num <= 5 and not _first_layer_notified.get(printer_id, False):
+        # First layer complete notification (layer_num >= 2 means layer 1 is done).
+        # Gate on actual printing state — Bambu firmware ticks layer_num during
+        # the pre-print calibration sequence (homing / mesh-level / bed scan /
+        # nozzle clean), so a bare layer_num check can fire minutes before the
+        # first real extrusion. We require gcode_state == RUNNING and
+        # mc_print_sub_stage in (0 = "Printing", None) so calibration sub-stages
+        # (1, 9, 14, ...) are excluded. The window widens to [2, 10] because if
+        # the layer counter advanced past 2 during PREPARE, the next on_layer_change
+        # edge fires later; _first_layer_notified stays clear until we actually send
+        # so a deferred re-evaluation can win. See issue #1837.
+        if 2 <= layer_num <= 10 and not _first_layer_notified.get(printer_id, False):
+            client = printer_manager.get_client(printer_id)
+            state = client.state if client else None
+            if not state or state.state != "RUNNING":
+                return
+            if state.mc_print_sub_stage not in (None, 0):
+                return
             _first_layer_notified[printer_id] = True
             try:
                 async with async_session() as db:
@@ -6052,8 +6067,6 @@ async def lifespan(app: FastAPI):
                     if not printer:
                         return
                     printer_name = printer.name
-                    client = printer_manager.get_client(printer_id)
-                    state = client.state if client else None
                     filename = (state.subtask_name or state.gcode_file or "Unknown") if state else "Unknown"
                     total_layers = state.total_layers if state else 0