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

Fix X1E model-based queue

Backend - X1E model-based queue support:
- Fix 'PrinterState' object has no attribute 'ams_units' error
  by accessing AMS data via status.raw_data.get("ams", [])
- Add model name normalization in print_queue.py to convert
  "Bambu Lab X1E" or "C13" to canonical "X1E" format
- Add case-insensitive model matching in print_scheduler.py
  using func.lower() for database queries

Fixes #162
maziggy 3 месяцев назад
Родитель
Сommit
74379da28e
2 измененных файлов с 34 добавлено и 11 удалено
  1. 26 8
      backend/app/api/routes/print_queue.py
  2. 8 3
      backend/app/services/print_scheduler.py

+ 26 - 8
backend/app/api/routes/print_queue.py

@@ -27,6 +27,7 @@ from backend.app.schemas.print_queue import (
     PrintQueueReorder,
     PrintQueueReorder,
 )
 )
 from backend.app.services.notification_service import notification_service
 from backend.app.services.notification_service import notification_service
+from backend.app.utils.printer_models import normalize_printer_model, normalize_printer_model_id
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -197,12 +198,21 @@ async def add_to_queue(
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
 ):
 ):
     """Add an item to the print queue."""
     """Add an item to the print queue."""
+    # Normalize target_model (e.g., "Bambu Lab X1E" / "C13" -> "X1E")
+    target_model_norm = None
+    if data.target_model:
+        target_model_norm = (
+            normalize_printer_model(data.target_model)
+            or normalize_printer_model_id(data.target_model)
+            or data.target_model
+        )
+
     # Validate that either archive_id or library_file_id is provided
     # Validate that either archive_id or library_file_id is provided
     if not data.archive_id and not data.library_file_id:
     if not data.archive_id and not data.library_file_id:
         raise HTTPException(400, "Either archive_id or library_file_id must be provided")
         raise HTTPException(400, "Either archive_id or library_file_id must be provided")
 
 
     # Cannot specify both printer_id and target_model
     # Cannot specify both printer_id and target_model
-    if data.printer_id and data.target_model:
+    if data.printer_id and target_model_norm:
         raise HTTPException(400, "Cannot specify both printer_id and target_model")
         raise HTTPException(400, "Cannot specify both printer_id and target_model")
 
 
     # Validate printer exists (if assigned)
     # Validate printer exists (if assigned)
