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
This commit is contained in:
maziggy
2026-01-30 12:05:18 +01:00
parent 836335be7d
commit 74379da28e
2 changed files with 34 additions and 11 deletions
+26 -8
View File
@@ -27,6 +27,7 @@ from backend.app.schemas.print_queue import (
PrintQueueReorder,
)
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__)
@@ -197,12 +198,21 @@ async def add_to_queue(
db: AsyncSession = Depends(get_db),
):
"""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
if not data.archive_id and not data.library_file_id:
raise HTTPException(400, "Either archive_id or library_file_id must be provided")
# 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")
# Validate printer exists (if assigned)
@@ -212,12 +222,12 @@ async def add_to_queue(
raise HTTPException(400, "Printer not found")
# Validate target_model has active printers
if data.target_model:
if target_model_norm:
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():
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
archive = None
@@ -237,7 +247,7 @@ async def add_to_queue(
# Extract filament types for model-based assignment (used by scheduler for validation)
required_filament_types = None
if data.target_model:
if target_model_norm:
# Get file path from archive or library file
file_path = None
if archive:
@@ -270,7 +280,7 @@ async def add_to_queue(
item = PrintQueueItem(
printer_id=data.printer_id,
target_model=data.target_model,
target_model=target_model_norm,
required_filament_types=required_filament_types,
archive_id=data.archive_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"])
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}")
# MQTT relay - publish queue job added
@@ -324,7 +334,7 @@ async def add_to_queue(
)
job_name = job_name.replace(".gcode.3mf", "").replace(".3mf", "")
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(
job_name=job_name,
@@ -423,6 +433,14 @@ async def update_queue_item(
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
new_printer_id = update_data.get("printer_id", item.printer_id)
new_target_model = update_data.get("target_model", item.target_model)
+8 -3
View File
@@ -4,7 +4,7 @@ import asyncio
import logging
from datetime import datetime
from sqlalchemy import select
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
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.printer_manager import printer_manager
from backend.app.services.tasmota import tasmota_service
from backend.app.utils.printer_models import normalize_printer_model
logger = logging.getLogger(__name__)
@@ -226,13 +227,17 @@ class PrintScheduler:
- (printer_id, None) if a matching printer was found
- (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(
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())
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
printers_busy = []