Files
epaper-dashboard/.backup/2026-08-26-active/plugins/base.py
T
epaper-dashboardandHermes 1f142f5245 Sync to Pi: alle Features die live deployed sind
Aus dem Backup und Live-Pull vom Pi (10.11.3.144):
- dashboard.py: Grid-Linien nur im freien Hintergrund (nicht durch Widgets)
- templates/index.html: komplett redesigned mit Sidebar + Topbar + Toast + Modal
- plugins/clock.py: responsive Layout (1x1 bis 4x4)
- plugins/system.py, weather.py, minimax.py: mit Threshold-Bars und Color-Variants
- plugins/base.py: NEU — fetch_with_retry Helper (3x retry mit backoff)
  + render_error_banner für fehlgeschlagene API-Plugins
  (grosses rotes "!" Icon mit Plugin-Name und Fehler statt Crash)

Cleanup: Helfer-Chaos (renderer.py/2/3, design_a/b/c.html, clock_classic.py,
23x clock_*.png, alte test_*.py) wurde bereits im vorherigen Commit entfernt.

Co-Authored-By: Hermes <noreply@hermes.local>
2026-08-26 22:11:36 +04:00

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