mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-08 07:01:40 +02:00
REST/Webhook smart plugs can now fetch power and energy data from individual URLs instead of requiring all values in a single status response. Each value falls back to the shared Status URL when no separate URL is set, preserving backward compatibility. Added power and energy multipliers for unit conversion (e.g. 0.001 for Wh→kWh).
128 lines
7.6 KiB
Python
128 lines
7.6 KiB
Python
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, Text, func
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from backend.app.core.database import Base
|
|
|
|
|
|
class SmartPlug(Base):
|
|
"""Smart plug for printer power control (Tasmota, Home Assistant, MQTT, or REST)."""
|
|
|
|
__tablename__ = "smart_plugs"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
name: Mapped[str] = mapped_column(String(100))
|
|
ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) # IPv4/IPv6 (required for Tasmota)
|
|
|
|
# Plug type: "tasmota" (default), "homeassistant", "mqtt", or "rest"
|
|
plug_type: Mapped[str] = mapped_column(String(20), default="tasmota")
|
|
# Home Assistant entity ID (e.g., "switch.printer_plug")
|
|
ha_entity_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
# Home Assistant energy sensor entities (optional, for separate energy sensors)
|
|
ha_power_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_power
|
|
ha_energy_today_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_today
|
|
ha_energy_total_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_total
|
|
|
|
# MQTT plug fields (required when plug_type="mqtt")
|
|
# Legacy field - kept for backward compatibility, now use mqtt_power_topic
|
|
mqtt_topic: Mapped[str | None] = mapped_column(
|
|
String(200), nullable=True
|
|
) # e.g., "zigbee2mqtt/shelly-working-room" (deprecated, use mqtt_power_topic)
|
|
|
|
# Power monitoring
|
|
mqtt_power_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for power data
|
|
mqtt_power_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "power_l1" or "data.power"
|
|
mqtt_power_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for power
|
|
|
|
# Energy monitoring
|
|
mqtt_energy_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for energy data
|
|
mqtt_energy_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "energy_l1"
|
|
mqtt_energy_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for energy
|
|
|
|
# State monitoring
|
|
mqtt_state_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for state data
|
|
mqtt_state_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "state_l1" for ON/OFF
|
|
mqtt_state_on_value: Mapped[str | None] = mapped_column(
|
|
String(50), nullable=True
|
|
) # What value means "ON" (e.g., "ON", "true", "1")
|
|
|
|
# Legacy multiplier - kept for backward compatibility
|
|
mqtt_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Deprecated, use mqtt_power_multiplier
|
|
|
|
# REST/Webhook plug fields (required when plug_type="rest")
|
|
# Control URLs
|
|
rest_on_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Full URL to turn ON
|
|
rest_on_body: Mapped[str | None] = mapped_column(Text, nullable=True) # Request body for ON
|
|
rest_off_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Full URL to turn OFF
|
|
rest_off_body: Mapped[str | None] = mapped_column(Text, nullable=True) # Request body for OFF
|
|
rest_method: Mapped[str | None] = mapped_column(String(10), nullable=True) # HTTP method: POST, PUT, GET
|
|
rest_headers: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON string of custom headers
|
|
# Status polling (optional)
|
|
rest_status_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # GET endpoint for state
|
|
rest_status_path: Mapped[str | None] = mapped_column(String(200), nullable=True) # JSON path to state value
|
|
rest_status_on_value: Mapped[str | None] = mapped_column(String(50), nullable=True) # What value means ON
|
|
# Energy monitoring (optional — can use separate URLs or extract from status response)
|
|
rest_power_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Separate URL for power data
|
|
rest_power_path: Mapped[str | None] = mapped_column(String(200), nullable=True) # JSON path for power (watts)
|
|
rest_power_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for power
|
|
rest_energy_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Separate URL for energy data
|
|
rest_energy_path: Mapped[str | None] = mapped_column(String(200), nullable=True) # JSON path for energy (kWh)
|
|
rest_energy_multiplier: Mapped[float] = mapped_column(
|
|
Float, default=1.0
|
|
) # Unit conversion (e.g., 0.001 for Wh→kWh)
|
|
|
|
# Link to printer (multiple plugs/scripts can be linked to one printer)
|
|
printer_id: Mapped[int | None] = mapped_column(ForeignKey("printers.id", ondelete="SET NULL"), nullable=True)
|
|
|
|
# Automation settings
|
|
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
auto_on: Mapped[bool] = mapped_column(Boolean, default=True) # Turn on at print start
|
|
auto_off: Mapped[bool] = mapped_column(Boolean, default=True) # Turn off at print complete/fail
|
|
auto_off_persistent: Mapped[bool] = mapped_column(Boolean, default=False) # Keep auto-off enabled between prints
|
|
|
|
# Turn-off delay mode: "time" or "temperature"
|
|
off_delay_mode: Mapped[str] = mapped_column(String(20), default="time")
|
|
off_delay_minutes: Mapped[int] = mapped_column(Integer, default=5) # For time mode
|
|
off_temp_threshold: Mapped[int] = mapped_column(Integer, default=70) # For temp mode (°C)
|
|
|
|
# Optional auth (some Tasmota configs require it)
|
|
username: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
password: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
|
|
# Power alerts
|
|
power_alert_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
power_alert_high: Mapped[float | None] = mapped_column(Float, nullable=True) # Alert when power > this (watts)
|
|
power_alert_low: Mapped[float | None] = mapped_column(Float, nullable=True) # Alert when power < this (watts)
|
|
power_alert_last_triggered: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # Cooldown tracking
|
|
|
|
# Schedule (time-based on/off)
|
|
schedule_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
schedule_on_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format
|
|
schedule_off_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format
|
|
|
|
# Switchbar visibility
|
|
show_in_switchbar: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
|
|
# Printer card visibility (for scripts)
|
|
show_on_printer_card: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
|
|
# Status tracking
|
|
last_state: Mapped[str | None] = mapped_column(String(10), nullable=True) # "ON"/"OFF"
|
|
last_checked: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
auto_off_executed: Mapped[bool] = mapped_column(Boolean, default=False) # True when auto-off was triggered
|
|
auto_off_pending: Mapped[bool] = mapped_column(Boolean, default=False) # True when waiting for cooldown
|
|
auto_off_pending_since: Mapped[datetime | None] = mapped_column(
|
|
DateTime, nullable=True
|
|
) # When auto-off was scheduled
|
|
|
|
# Timestamps
|
|
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())
|
|
|
|
# Relationship
|
|
printer: Mapped["Printer"] = relationship(back_populates="smart_plugs")
|
|
|
|
|
|
from backend.app.models.printer import Printer # noqa: E402
|