Files
location_managemet/app/models/board.py
2026-02-26 19:24:17 +02:00

145 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Board model.
Supported board types
---------------------
olimex_esp32_c6 Olimex ESP32-C6-EVB (4 relays, 4 digital inputs)
Communicates via HTTP REST + webhook callbacks
generic_esp32 Generic ESP32 with custom firmware (configurable I/O)
generic_esp8266 Generic ESP8266 with custom firmware (configurable I/O)
"""
import json
from datetime import datetime
from app import db
class Board(db.Model):
__tablename__ = "boards"
id = db.Column(db.Integer, primary_key=True)
# Human-readable local name
name = db.Column(db.String(128), nullable=False)
board_type = db.Column(db.String(32), nullable=False, default="olimex_esp32_c6_evb")
host = db.Column(db.String(128), nullable=False)
port = db.Column(db.Integer, nullable=False, default=80)
# Number of relays / outputs exposed by this board
num_relays = db.Column(db.Integer, nullable=False, default=4)
# Number of digital inputs
num_inputs = db.Column(db.Integer, nullable=False, default=4)
# JSON blob: {"relay_1": true, "relay_2": false, ...}
relay_states_json = db.Column(db.Text, default="{}")
# JSON blob: {"input_1": false, "input_2": true, ...}
input_states_json = db.Column(db.Text, default="{}")
# Relay and input labels (legacy, kept for backward compat)
labels_json = db.Column(db.Text, default="{}")
# Entity config: {"relay_1": {"type": "light", "name": "...", "icon": ""}, ...}
entities_json = db.Column(db.Text, default="{}")
# Whether this board is currently reachable
is_online = db.Column(db.Boolean, default=False)
last_seen = db.Column(db.DateTime, nullable=True)
# Firmware version string reported by the board
firmware_version = db.Column(db.String(64), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# ── relationships ────────────────────────────────────────────────
workflows_trigger = db.relationship(
"Workflow", foreign_keys="Workflow.trigger_board_id",
back_populates="trigger_board", cascade="all, delete-orphan"
)
workflows_target = db.relationship(
"Workflow", foreign_keys="Workflow.action_board_id",
back_populates="action_board"
)
# ── helpers ──────────────────────────────────────────────────────
@property
def relay_states(self) -> dict:
try:
return json.loads(self.relay_states_json or "{}")
except (ValueError, TypeError):
return {}
@relay_states.setter
def relay_states(self, value: dict):
self.relay_states_json = json.dumps(value)
@property
def input_states(self) -> dict:
try:
return json.loads(self.input_states_json or "{}")
except (ValueError, TypeError):
return {}
@input_states.setter
def input_states(self, value: dict):
self.input_states_json = json.dumps(value)
@property
def labels(self) -> dict:
try:
return json.loads(self.labels_json or "{}")
except (ValueError, TypeError):
return {}
@labels.setter
def labels(self, value: dict):
self.labels_json = json.dumps(value)
@property
def entities(self) -> dict:
try:
return json.loads(self.entities_json or "{}")
except (ValueError, TypeError):
return {}
@entities.setter
def entities(self, value: dict):
self.entities_json = json.dumps(value)
def get_relay_entity(self, n: int) -> dict:
from app.models.entity_types import RELAY_ENTITY_TYPES
cfg = self.entities.get(f"relay_{n}", {})
etype = cfg.get("type", "switch")
tdef = RELAY_ENTITY_TYPES.get(etype, RELAY_ENTITY_TYPES["switch"])
# name: entities > legacy labels > type default
name = cfg.get("name", "").strip() or self.labels.get(f"relay_{n}", "") or f"{tdef['label']} {n}"
icon = cfg.get("icon", "").strip() or tdef["icon"]
return {
"name": name,
"icon": icon,
"type": etype,
"on_color": tdef["on"][0],
"on_label": tdef["on"][1],
"off_color": tdef["off"][0],
"off_label": tdef["off"][1],
}
def get_input_entity(self, n: int) -> dict:
from app.models.entity_types import INPUT_ENTITY_TYPES
cfg = self.entities.get(f"input_{n}", {})
etype = cfg.get("type", "generic")
tdef = INPUT_ENTITY_TYPES.get(etype, INPUT_ENTITY_TYPES["generic"])
name = cfg.get("name", "").strip() or self.labels.get(f"input_{n}", "") or f"{tdef['label']} {n}"
icon = cfg.get("icon", "").strip() or tdef["icon"]
return {
"name": name,
"icon": icon,
"type": etype,
"active_color": tdef["active"][0],
"active_label": tdef["active"][1],
"idle_color": tdef["idle"][0],
"idle_label": tdef["idle"][1],
}
def get_relay_label(self, relay_num: int) -> str:
return self.get_relay_entity(relay_num)["name"]
def get_input_label(self, input_num: int) -> str:
return self.get_input_entity(input_num)["name"]
@property
def base_url(self) -> str:
return f"http://{self.host}:{self.port}"
def __repr__(self) -> str:
return f"<Board {self.name} ({self.board_type}) @ {self.host}:{self.port}>"