"""Base class every board driver must implement. To create a new board driver: 1. Make a subfolder inside ``app/drivers/`` (e.g. ``my_custom_board/``) 2. Add a ``driver.py`` that subclasses ``BoardDriver`` 3. Set the class-level metadata attributes 4. The registry will auto-discover and register it at startup — no other changes needed anywhere in the application. """ from __future__ import annotations from abc import ABC, abstractmethod from typing import TYPE_CHECKING if TYPE_CHECKING: from app.models.board import Board class BoardDriver(ABC): """Abstract interface all board drivers must satisfy.""" # ── Metadata (override in subclass) ────────────────────────────────────── #: Unique identifier stored in Board.board_type column DRIVER_ID: str = "" #: Human-readable name shown in the UI DISPLAY_NAME: str = "" #: Short description shown on the Add-board form DESCRIPTION: str = "" #: Default I/O counts pre-filled when this type is selected DEFAULT_NUM_RELAYS: int = 4 DEFAULT_NUM_INPUTS: int = 4 #: Firmware download link shown in the UI (optional) FIRMWARE_URL: str = "" # ── Core API ────────────────────────────────────────────────────────────── @abstractmethod def get_relay_status(self, board: "Board", relay_num: int) -> bool | None: """Return current relay state or None on comm error.""" @abstractmethod def set_relay(self, board: "Board", relay_num: int, state: bool) -> bool: """Set relay ON/OFF. Return True on success.""" @abstractmethod def toggle_relay(self, board: "Board", relay_num: int) -> bool | None: """Toggle relay. Return new state or None on error.""" @abstractmethod def poll(self, board: "Board") -> dict: """Fetch all I/O states. Must return:: { "relay_states": {"relay_1": True, ...}, "input_states": {"input_1": False, ...}, "is_online": True, "firmware_version": "1.0.0", # optional } """ @abstractmethod def register_webhook(self, board: "Board", callback_url: str) -> bool: """Tell the board where to POST input events. Return True on success.""" # ── Optional hooks ──────────────────────────────────────────────────────── def on_board_added(self, board: "Board", server_base_url: str) -> None: """Called once right after a board is created in the DB.""" self.register_webhook(board, f"{server_base_url}/api/webhook/{board.id}") def on_board_deleted(self, board: "Board") -> None: """Called just before a board is deleted from the DB.""" def __repr__(self) -> str: return f""