- dashboard.py: plugin-based renderer with 4x4 grid layout - admin.py: web UI with layout editor + plugin configs - layout.py: pack algorithm, item placement, grid system - plugins/: clock, weather, system, spotify, strava, gmail, minimax, hello - network_watchdog.py: WiFi AP/client mode management - waveshare_epd_init.py: vendor driver stub
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
"""Plugin ABC für das Waveshare 7.3" Dashboard.
|
|
|
|
Ein Plugin ist ein Python-Modul in /plugins/, das eine Klasse Widget
|
|
exportiert. Die Klasse wird beim Start dynamisch geladen und in der
|
|
Admin-UI zur Auswahl angeboten.
|
|
|
|
Minimal-Beispiel siehe plugins/hello.py.
|
|
"""
|
|
from __future__ import annotations
|
|
from abc import ABC, abstractmethod
|
|
from typing import Any
|
|
|
|
|
|
class Widget(ABC):
|
|
# ---- Metadaten (Klassenattribute) ----
|
|
name: str = "" # Eindeutiger Identifier, lowercase, keine Leerzeichen
|
|
label: str = "" # Anzeigename in der UI
|
|
description: str = "" # Kurzbeschreibung in der UI
|
|
category: str = "general" # "info" | "system" | "weather" | "smart-home" | ...
|
|
|
|
# Optional: Schema der Config-Felder (für UI-Form-Generierung).
|
|
# Liste von Dicts mit keys: key, label, type, default, secret, choices, help
|
|
# type ∈ {"string", "int", "float", "bool", "secret", "select", "lat_lon"}
|
|
config_schema: list[dict] = []
|
|
default_config: dict = {}
|
|
|
|
def __init__(self, config: dict):
|
|
# Merge defaults mit übergebener Config
|
|
merged = dict(self.default_config)
|
|
merged.update(config or {})
|
|
self.config = merged
|
|
|
|
@abstractmethod
|
|
def fetch(self) -> dict:
|
|
"""Daten holen. Sollte schnell sein — wird alle refresh_interval Sekunden
|
|
aufgerufen, plus einmal vor jedem Render. Exceptions werden geloggt und
|
|
führen zur Beibehaltung der letzten Daten."""
|
|
|
|
@abstractmethod
|
|
def render(self, draw, fonts, x: int, y: int, w: int, h: int) -> None:
|
|
"""Zeichne in den gegebenen Slot (x,y,w,h) auf den draw-Context.
|
|
Renderer nutzt RGB-Palette aus palette.py."""
|
|
|
|
# ---- Optionale Lifecycle-Hooks ----
|
|
def on_load(self) -> None:
|
|
"""Wird einmal beim Plugin-Load aufgerufen."""
|
|
|
|
def on_unload(self) -> None:
|
|
"""Wird beim Beenden aufgerufen."""
|
|
|
|
# ---- Helper für Plugins ----
|
|
def cfg(self, key: str, default: Any = None) -> Any:
|
|
return self.config.get(key, default)
|
|
|
|
|
|
def all_widget_classes() -> list[type[Widget]]:
|
|
"""Lade alle Plugin-Klassen aus dem plugins/-Ordner."""
|
|
import os, importlib, pkgutil
|
|
plugins_pkg = os.path.join(os.path.dirname(__file__), "..", "plugins")
|
|
plugins_pkg = os.path.abspath(plugins_pkg)
|
|
classes: list[type[Widget]] = []
|
|
for _, modname, _ in pkgutil.iter_modules([plugins_pkg]):
|
|
mod = importlib.import_module(f"plugins.{modname}")
|
|
cls = getattr(mod, "Widget", None)
|
|
if cls and isinstance(cls, type) and issubclass(cls, Widget) and cls is not Widget:
|
|
classes.append(cls)
|
|
# alphabetisch
|
|
classes.sort(key=lambda c: c.label or c.name)
|
|
return classes
|