mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-04 13:11:35 +02:00
Users with local DNS can now add printers using hostnames like printer.local or my-printer.home.lan. Updated backend schema validation, database column width, frontend form patterns/placeholders, and i18n labels across all locales. Added integration tests for hostname and FQDN creation plus invalid hostname rejection.
232 lines
9.1 KiB
Python
232 lines
9.1 KiB
Python
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class PrinterBase(BaseModel):
|
|
name: str = Field(..., min_length=1, max_length=100)
|
|
serial_number: str = Field(..., min_length=1, max_length=50)
|
|
ip_address: str = Field(
|
|
...,
|
|
max_length=253,
|
|
pattern=r"^(\d{1,3}(\.\d{1,3}){3}|[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*)$",
|
|
)
|
|
access_code: str = Field(..., min_length=1, max_length=20)
|
|
model: str | None = None
|
|
location: str | None = None # Group/location name
|
|
auto_archive: bool = True
|
|
external_camera_url: str | None = None
|
|
external_camera_type: str | None = None # "mjpeg", "rtsp", "snapshot", "usb"
|
|
external_camera_enabled: bool = False
|
|
|
|
|
|
class PrinterCreate(PrinterBase):
|
|
pass
|
|
|
|
|
|
class PlateDetectionROI(BaseModel):
|
|
"""Region of interest for plate detection (percentages 0.0-1.0)."""
|
|
|
|
x: float = Field(..., ge=0.0, le=1.0) # X start %
|
|
y: float = Field(..., ge=0.0, le=1.0) # Y start %
|
|
w: float = Field(..., ge=0.0, le=1.0) # Width %
|
|
h: float = Field(..., ge=0.0, le=1.0) # Height %
|
|
|
|
|
|
class PrinterUpdate(BaseModel):
|
|
name: str | None = None
|
|
ip_address: str | None = Field(
|
|
default=None,
|
|
max_length=253,
|
|
pattern=r"^(\d{1,3}(\.\d{1,3}){3}|[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*)$",
|
|
)
|
|
access_code: str | None = None
|
|
model: str | None = None
|
|
location: str | None = None
|
|
is_active: bool | None = None
|
|
auto_archive: bool | None = None
|
|
print_hours_offset: float | None = None
|
|
external_camera_url: str | None = None
|
|
external_camera_type: str | None = None
|
|
external_camera_enabled: bool | None = None
|
|
plate_detection_enabled: bool | None = None
|
|
plate_detection_roi: PlateDetectionROI | None = None
|
|
|
|
|
|
class PrinterResponse(PrinterBase):
|
|
id: int
|
|
is_active: bool
|
|
nozzle_count: int = 1 # 1 or 2, auto-detected from MQTT
|
|
print_hours_offset: float = 0.0
|
|
external_camera_url: str | None = None
|
|
external_camera_type: str | None = None
|
|
external_camera_enabled: bool = False
|
|
plate_detection_enabled: bool = False
|
|
plate_detection_roi: PlateDetectionROI | None = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
@classmethod
|
|
def from_orm_with_roi(cls, printer) -> "PrinterResponse":
|
|
"""Create response from ORM model, converting ROI fields to nested object."""
|
|
data = {
|
|
"id": printer.id,
|
|
"name": printer.name,
|
|
"serial_number": printer.serial_number,
|
|
"ip_address": printer.ip_address,
|
|
"access_code": printer.access_code,
|
|
"model": printer.model,
|
|
"location": printer.location,
|
|
"auto_archive": printer.auto_archive,
|
|
"external_camera_url": printer.external_camera_url,
|
|
"external_camera_type": printer.external_camera_type,
|
|
"external_camera_enabled": printer.external_camera_enabled,
|
|
"is_active": printer.is_active,
|
|
"nozzle_count": printer.nozzle_count,
|
|
"print_hours_offset": printer.print_hours_offset,
|
|
"plate_detection_enabled": printer.plate_detection_enabled,
|
|
"created_at": printer.created_at,
|
|
"updated_at": printer.updated_at,
|
|
}
|
|
# Build ROI object if any ROI field is set
|
|
if any(
|
|
[
|
|
printer.plate_detection_roi_x is not None,
|
|
printer.plate_detection_roi_y is not None,
|
|
printer.plate_detection_roi_w is not None,
|
|
printer.plate_detection_roi_h is not None,
|
|
]
|
|
):
|
|
data["plate_detection_roi"] = PlateDetectionROI(
|
|
x=printer.plate_detection_roi_x or 0.15,
|
|
y=printer.plate_detection_roi_y or 0.35,
|
|
w=printer.plate_detection_roi_w or 0.70,
|
|
h=printer.plate_detection_roi_h or 0.55,
|
|
)
|
|
return cls(**data)
|
|
|
|
|
|
class HMSErrorResponse(BaseModel):
|
|
code: str
|
|
attr: int = 0 # Attribute value for constructing wiki URL
|
|
module: int
|
|
severity: int # 1=fatal, 2=serious, 3=common, 4=info
|
|
|
|
|
|
class AMSTray(BaseModel):
|
|
id: int
|
|
tray_color: str | None = None
|
|
tray_type: str | None = None
|
|
tray_sub_brands: str | None = None # Full name like "PLA Basic", "PETG HF"
|
|
tray_id_name: str | None = None # Bambu filament ID like "A00-Y2" (can decode to color)
|
|
tray_info_idx: str | None = None # Filament preset ID like "GFA00"
|
|
remain: int = 0
|
|
k: float | None = None # Pressure advance value (from tray or K-profile lookup)
|
|
cali_idx: int | None = None # Calibration index for K-profile lookup
|
|
tag_uid: str | None = None # RFID tag UID (any tag)
|
|
tray_uuid: str | None = None # Bambu Lab spool UUID (32-char hex)
|
|
nozzle_temp_min: int | None = None # Min nozzle temperature
|
|
nozzle_temp_max: int | None = None # Max nozzle temperature
|
|
|
|
|
|
class AMSUnit(BaseModel):
|
|
id: int
|
|
humidity: int | None = None
|
|
temp: float | None = None
|
|
is_ams_ht: bool = False # True for AMS-HT (single spool), False for regular AMS (4 spools)
|
|
tray: list[AMSTray] = []
|
|
|
|
|
|
class NozzleInfoResponse(BaseModel):
|
|
nozzle_type: str = "" # "stainless_steel" or "hardened_steel"
|
|
nozzle_diameter: str = "" # e.g., "0.4"
|
|
|
|
|
|
class PrintOptionsResponse(BaseModel):
|
|
"""AI detection and print options from xcam data."""
|
|
|
|
# Core AI detectors
|
|
spaghetti_detector: bool = False
|
|
print_halt: bool = False
|
|
halt_print_sensitivity: str = "medium" # Spaghetti sensitivity
|
|
first_layer_inspector: bool = False
|
|
printing_monitor: bool = False
|
|
buildplate_marker_detector: bool = False
|
|
allow_skip_parts: bool = False
|
|
# Additional AI detectors (decoded from cfg bitmask)
|
|
nozzle_clumping_detector: bool = True
|
|
nozzle_clumping_sensitivity: str = "medium"
|
|
pileup_detector: bool = True
|
|
pileup_sensitivity: str = "medium"
|
|
airprint_detector: bool = True
|
|
airprint_sensitivity: str = "medium"
|
|
auto_recovery_step_loss: bool = True
|
|
filament_tangle_detect: bool = False
|
|
|
|
|
|
class PrinterStatus(BaseModel):
|
|
id: int
|
|
name: str
|
|
connected: bool
|
|
state: str | None = None
|
|
current_print: str | None = None
|
|
subtask_name: str | None = None
|
|
gcode_file: str | None = None
|
|
progress: float | None = None
|
|
remaining_time: int | None = None
|
|
layer_num: int | None = None
|
|
total_layers: int | None = None
|
|
temperatures: dict | None = None
|
|
cover_url: str | None = None
|
|
hms_errors: list[HMSErrorResponse] = []
|
|
ams: list[AMSUnit] = []
|
|
ams_exists: bool = False
|
|
vt_tray: AMSTray | None = None # Virtual tray / external spool
|
|
sdcard: bool = False # SD card inserted
|
|
store_to_sdcard: bool = False # Store sent files on SD card
|
|
timelapse: bool = False # Timelapse recording active
|
|
ipcam: bool = False # Live view enabled
|
|
wifi_signal: int | None = None # WiFi signal strength in dBm
|
|
nozzles: list[NozzleInfoResponse] = [] # Nozzle hardware info (index 0=left/primary, 1=right)
|
|
print_options: PrintOptionsResponse | None = None # AI detection and print options
|
|
# Calibration stage tracking
|
|
stg_cur: int = -1 # Current stage number (-1 = not calibrating)
|
|
stg_cur_name: str | None = None # Human-readable current stage name
|
|
stg: list[int] = [] # List of stage numbers in calibration sequence
|
|
# Air conditioning mode (0=cooling, 1=heating)
|
|
airduct_mode: int = 0
|
|
# Print speed level (1=silent, 2=standard, 3=sport, 4=ludicrous)
|
|
speed_level: int = 2
|
|
# Chamber light on/off
|
|
chamber_light: bool = False
|
|
# Active extruder for dual nozzle (0=right, 1=left)
|
|
active_extruder: int = 0
|
|
# AMS mapping for dual nozzle: which AMS is connected to which nozzle
|
|
ams_mapping: list[int] = []
|
|
# Per-AMS extruder map: {ams_id: extruder_id} where 0=right, 1=left
|
|
ams_extruder_map: dict[str, int] = {}
|
|
# Currently loaded tray (global ID): 254 = external spool, 255 = no filament
|
|
tray_now: int = 255
|
|
# AMS status for filament change tracking
|
|
# Main status: 0=idle, 1=filament_change, 2=rfid_identifying, 3=assist, 4=calibration
|
|
ams_status_main: int = 0
|
|
# Sub status: specific step within filament change (when main=1)
|
|
# Known values: 4=retraction, 6=load verification, 7=purge
|
|
ams_status_sub: int = 0
|
|
# mc_print_sub_stage - filament change step indicator used by OrcaSlicer/BambuStudio
|
|
mc_print_sub_stage: int = 0
|
|
# Timestamp of last AMS data update (for RFID refresh detection)
|
|
last_ams_update: float = 0.0
|
|
# Number of printable objects in current print (for skip objects feature)
|
|
printable_objects_count: int = 0
|
|
# Fan speeds (0-100 percentage, None if not available for this model)
|
|
cooling_fan_speed: int | None = None # Part cooling fan
|
|
big_fan1_speed: int | None = None # Auxiliary fan
|
|
big_fan2_speed: int | None = None # Chamber/exhaust fan
|
|
heatbreak_fan_speed: int | None = None # Hotend heatbreak fan
|
|
# Firmware version (from info.module[name="ota"].sw_ver)
|
|
firmware_version: str | None = None
|