smart_plug.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from datetime import datetime
  2. from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, Text, func
  3. from sqlalchemy.orm import Mapped, mapped_column, relationship
  4. from backend.app.core.database import Base
  5. class SmartPlug(Base):
  6. """Smart plug for printer power control (Tasmota, Home Assistant, MQTT, or REST)."""
  7. __tablename__ = "smart_plugs"
  8. id: Mapped[int] = mapped_column(primary_key=True)
  9. name: Mapped[str] = mapped_column(String(100))
  10. ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) # IPv4/IPv6 (required for Tasmota)
  11. # Plug type: "tasmota" (default), "homeassistant", "mqtt", or "rest"
  12. plug_type: Mapped[str] = mapped_column(String(20), default="tasmota")
  13. # Home Assistant entity ID (e.g., "switch.printer_plug")
  14. ha_entity_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
  15. # Home Assistant energy sensor entities (optional, for separate energy sensors)
  16. ha_power_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_power
  17. ha_energy_today_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_today
  18. ha_energy_total_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_total
  19. # MQTT plug fields (required when plug_type="mqtt")
  20. # Legacy field - kept for backward compatibility, now use mqtt_power_topic
  21. mqtt_topic: Mapped[str | None] = mapped_column(
  22. String(200), nullable=True
  23. ) # e.g., "zigbee2mqtt/shelly-working-room" (deprecated, use mqtt_power_topic)
  24. # Power monitoring
  25. mqtt_power_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for power data
  26. mqtt_power_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "power_l1" or "data.power"
  27. mqtt_power_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for power
  28. # Energy monitoring
  29. mqtt_energy_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for energy data
  30. mqtt_energy_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "energy_l1"
  31. mqtt_energy_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for energy
  32. # State monitoring
  33. mqtt_state_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for state data
  34. mqtt_state_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "state_l1" for ON/OFF
  35. mqtt_state_on_value: Mapped[str | None] = mapped_column(
  36. String(50), nullable=True
  37. ) # What value means "ON" (e.g., "ON", "true", "1")
  38. # Legacy multiplier - kept for backward compatibility
  39. mqtt_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Deprecated, use mqtt_power_multiplier
  40. # REST/Webhook plug fields (required when plug_type="rest")
  41. # Control URLs
  42. rest_on_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Full URL to turn ON
  43. rest_on_body: Mapped[str | None] = mapped_column(Text, nullable=True) # Request body for ON
  44. rest_off_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Full URL to turn OFF
  45. rest_off_body: Mapped[str | None] = mapped_column(Text, nullable=True) # Request body for OFF
  46. rest_method: Mapped[str | None] = mapped_column(String(10), nullable=True) # HTTP method: POST, PUT, GET
  47. rest_headers: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON string of custom headers
  48. # Status polling (optional)
  49. rest_status_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # GET endpoint for state
  50. rest_status_path: Mapped[str | None] = mapped_column(String(200), nullable=True) # JSON path to state value
  51. rest_status_on_value: Mapped[str | None] = mapped_column(String(50), nullable=True) # What value means ON
  52. # Energy monitoring (optional — can use separate URLs or extract from status response)
  53. rest_power_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Separate URL for power data
  54. rest_power_path: Mapped[str | None] = mapped_column(String(200), nullable=True) # JSON path for power (watts)
  55. rest_power_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for power
  56. rest_energy_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Separate URL for energy data
  57. rest_energy_path: Mapped[str | None] = mapped_column(String(200), nullable=True) # JSON path for energy (kWh)
  58. rest_energy_multiplier: Mapped[float] = mapped_column(
  59. Float, default=1.0
  60. ) # Unit conversion (e.g., 0.001 for Wh→kWh)
  61. # Link to printer (multiple plugs/scripts can be linked to one printer)
  62. printer_id: Mapped[int | None] = mapped_column(ForeignKey("printers.id", ondelete="SET NULL"), nullable=True)
  63. # Automation settings
  64. enabled: Mapped[bool] = mapped_column(Boolean, default=True)
  65. auto_on: Mapped[bool] = mapped_column(Boolean, default=True) # Turn on at print start
  66. auto_off: Mapped[bool] = mapped_column(Boolean, default=True) # Turn off at print complete/fail
  67. auto_off_persistent: Mapped[bool] = mapped_column(Boolean, default=False) # Keep auto-off enabled between prints
  68. # Turn-off delay mode: "time" or "temperature"
  69. off_delay_mode: Mapped[str] = mapped_column(String(20), default="time")
  70. off_delay_minutes: Mapped[int] = mapped_column(Integer, default=5) # For time mode
  71. off_temp_threshold: Mapped[int] = mapped_column(Integer, default=70) # For temp mode (°C)
  72. # Optional auth (some Tasmota configs require it)
  73. username: Mapped[str | None] = mapped_column(String(50), nullable=True)
  74. password: Mapped[str | None] = mapped_column(String(100), nullable=True)
  75. # Power alerts
  76. power_alert_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
  77. power_alert_high: Mapped[float | None] = mapped_column(Float, nullable=True) # Alert when power > this (watts)
  78. power_alert_low: Mapped[float | None] = mapped_column(Float, nullable=True) # Alert when power < this (watts)
  79. power_alert_last_triggered: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # Cooldown tracking
  80. # Schedule (time-based on/off)
  81. schedule_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
  82. schedule_on_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format
  83. schedule_off_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format
  84. # Switchbar visibility
  85. show_in_switchbar: Mapped[bool] = mapped_column(Boolean, default=False)
  86. # Printer card visibility (for scripts)
  87. show_on_printer_card: Mapped[bool] = mapped_column(Boolean, default=True)
  88. # Status tracking
  89. last_state: Mapped[str | None] = mapped_column(String(10), nullable=True) # "ON"/"OFF"
  90. last_checked: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  91. auto_off_executed: Mapped[bool] = mapped_column(Boolean, default=False) # True when auto-off was triggered
  92. auto_off_pending: Mapped[bool] = mapped_column(Boolean, default=False) # True when waiting for cooldown
  93. auto_off_pending_since: Mapped[datetime | None] = mapped_column(
  94. DateTime, nullable=True
  95. ) # When auto-off was scheduled
  96. # Timestamps
  97. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  98. updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
  99. # Relationship
  100. printer: Mapped["Printer"] = relationship(back_populates="smart_plugs")
  101. from backend.app.models.printer import Printer # noqa: E402