Browse Source

[Fix] Print complete notification not firing (#736)

  Print complete notifications were chained behind the finish photo
  capture task with no timeout. If photo capture hung, the notification
  would never send. Added a 45-second timeout so notifications always
  fire regardless of photo outcome.

  Also added diagnostic logging to MQTT state detection and upgraded
  notification error logging to include stack traces for easier
  debugging.
maziggy 2 months ago
parent
commit
dbe86968b0
3 changed files with 19 additions and 6 deletions
  1. 1 0
      CHANGELOG.md
  2. 10 6
      backend/app/main.py
  3. 8 0
      backend/app/services/bambu_mqtt.py

+ 1 - 0
CHANGELOG.md

@@ -12,6 +12,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **X1C Virtual Printer Not Accepting Sends** ([#735](https://github.com/maziggy/bambuddy/issues/735)) — X1C (and X1) virtual printers were advertised with legacy SSDP model codes (`3DPrinter-X1-Carbon` / `3DPrinter-X1`) that BambuStudio doesn't recognize, causing "incompatible printer preset" when sending. Fixed to use the correct codes (`BL-P001` / `BL-P002`). Also fixed proxy mode auto-inherit storing the printer's display name (e.g. `X1C`) instead of the SSDP code. Existing VPs are automatically migrated on startup. Reported by @RosdasHH.
 - **X1C Virtual Printer Not Accepting Sends** ([#735](https://github.com/maziggy/bambuddy/issues/735)) — X1C (and X1) virtual printers were advertised with legacy SSDP model codes (`3DPrinter-X1-Carbon` / `3DPrinter-X1`) that BambuStudio doesn't recognize, causing "incompatible printer preset" when sending. Fixed to use the correct codes (`BL-P001` / `BL-P002`). Also fixed proxy mode auto-inherit storing the printer's display name (e.g. `X1C`) instead of the SSDP code. Existing VPs are automatically migrated on startup. Reported by @RosdasHH.
 - **White Filament Color Swatches Invisible in Light Theme** ([#726](https://github.com/maziggy/bambuddy/issues/726)) — Filament color circles used a white border that was invisible against light theme backgrounds, making white spools indistinguishable. Changed to a dark border (`border-black/20`) across all views: Inventory, Archives, Assign Spool, Configure AMS Slot, Calendar, Projects, Filament Trends, Local Profiles, Link Spool, and Spoolman Settings. Reported by user.
 - **White Filament Color Swatches Invisible in Light Theme** ([#726](https://github.com/maziggy/bambuddy/issues/726)) — Filament color circles used a white border that was invisible against light theme backgrounds, making white spools indistinguishable. Changed to a dark border (`border-black/20`) across all views: Inventory, Archives, Assign Spool, Configure AMS Slot, Calendar, Projects, Filament Trends, Local Profiles, Link Spool, and Spoolman Settings. Reported by user.
 - **Camera Window Overlapping Modals** ([#738](https://github.com/maziggy/bambuddy/issues/738)) — Floating camera viewer rendered on top of modals (e.g. Assign Spool), making them unusable. Lowered camera z-index so modals always appear above it. Reported by @maziggy.
 - **Camera Window Overlapping Modals** ([#738](https://github.com/maziggy/bambuddy/issues/738)) — Floating camera viewer rendered on top of modals (e.g. Assign Spool), making them unusable. Lowered camera z-index so modals always appear above it. Reported by @maziggy.
+- **Print Complete Notification Not Firing** ([#736](https://github.com/maziggy/bambuddy/issues/736)) — Print complete notifications could silently fail if the finish photo capture hung or timed out, because the notification was chained behind the photo task with no timeout. Added a 45-second timeout so notifications always send even if photo capture stalls. Also added diagnostic logging for MQTT state detection to trace completion triggers. Reported by @piatho.
 - **Webhook Notifications Missing Camera Snapshot** ([#679](https://github.com/maziggy/bambuddy/issues/679)) — Webhook notification providers did not include camera snapshots (e.g. from First Layer Complete notifications), even though providers like Telegram, Pushover, ntfy, and Discord already attached them. The webhook payload now includes a base64-encoded `image` field when a snapshot is available (generic format only, not Slack format). Reported by @Arn0uDz.
 - **Webhook Notifications Missing Camera Snapshot** ([#679](https://github.com/maziggy/bambuddy/issues/679)) — Webhook notification providers did not include camera snapshots (e.g. from First Layer Complete notifications), even though providers like Telegram, Pushover, ntfy, and Discord already attached them. The webhook payload now includes a base64-encoded `image` field when a snapshot is available (generic format only, not Slack format). Reported by @Arn0uDz.
 - **Mobile Sidebar Not Scrollable** — On mobile devices with many navigation items, the sidebar did not scroll, making bottom items unreachable. Added overflow scrolling to the nav section while keeping the logo and footer pinned.
 - **Mobile Sidebar Not Scrollable** — On mobile devices with many navigation items, the sidebar did not scroll, making bottom items unreachable. Added overflow scrolling to the nav section while keeping the logo and footer pinned.
 - **User Notification Ruff/Lint Fixes** ([#693](https://github.com/maziggy/bambuddy/pull/693)) — Fixed missing `timezone` import in email timestamp, unused lambda argument, PEP 8 blank line spacing for `mark_printer_stopped_by_user`, and SQLAlchemy forward reference in `UserEmailPreference` model.
 - **User Notification Ruff/Lint Fixes** ([#693](https://github.com/maziggy/bambuddy/pull/693)) — Fixed missing `timezone` import in email timestamp, unused lambda argument, PEP 8 blank line spacing for `mark_printer_stopped_by_user`, and SQLAlchemy forward reference in `UserEmailPreference` model.

+ 10 - 6
backend/app/main.py

@@ -3097,7 +3097,7 @@ async def on_print_complete(printer_id: int, data: dict):
 
 
                 logger.info("[NOTIFY-BG] Completed")
                 logger.info("[NOTIFY-BG] Completed")
         except Exception as e:
         except Exception as e:
-            logger.warning("[NOTIFY-BG] Failed: %s", e)
+            logger.error("[NOTIFY-BG] Failed: %s", e, exc_info=True)
 
 
     async def _background_maintenance_check():
     async def _background_maintenance_check():
         """Check for maintenance due in background."""
         """Check for maintenance due in background."""
@@ -3145,17 +3145,21 @@ async def on_print_complete(printer_id: int, data: dict):
     asyncio.create_task(_background_smart_plug())
     asyncio.create_task(_background_smart_plug())
     asyncio.create_task(_background_maintenance_check())
     asyncio.create_task(_background_maintenance_check())
 
 
-    # Notification task waits for photo capture to complete first
+    # Notification task waits for photo capture to complete first (with timeout)
     async def _photo_then_notify():
     async def _photo_then_notify():
         """Wait for photo capture, then send notification with photo URL."""
         """Wait for photo capture, then send notification with photo URL."""
+        finish_photo = None
         try:
         try:
-            finish_photo = await photo_task
+            finish_photo = await asyncio.wait_for(photo_task, timeout=45)
             logger.info("[PHOTO-NOTIFY] Photo task returned: %s", finish_photo)
             logger.info("[PHOTO-NOTIFY] Photo task returned: %s", finish_photo)
+        except TimeoutError:
+            logger.warning("[PHOTO-NOTIFY] Photo capture timed out after 45s, sending notification without photo")
+        except Exception as e:
+            logger.warning("[PHOTO-NOTIFY] Photo task failed: %s", e)
+        try:
             await _background_notifications(finish_photo)
             await _background_notifications(finish_photo)
         except Exception as e:
         except Exception as e:
-            logger.warning("[PHOTO-NOTIFY] Failed: %s", e)
-            # Still try to send notification without photo
-            await _background_notifications(None)
+            logger.error("[PHOTO-NOTIFY] Notification sending failed: %s", e, exc_info=True)
 
 
     asyncio.create_task(_photo_then_notify())
     asyncio.create_task(_photo_then_notify())
 
 

+ 8 - 0
backend/app/services/bambu_mqtt.py

@@ -2404,6 +2404,14 @@ class BambuMQTTClient:
         ):
         ):
             should_trigger_completion = True
             should_trigger_completion = True
 
 
+        # Log when we see a terminal state but DON'T trigger completion (diagnostics)
+        if not should_trigger_completion and self.state.state in ("FINISH", "FAILED"):
+            logger.info(
+                f"[{self.serial_number}] State is {self.state.state} but completion NOT triggered: "
+                f"prev={self._previous_gcode_state}, was_running={self._was_running}, "
+                f"already_triggered={self._completion_triggered}, has_callback={bool(self.on_print_complete)}"
+            )
+
         if should_trigger_completion:
         if should_trigger_completion:
             if self.state.state == "FINISH":
             if self.state.state == "FINISH":
                 status = "completed"
                 status = "completed"