printer.py 5.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. from datetime import datetime
  2. from sqlalchemy import JSON, Boolean, DateTime, Float, String, false, func
  3. from sqlalchemy.orm import Mapped, mapped_column, relationship
  4. from backend.app.core.database import Base
  5. class Printer(Base):
  6. __tablename__ = "printers"
  7. # SQLite would give a freed id to the next row; see core/database AUTOINCREMENT_TABLES
  8. __table_args__ = {"sqlite_autoincrement": True}
  9. id: Mapped[int] = mapped_column(primary_key=True)
  10. name: Mapped[str] = mapped_column(String(100))
  11. serial_number: Mapped[str] = mapped_column(String(50), unique=True)
  12. ip_address: Mapped[str] = mapped_column(String(253))
  13. access_code: Mapped[str] = mapped_column(String(20))
  14. model: Mapped[str | None] = mapped_column(String(50))
  15. location: Mapped[str | None] = mapped_column(String(100)) # Group/location name
  16. nozzle_count: Mapped[int] = mapped_column(default=1) # 1 or 2, auto-detected from MQTT
  17. is_active: Mapped[bool] = mapped_column(Boolean, default=True)
  18. auto_archive: Mapped[bool] = mapped_column(Boolean, default=True)
  19. print_hours_offset: Mapped[float] = mapped_column(Float, default=0.0) # Baseline hours to add
  20. # What an hour of printing costs in wear on this machine (#694). Empty or 0
  21. # leaves wear cost off for the printer.
  22. wear_cost_per_hour: Mapped[float | None] = mapped_column(Float, nullable=True)
  23. runtime_seconds: Mapped[int] = mapped_column(
  24. default=0
  25. ) # Accumulated active runtime (RUNNING state only — see #1521)
  26. last_runtime_update: Mapped[datetime | None] = mapped_column(
  27. DateTime, nullable=True
  28. ) # Last time runtime was updated
  29. # External camera configuration
  30. external_camera_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
  31. external_camera_type: Mapped[str | None] = mapped_column(String(20), nullable=True) # mjpeg, rtsp, snapshot
  32. external_camera_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
  33. # Optional single-frame snapshot URL — when set, used for snapshot / finish-photo
  34. # / timelapse / plate-detect captures instead of opening the live stream and
  35. # skipping a warm-up frame. Bypasses MJPEG warm-up issues on sources that
  36. # expose a dedicated frame endpoint (e.g. go2rtc's /api/frame.jpeg). #1177.
  37. external_camera_snapshot_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
  38. camera_rotation: Mapped[int] = mapped_column(default=0) # 0, 90, 180, 270 degrees
  39. # Picked for the chamber light while the camera is in use (#1655). Only
  40. # counts when the app setting camera_light_mode is "selected".
  41. camera_light_auto: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())
  42. # Plate detection - check if build plate is empty before starting print
  43. plate_detection_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
  44. # ROI for plate detection (percentages: 0.0-1.0)
  45. plate_detection_roi_x: Mapped[float | None] = mapped_column(Float, nullable=True) # X start %
  46. plate_detection_roi_y: Mapped[float | None] = mapped_column(Float, nullable=True) # Y start %
  47. plate_detection_roi_w: Mapped[float | None] = mapped_column(Float, nullable=True) # Width %
  48. plate_detection_roi_h: Mapped[float | None] = mapped_column(Float, nullable=True) # Height %
  49. # Queue: True after a print finishes/fails, until user acknowledges the plate is cleared.
  50. # Persisted so the gate survives crashes and power cycles (issue #961).
  51. awaiting_plate_clear: Mapped[bool] = mapped_column(Boolean, default=False)
  52. # Optional per-printer WLED preset mapping. WLED owns all visual effects.
  53. wled_config: Mapped[dict | None] = mapped_column(JSON, nullable=True)
  54. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  55. updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
  56. # Relationships
  57. archives: Mapped[list["PrintArchive"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
  58. # Deleted with the printer, as providers: unassigned plugs are everyone's.
  59. smart_plugs: Mapped[list["SmartPlug"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
  60. # Deleted with the printer: setting their printer_id to NULL (the default)
  61. # would make them providers for all printers.
  62. notification_providers: Mapped[list["NotificationProvider"]] = relationship(
  63. back_populates="printer", cascade="all, delete-orphan"
  64. )
  65. maintenance_items: Mapped[list["PrinterMaintenance"]] = relationship(
  66. back_populates="printer", cascade="all, delete-orphan"
  67. )
  68. kprofile_notes: Mapped[list["KProfileNote"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
  69. ams_history: Mapped[list["AMSSensorHistory"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
  70. sensor_history: Mapped[list["PrinterSensorHistory"]] = relationship(
  71. back_populates="printer", cascade="all, delete-orphan"
  72. )
  73. ha_sensors: Mapped[list["PrinterHASensor"]] = relationship(back_populates="printer", cascade="all, delete-orphan")
  74. from backend.app.models.ams_history import AMSSensorHistory # noqa: E402
  75. from backend.app.models.archive import PrintArchive # noqa: E402
  76. from backend.app.models.kprofile_note import KProfileNote # noqa: E402
  77. from backend.app.models.maintenance import PrinterMaintenance # noqa: E402
  78. from backend.app.models.notification import NotificationProvider # noqa: E402
  79. from backend.app.models.printer_ha_sensor import PrinterHASensor # noqa: E402
  80. from backend.app.models.printer_sensor_history import PrinterSensorHistory # noqa: E402
  81. from backend.app.models.smart_plug import SmartPlug # noqa: E402