Browse Source

Fix external spool assignments lost on restart (#493)

External spool assignments (ams_id=255) were silently deleted on every
AMS change because _find_tray_in_ams_data only searches AMS units, not
vt_tray. Now looks up external assignments in the printer's vt_tray data
instead, and preserves assignments when vt_tray hasn't arrived yet.
maziggy 3 months ago
parent
commit
32b50e02d8
2 changed files with 16 additions and 1 deletions
  1. 1 0
      CHANGELOG.md
  2. 15 1
      backend/app/main.py

+ 1 - 0
CHANGELOG.md

@@ -8,6 +8,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **"Unknown stage (74)" on H2D During Print Preparation** — The H2D firmware reports `stg_cur=74` during print preparation, but this stage was not in the stage name lookup table (which went up to 66, sourced from BambuStudio). Now maps stage 74 to "Preparing". Also added stage 77 ("Preparing AMS") which was present in BambuStudio but missing from the lookup.
 - **Wrong Documentation Link for "Lubricate Carbon Rods" on P2S** ([#490](https://github.com/maziggy/bambuddy/issues/490)) — The "Lubricate Carbon Rods" maintenance task linked to the belt tension wiki page instead of the XYZ axis lubrication page for P2S printers.
 - **External Spool Mapping Inverted on H2C** ([#492](https://github.com/maziggy/bambuddy/issues/492)) — On H2C dual-nozzle printers, printing from the right nozzle's external spool (Ext-R) incorrectly highlighted the left external spool (Ext-L) as active. The H2C firmware reports `tray_now=254` generically for both external spools, so the frontend's direct ID comparison (`effectiveTrayNow === extTrayId`) always matched Ext-L (id=254). Now uses `active_extruder` on dual-nozzle printers to determine which external spool is active: extruder 1 (left) → Ext-L, extruder 0 (right) → Ext-R.
+- **External Spool Assignments Lost on Restart** ([#493](https://github.com/maziggy/bambuddy/issues/493)) — Filament spool assignments on external spool holders (Ext-L / Ext-R) were silently deleted every time AMS data changed, including on container restart. The `on_ams_change` stale-assignment cleanup searched only AMS unit data for matching trays, but external spools live in `vt_tray` (a separate MQTT field). Since `_find_tray_in_ams_data` never found them, external assignments were always marked as stale and removed. Now looks up external spool assignments (`ams_id=255`) in the printer's `vt_tray` data instead, and keeps the assignment if `vt_tray` data hasn't arrived yet.
 - **Developer Mode Detection Always Reports Null** — The MQTT `fun` field is an integer in the JSON payload, but the parser used `int(value, 16)` which requires a string argument. This raised `TypeError` on every message, silently caught by the exception handler, so `developer_mode` was never set. Now handles both integer and hex string formats.
 - **File Manager Rename Doesn't Update Displayed Name** ([#460](https://github.com/maziggy/bambuddy/issues/460)) — Renaming a file in the File Manager updated the `filename` field but not `file_metadata.print_name`, which the UI uses as the primary display name. Since `print_name` is extracted from inside the 3MF at upload time, it always took precedence over the renamed `filename`. The rename endpoint now also updates `print_name` in the file metadata when present.
 - **Finish Photo Not Captured When Archive Has No Source 3MF** ([#484](https://github.com/maziggy/bambuddy/issues/484)) — When a print completed but the 3MF source file wasn't downloaded from the printer (e.g. FTP download failure), the archive's `file_path` was null. The finish photo capture silently skipped because it derived the save directory from `file_path`. Now falls back to `archive/{id}/` so the photo is captured regardless.

+ 15 - 1
backend/app/main.py

@@ -587,7 +587,21 @@ async def on_ams_change(printer_id: int, ams_data: list):
             result = await db.execute(select(SA).where(SA.printer_id == printer_id).options(selectinload(SA.spool)))
             stale = []
             for assignment in result.scalars().all():
-                current_tray = _find_tray_in_ams_data(ams_data, assignment.ams_id, assignment.tray_id)
+                # External spool assignments (ams_id=255) live in vt_tray, not AMS data
+                if assignment.ams_id == 255:
+                    ps = printer_manager.get_status(printer_id)
+                    vt_tray_raw = ps.raw_data.get("vt_tray", []) if ps else []
+                    ext_id = assignment.tray_id + 254  # 0→254, 1→255
+                    current_tray = None
+                    for vt in vt_tray_raw:
+                        if isinstance(vt, dict) and int(vt.get("id", 254)) == ext_id:
+                            current_tray = vt
+                            break
+                    if not current_tray:
+                        # vt_tray data may not have arrived yet — keep assignment
+                        continue
+                else:
+                    current_tray = _find_tray_in_ams_data(ams_data, assignment.ams_id, assignment.tray_id)
                 if not current_tray:
                     logger.info(
                         "Auto-unlink: spool %d AMS%d-T%d — tray not found in AMS data (slot empty?)",