Files
bambuddy/backend/app/models/printer.py
T
maziggy 6954cff3ee Turn on the chamber light while the camera is in use
A printer kept dark between uses showed a black live view and sent black
notification photos (#1655). Settings → Camera can turn the light on for
all printers or for selected ones, for the live view, notification
photos, the finish photo and the snapshot API that Home Assistant uses,
and turns it off 15 seconds after the last use. It only turns off a
light it turned on, and switching the light by hand hands it back.
Snapshots wait an adjustable delay after the light comes on. Timelapse,
the Obico check and the in-print frame bank don't switch it, since they
would make it flash.

The camera card no longer lists every printer: external cameras are
added from a searchable printer picker and only those printers are
listed, so large farms get a short page.
2026-10-04 11:26:07 +02:00

78 lines
4.8 KiB
Python

from datetime import datetime
from sqlalchemy import Boolean, DateTime, Float, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.core.database import Base
class Printer(Base):
__tablename__ = "printers"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
serial_number: Mapped[str] = mapped_column(String(50), unique=True)
ip_address: Mapped[str] = mapped_column(String(253))
access_code: Mapped[str] = mapped_column(String(20))
model: Mapped[str | None] = mapped_column(String(50))
location: Mapped[str | None] = mapped_column(String(100)) # Group/location name
nozzle_count: Mapped[int] = mapped_column(default=1) # 1 or 2, auto-detected from MQTT
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
auto_archive: Mapped[bool] = mapped_column(Boolean, default=True)
print_hours_offset: Mapped[float] = mapped_column(Float, default=0.0) # Baseline hours to add
runtime_seconds: Mapped[int] = mapped_column(
default=0
) # Accumulated active runtime (RUNNING state only — see #1521)
last_runtime_update: Mapped[datetime | None] = mapped_column(
DateTime, nullable=True
) # Last time runtime was updated
# External camera configuration
external_camera_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
external_camera_type: Mapped[str | None] = mapped_column(String(20), nullable=True) # mjpeg, rtsp, snapshot
external_camera_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
# Optional single-frame snapshot URL — when set, used for snapshot / finish-photo
# / timelapse / plate-detect captures instead of opening the live stream and
# skipping a warm-up frame. Bypasses MJPEG warm-up issues on sources that
# expose a dedicated frame endpoint (e.g. go2rtc's /api/frame.jpeg). #1177.
external_camera_snapshot_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
camera_rotation: Mapped[int] = mapped_column(default=0) # 0, 90, 180, 270 degrees
# Picked for the chamber light while the camera is in use (#1655). Only
# counts when the app setting camera_light_mode is "selected".
camera_light_auto: Mapped[bool] = mapped_column(Boolean, default=False)
# Plate detection - check if build plate is empty before starting print
plate_detection_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
# ROI for plate detection (percentages: 0.0-1.0)
plate_detection_roi_x: Mapped[float | None] = mapped_column(Float, nullable=True) # X start %
plate_detection_roi_y: Mapped[float | None] = mapped_column(Float, nullable=True) # Y start %
plate_detection_roi_w: Mapped[float | None] = mapped_column(Float, nullable=True) # Width %
plate_detection_roi_h: Mapped[float | None] = mapped_column(Float, nullable=True) # Height %
# Queue: True after a print finishes/fails, until user acknowledges the plate is cleared.
# Persisted so the gate survives crashes and power cycles (issue #961).
awaiting_plate_clear: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
# Relationships
archives: Mapped[list["PrintArchive"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
smart_plugs: Mapped[list["SmartPlug"]] = relationship(back_populates="printer")
notification_providers: Mapped[list["NotificationProvider"]] = relationship(back_populates="printer")
maintenance_items: Mapped[list["PrinterMaintenance"]] = relationship(
back_populates="printer", cascade="all, delete-orphan"
)
kprofile_notes: Mapped[list["KProfileNote"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
ams_history: Mapped[list["AMSSensorHistory"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
sensor_history: Mapped[list["PrinterSensorHistory"]] = relationship(
back_populates="printer", cascade="all, delete-orphan"
)
ha_sensors: Mapped[list["PrinterHASensor"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
from backend.app.models.ams_history import AMSSensorHistory # noqa: E402
from backend.app.models.archive import PrintArchive # noqa: E402
from backend.app.models.kprofile_note import KProfileNote # noqa: E402
from backend.app.models.maintenance import PrinterMaintenance # noqa: E402
from backend.app.models.notification import NotificationProvider # noqa: E402
from backend.app.models.printer_ha_sensor import PrinterHASensor # noqa: E402
from backend.app.models.printer_sensor_history import PrinterSensorHistory # noqa: E402
from backend.app.models.smart_plug import SmartPlug # noqa: E402