updated interface
This commit is contained in:
@@ -65,6 +65,10 @@ class Board(db.Model):
|
||||
"TuyaDevice", back_populates="board",
|
||||
cascade="all, delete-orphan", lazy="dynamic"
|
||||
)
|
||||
devices = db.relationship(
|
||||
"Device", back_populates="board",
|
||||
cascade="all, delete-orphan", lazy="dynamic"
|
||||
)
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────
|
||||
@property
|
||||
|
||||
105
app/models/device.py
Normal file
105
app/models/device.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""User-defined device abstraction.
|
||||
|
||||
A Device is a human-friendly alias for a board relay (controllable output) or
|
||||
a board digital input (sensor/trigger) that the user wants to expose as a named
|
||||
entity — e.g. "Outdoor Light 1 (Courtyard)" mapped to Board "Garage" / Relay 4.
|
||||
|
||||
Devices provide an intermediate, named layer so they can later be placed on
|
||||
Layout pages as interactive widgets without the UI needing raw board/relay IDs.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from app import db
|
||||
|
||||
|
||||
class Device(db.Model):
|
||||
__tablename__ = "devices"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128), nullable=False)
|
||||
description = db.Column(db.String(256), nullable=True)
|
||||
area = db.Column(db.String(64), nullable=True) # e.g. "Courtyard", "Living Room"
|
||||
|
||||
# Device category — drives default icon and state labels
|
||||
device_class = db.Column(db.String(32), nullable=False, default="switch")
|
||||
|
||||
# Bootstrap-Icons class override; None → use type default
|
||||
icon = db.Column(db.String(64), nullable=True)
|
||||
|
||||
# ── Source entity on a board ─────────────────────────────────────────────
|
||||
board_id = db.Column(
|
||||
db.Integer, db.ForeignKey("boards.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
entity_type = db.Column(db.String(16), nullable=True) # "relay" | "input"
|
||||
entity_num = db.Column(db.Integer, nullable=True) # 1-based relay/input index
|
||||
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
# ── relationships ────────────────────────────────────────────────────────
|
||||
board = db.relationship("Board", back_populates="devices")
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────
|
||||
@property
|
||||
def is_controllable(self) -> bool:
|
||||
"""True when the source entity is a relay (can be toggled ON/OFF)."""
|
||||
return self.entity_type == "relay"
|
||||
|
||||
@property
|
||||
def effective_icon(self) -> str:
|
||||
"""Icon class to use in the UI (custom override or type default)."""
|
||||
from app.models.entity_types import RELAY_ENTITY_TYPES, INPUT_ENTITY_TYPES
|
||||
if self.icon:
|
||||
return self.icon
|
||||
if self.entity_type == "relay":
|
||||
return RELAY_ENTITY_TYPES.get(
|
||||
self.device_class, RELAY_ENTITY_TYPES["switch"]
|
||||
)["icon"]
|
||||
if self.entity_type == "input":
|
||||
return INPUT_ENTITY_TYPES.get(
|
||||
self.device_class, INPUT_ENTITY_TYPES["generic"]
|
||||
)["icon"]
|
||||
return "bi-cpu"
|
||||
|
||||
@property
|
||||
def current_state(self):
|
||||
"""Returns the current boolean state or None if unavailable."""
|
||||
if not self.board or not self.entity_type or not self.entity_num:
|
||||
return None
|
||||
if self.entity_type == "relay":
|
||||
return self.board.relay_states.get(f"relay_{self.entity_num}", False)
|
||||
if self.entity_type == "input":
|
||||
raw = self.board.input_states.get(f"input_{self.entity_num}", True)
|
||||
return not raw # NC contact: raw True = resting → False = idle
|
||||
return None
|
||||
|
||||
@property
|
||||
def state_label(self) -> str:
|
||||
"""Human-readable state string."""
|
||||
from app.models.entity_types import RELAY_ENTITY_TYPES, INPUT_ENTITY_TYPES
|
||||
state = self.current_state
|
||||
if state is None:
|
||||
return "Unknown"
|
||||
if self.entity_type == "relay":
|
||||
tdef = RELAY_ENTITY_TYPES.get(self.device_class, RELAY_ENTITY_TYPES["switch"])
|
||||
return tdef["on"][1] if state else tdef["off"][1]
|
||||
if self.entity_type == "input":
|
||||
tdef = INPUT_ENTITY_TYPES.get(self.device_class, INPUT_ENTITY_TYPES["generic"])
|
||||
return tdef["active"][1] if state else tdef["idle"][1]
|
||||
return "Unknown"
|
||||
|
||||
@property
|
||||
def state_color(self) -> str:
|
||||
"""Bootstrap color name for current state badge."""
|
||||
from app.models.entity_types import RELAY_ENTITY_TYPES, INPUT_ENTITY_TYPES
|
||||
state = self.current_state
|
||||
if state is None:
|
||||
return "secondary"
|
||||
if self.entity_type == "relay":
|
||||
tdef = RELAY_ENTITY_TYPES.get(self.device_class, RELAY_ENTITY_TYPES["switch"])
|
||||
return tdef["on"][0] if state else tdef["off"][0]
|
||||
if self.entity_type == "input":
|
||||
tdef = INPUT_ENTITY_TYPES.get(self.device_class, INPUT_ENTITY_TYPES["generic"])
|
||||
return tdef["active"][0] if state else tdef["idle"][0]
|
||||
return "secondary"
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Device {self.name!r} id={self.id}>"
|
||||
Reference in New Issue
Block a user