@@ -212,12 +222,12 @@ async def add_to_queue(
             raise HTTPException(400, "Printer not found")
             raise HTTPException(400, "Printer not found")
 
 
     # Validate target_model has active printers
     # Validate target_model has active printers
-    if data.target_model:
+    if target_model_norm:
         result = await db.execute(
         result = await db.execute(
-            select(Printer).where(Printer.model == data.target_model).where(Printer.is_active == True)  # noqa: E712
+            select(Printer).where(Printer.model == target_model_norm).where(Printer.is_active == True)  # noqa: E712
         )
         )
         if not result.scalars().first():
         if not result.scalars().first():
-            raise HTTPException(400, f"No active printers for model: {data.target_model}")
+            raise HTTPException(400, f"No active printers for model: {target_model_norm}")
 
 
     # Validate archive exists (if provided) and get it for filament extraction
     # Validate archive exists (if provided) and get it for filament extraction
     archive = None
     archive = None
@@ -237,7 +247,7 @@ async def add_to_queue(
 
 
     # Extract filament types for model-based assignment (used by scheduler for validation)
     # Extract filament types for model-based assignment (used by scheduler for validation)
     required_filament_types = None
     required_filament_types = None
-    if data.target_model:
+    if target_model_norm:
         # Get file path from archive or library file
         # Get file path from archive or library file
         file_path = None
         file_path = None
         if archive:
         if archive:
@@ -270,7 +280,7 @@ async def add_to_queue(
 
 
     item = PrintQueueItem(
     item = PrintQueueItem(
         printer_id=data.printer_id,
         printer_id=data.printer_id,
-        target_model=data.target_model,
+        target_model=target_model_norm,
         required_filament_types=required_filament_types,
         required_filament_types=required_filament_types,
         archive_id=data.archive_id,
         archive_id=data.archive_id,
         library_file_id=data.library_file_id,
         library_file_id=data.library_file_id,
@@ -297,7 +307,7 @@ async def add_to_queue(
     await db.refresh(item, ["archive", "printer", "library_file"])
     await db.refresh(item, ["archive", "printer", "library_file"])
 
 
     source_name = f"archive {data.archive_id}" if data.archive_id else f"library file {data.library_file_id}"
     source_name = f"archive {data.archive_id}" if data.archive_id else f"library file {data.library_file_id}"
-    target_desc = data.printer_id or (f"model {data.target_model}" if data.target_model else "unassigned")
+    target_desc = data.printer_id or (f"model {target_model_norm}" if target_model_norm else "unassigned")
     logger.info(f"Added {source_name} to queue for {target_desc}")
     logger.info(f"Added {source_name} to queue for {target_desc}")
 
 
     # MQTT relay - publish queue job added
     # MQTT relay - publish queue job added
@@ -324,7 +334,7 @@ async def add_to_queue(
         )
         )
         job_name = job_name.replace(".gcode.3mf", "").replace(".3mf", "")
         job_name = job_name.replace(".gcode.3mf", "").replace(".3mf", "")
         target = (
         target = (
-            item.printer.name if item.printer else (f"Any {item.target_model}" if data.target_model else "Unassigned")
+            item.printer.name if item.printer else (f"Any {item.target_model}" if target_model_norm else "Unassigned")
         )
         )
         await notification_service.on_queue_job_added(
         await notification_service.on_queue_job_added(
             job_name=job_name,
             job_name=job_name,
@@ -423,6 +433,14 @@ async def update_queue_item(
 
 
     update_data = data.model_dump(exclude_unset=True)
     update_data = data.model_dump(exclude_unset=True)
 
 
+    # Normalize target_model if being updated
+    if "target_model" in update_data and update_data["target_model"]:
+        update_data["target_model"] = (
+            normalize_printer_model(update_data["target_model"])
+            or normalize_printer_model_id(update_data["target_model"])
+            or update_data["target_model"]
+        )
+
     # Cannot specify both printer_id and target_model
     # Cannot specify both printer_id and target_model
     new_printer_id = update_data.get("printer_id", item.printer_id)
     new_printer_id = update_data.get("printer_id", item.printer_id)
     new_target_model = update_data.get("target_model", item.target_model)
     new_target_model = update_data.get("target_model", item.target_model)

+ 8 - 3
backend/app/services/print_scheduler.py

@@ -4,7 +4,7 @@ import asyncio
 import logging
 import logging
 from datetime import datetime
 from datetime import datetime
 
 
-from sqlalchemy import select
+from sqlalchemy import func, select
 from sqlalchemy.ext.asyncio import AsyncSession
 from sqlalchemy.ext.asyncio import AsyncSession
 
 
 from backend.app.core.config import settings
 from backend.app.core.config import settings
@@ -18,6 +18,7 @@ from backend.app.services.bambu_ftp import delete_file_async, get_ftp_retry_sett
 from backend.app.services.notification_service import notification_service
 from backend.app.services.notification_service import notification_service
 from backend.app.services.printer_manager import printer_manager
 from backend.app.services.printer_manager import printer_manager
 from backend.app.services.tasmota import tasmota_service
 from backend.app.services.tasmota import tasmota_service
+from backend.app.utils.printer_models import normalize_printer_model
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -226,13 +227,17 @@ class PrintScheduler:
             - (printer_id, None) if a matching printer was found
             - (printer_id, None) if a matching printer was found
             - (None, reason) if no printer is available, with explanation
             - (None, reason) if no printer is available, with explanation
         """
         """
+        # Normalize model name and use case-insensitive matching
+        normalized_model = normalize_printer_model(model) or model
         result = await db.execute(
         result = await db.execute(
-            select(Printer).where(Printer.model == model).where(Printer.is_active == True)  # noqa: E712
+            select(Printer)
+            .where(func.lower(Printer.model) == normalized_model.lower())
+            .where(Printer.is_active == True)  # noqa: E712
         )
         )
         printers = list(result.scalars().all())
         printers = list(result.scalars().all())
 
 
         if not printers:
         if not printers:
-            return None, f"No active {model} printers configured"
+            return None, f"No active {normalized_model} printers configured"
 
 
         # Track reasons for skipping printers
         # Track reasons for skipping printers
         printers_busy = []
         printers_busy = []