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>
This commit is contained in:
co-authored by
Hermes
parent
af9a99a379
commit
1f142f5245
@@ -0,0 +1,69 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user