mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-08-11 00:30:12 -04:00
Binds binary_sensor and reading-carrying sensor entities to a printer and renders their state on its card, worded by Home Assistant's device_class. Optional per-sensor alert condition drives a notification on the transition into the alert state and an opt-in interlock that holds queued prints while alerting -- a hold with a readable waiting_reason, never a failure, and only ever on a sensor that was read successfully. Sensors get their own table rather than a wider entity pattern on SmartPlug: get_smart_plug_by_printer would otherwise hand the card's power button a door contact to switch. The hold is passed to the model matcher directly rather than merged into busy_printers: _check_auto_drying reads that set as "is currently printing" and would put an idle-but-held printer down the mid-print drying path. The notification_providers migration spells its default FALSE, not 0 -- Postgres rejects an integer default for a boolean and _safe_execute swallows the error.
72 lines
3.6 KiB
Python
72 lines
3.6 KiB
Python
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from backend.app.core.database import Base
|
|
|
|
|
|
class PrinterHASensor(Base):
|
|
"""A read-only Home Assistant entity bound to a printer (#1148, #448).
|
|
|
|
Deliberately *not* a ``SmartPlug`` row with a wider entity pattern. A plug
|
|
carries auto-on/auto-off, schedules, power alerts, energy snapshots and
|
|
``controls_printer_power``; none of that means anything for a door contact,
|
|
and ``get_smart_plug_by_printer`` would hand the card's power button a
|
|
sensor to switch. Sensors get their own table and their own read-only
|
|
routes instead.
|
|
|
|
Not to be confused with ``PrinterSensorHistory``, which stores the
|
|
printer's *own* heater readings.
|
|
"""
|
|
|
|
__tablename__ = "printer_ha_sensors"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
printer_id: Mapped[int] = mapped_column(ForeignKey("printers.id", ondelete="CASCADE"), index=True)
|
|
|
|
name: Mapped[str] = mapped_column(String(100))
|
|
entity_id: Mapped[str] = mapped_column(String(255))
|
|
|
|
# "binary" for binary_sensor.*, "numeric" for sensor.*. Decides how the
|
|
# state is rendered and which alert fields apply.
|
|
kind: Mapped[str] = mapped_column(String(16), default="binary")
|
|
|
|
# HA's own device_class, snapshotted when the entity is bound. Drives the
|
|
# on/off wording (door -> Open/Closed, motion -> Detected/Clear) and the
|
|
# icon, so the card doesn't have to say "On" for an open door.
|
|
device_class: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
# Numeric only: "°C", "%", "ppm", ... shown next to the value.
|
|
unit: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
|
|
# What counts as needing attention. One notion, three consumers: the pill
|
|
# colour on the card, the notification, and the print interlock.
|
|
# Binary sensors use alert_state ("on"/"off"/None), numeric ones the
|
|
# thresholds. All None means "just show the value".
|
|
alert_state: Mapped[str | None] = mapped_column(String(8), nullable=True)
|
|
alert_above: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
alert_below: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
|
|
# Hold queued prints for this printer while the sensor is in its alert
|
|
# state — the enclosure-door case this feature was asked for. Opt-in, and
|
|
# only ever a *hold*: the item stays pending with a waiting_reason and
|
|
# dispatches by itself once the door closes.
|
|
block_print: Mapped[bool] = mapped_column(Boolean, default=False, server_default="0")
|
|
notify_on_alert: Mapped[bool] = mapped_column(Boolean, default=False, server_default="0")
|
|
|
|
show_on_printer_card: Mapped[bool] = mapped_column(Boolean, default=True, server_default="1")
|
|
sort_order: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
|
|
# Last poll result. Persisted so a restart doesn't blank the card until the
|
|
# first poll lands, and so notifications only fire on a real transition.
|
|
last_state: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
last_changed: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
last_checked: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
|
|
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())
|
|
|
|
printer: Mapped["Printer"] = relationship(back_populates="ha_sensors")
|
|
|
|
|
|
from backend.app.models.printer import Printer # noqa: E402
|