epaper-display/display/docs/superpowers/specs/2026-04-06-epaper-display-system-design.md
Christian Mueller 7ffad8364b docs: add design specification for ESP32 E-Paper display system
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:35:01 +02:00

16 KiB
Raw Permalink Blame History

ESP32 E-Paper Anzeigesystem — Design-Spezifikation

1. Übersicht

Modulares System zur Anzeige von Daten aus MQTT-Brokern und PostgreSQL-Datenbanken auf einem ESP32-basierten E-Paper-Display (Waveshare 7.5" V2, 800×480, schwarz/weiß).

Komponenten:

  • Middleware (Node.js/Express/TypeScript) — Datensammlung, Verarbeitung, API
  • Webinterface (React/TypeScript) — Konfiguration und Verwaltung
  • ESP32 Firmware (PlatformIO/Arduino) — Display-Rendering

Architektur: Modularer Monolith — ein Express-Server mit klar getrennten internen Modulen. React SPA wird als Static Files ausgeliefert.

Datenbank: SQLite (Config, Datenpunkte, Layouts)

Deployment: Proxmox LXC-Container, Deploy via Git Clone

2. Systemarchitektur

Datenquellen (MQTT, PostgreSQL)
            │
            ▼
       Middleware
   (Node.js/Express/TS)
   ┌─────────────────────────────┐
   │  MQTT Module                │
   │  PostgreSQL Module          │
   │  Datapoint Manager          │
   │  Display Module             │
   │  REST API (JWT Auth)        │
   │  SQLite DB                  │
   └──────────┬──────────────────┘
              │
              ├──► Webinterface (React SPA)
              │
              └──► ESP32 (E-Paper)

2.1 Module

Modul Verantwortung
MQTT Module Broker-Verbindungen, Topic Discovery (Wildcard + Prefix), Payload-Parsing (JSON-Pfade), Werte an Datapoint Manager weiterleiten
PostgreSQL Module DB-Connections verwalten, konfigurierte Queries periodisch ausführen, Ergebnisse auf Datenpunkte mappen
Datapoint Manager Zentrale Registry aller Datenpunkte mit Name, Einheit, Wert, Timestamp, Status. Transformationen (Runden, Skalieren, Mapping). SQLite-Persistenz
Display Module Layout-Templates, Zuordnung Datenpunkte → Layout-Elemente, JSON-Builder für ESP32 API. Unterstützt freie Positionierung und Grid-Modus
REST API Express-Router mit JWT-Auth, CRUD für alle Entitäten, Display-Endpoint für ESP32

3. Datenmodell (SQLite)

3.1 mqtt_brokers

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
name TEXT NOT NULL Anzeigename
host TEXT NOT NULL Broker-Hostname
port INTEGER DEFAULT 1883 Port
username TEXT NULL Auth
password TEXT NULL Auth (verschlüsselt gespeichert)
use_tls BOOLEAN DEFAULT false TLS aktivieren
enabled BOOLEAN DEFAULT true Broker aktiv
status TEXT DEFAULT 'disconnected' Verbindungsstatus
created_at DATETIME Erstellzeitpunkt
updated_at DATETIME Letzte Änderung

3.2 mqtt_topics

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
broker_id INTEGER FK Referenz auf mqtt_brokers
topic TEXT NOT NULL Topic-Pfad
json_path TEXT NULL JSON-Pfad für Wert-Extraktion (z.B. $.value)
selected BOOLEAN DEFAULT false Vom User ausgewählt
ignored BOOLEAN DEFAULT false Vom User ignoriert
last_payload TEXT NULL Letzte empfangene Payload
discovered_at DATETIME Entdeckungszeitpunkt

3.3 pg_sources

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
name TEXT NOT NULL Anzeigename
host TEXT NOT NULL DB-Host
port INTEGER DEFAULT 5432 Port
database TEXT NOT NULL Datenbankname
username TEXT NOT NULL DB-User
password TEXT NOT NULL DB-Passwort (verschlüsselt)
use_tls BOOLEAN DEFAULT false TLS
enabled BOOLEAN DEFAULT true Aktiv
status TEXT DEFAULT 'disconnected' Verbindungsstatus
created_at DATETIME Erstellzeitpunkt

3.4 pg_queries

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
source_id INTEGER FK Referenz auf pg_sources
name TEXT NOT NULL Anzeigename
query TEXT NOT NULL SQL-Abfrage (read-only)
interval_sec INTEGER DEFAULT 60 Abfrage-Intervall
result_column TEXT NOT NULL Spaltenname für den Wert
last_result TEXT NULL Letztes Ergebnis
last_run_at DATETIME NULL Letzte Ausführung

3.5 datapoints

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
name TEXT NOT NULL Technischer Name
label TEXT NOT NULL Anzeigename
unit TEXT NULL Einheit (°C, %, ppm, ...)
value TEXT NULL Letzter Wert
status TEXT DEFAULT 'unknown' ok / error / stale
source_type TEXT NOT NULL 'mqtt' oder 'postgresql'
source_id INTEGER NOT NULL FK auf Broker oder PG-Source
topic_id INTEGER NULL FK auf mqtt_topics (nur bei MQTT)
query_id INTEGER NULL FK auf pg_queries (nur bei PG)
transform TEXT NULL JSON: {"round": 1, "scale": 1.0, "map": {}}
enabled BOOLEAN DEFAULT true Aktiv
updated_at DATETIME Letztes Update

3.6 displays

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
name TEXT NOT NULL Anzeigename
description TEXT NULL Beschreibung
layout_id INTEGER FK Referenz auf layouts
api_token TEXT NULL Optionaler API-Token für ESP32
last_seen DATETIME NULL Letzter ESP32-Abruf
refresh_sec INTEGER DEFAULT 300 Update-Intervall (5 Min)
created_at DATETIME Erstellzeitpunkt

3.7 layouts

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
name TEXT NOT NULL Layout-Name
type TEXT DEFAULT 'free' 'free' (freie Positionierung) oder 'grid'
width INTEGER DEFAULT 800 Display-Breite
height INTEGER DEFAULT 480 Display-Höhe
grid_cols INTEGER NULL Spalten bei Grid-Modus
grid_rows INTEGER NULL Zeilen bei Grid-Modus
created_at DATETIME Erstellzeitpunkt

3.8 layout_elements

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
layout_id INTEGER FK Referenz auf layouts
datapoint_id INTEGER FK NULL Zugewiesener Datenpunkt
type TEXT NOT NULL 'value', 'label', 'line', 'rect'
x INTEGER NOT NULL X-Position
y INTEGER NOT NULL Y-Position
width INTEGER NULL Breite (für Karten, Linien, Rects)
height INTEGER NULL Höhe
font_size INTEGER DEFAULT 24 Schriftgröße
font_weight TEXT DEFAULT 'normal' 'normal' oder 'bold'
text_align TEXT DEFAULT 'left' 'left', 'center', 'right'
icon TEXT NULL Icon-Name (thermometer, droplet, ...)
style TEXT DEFAULT 'plain' 'plain' (nur Text) oder 'card' (mit Rahmen)
static_text TEXT NULL Fester Text für Labels/Titel
grid_col INTEGER NULL Spalte im Grid-Modus
grid_row INTEGER NULL Zeile im Grid-Modus
sort_order INTEGER DEFAULT 0 Reihenfolge

3.9 users

Spalte Typ Beschreibung
id INTEGER PK Auto-Increment
username TEXT UNIQUE NOT NULL Benutzername
password_hash TEXT NOT NULL bcrypt-Hash
created_at DATETIME Erstellzeitpunkt

4. REST API

4.1 Authentifizierung

  • Login: POST /api/auth/login → JWT Token
  • Alle /api/* Endpunkte (außer Display-Endpoint) erfordern JWT im Authorization: Bearer <token> Header
  • Display-Endpoint optional per Query-Param: ?token=xxx

4.2 Endpunkte

Methode Pfad Beschreibung
Auth
POST /api/auth/login Login → JWT
MQTT Broker
GET /api/mqtt/brokers Alle Broker auflisten
POST /api/mqtt/brokers Broker hinzufügen
PUT /api/mqtt/brokers/:id Broker bearbeiten
DELETE /api/mqtt/brokers/:id Broker löschen
POST /api/mqtt/brokers/:id/test Verbindung testen
MQTT Topics
GET /api/mqtt/brokers/:id/topics Topics eines Brokers
PUT /api/mqtt/topics/:id Topic bearbeiten
POST /api/mqtt/brokers/:id/discover Discovery starten
PostgreSQL
GET/POST /api/pg/sources PG-Verbindungen
PUT/DELETE /api/pg/sources/:id PG-Verbindung bearbeiten/löschen
POST /api/pg/sources/:id/test Verbindung testen
GET/POST /api/pg/sources/:id/queries Queries verwalten
PUT/DELETE /api/pg/queries/:id Query bearbeiten/löschen
Datenpunkte
GET /api/datapoints Alle Datenpunkte
PUT /api/datapoints/:id Datenpunkt bearbeiten
Displays
GET/POST /api/displays Displays verwalten
PUT/DELETE /api/displays/:id Display bearbeiten/löschen
Layouts
GET/POST /api/layouts Layouts verwalten
GET /api/layouts/:id Layout mit Elementen
PUT/DELETE /api/layouts/:id Layout bearbeiten/löschen
POST /api/layouts/:id/elements Element hinzufügen
PUT/DELETE /api/layouts/elements/:id Element bearbeiten/löschen
ESP32 Display
GET /api/display/:id Display-Daten für ESP32

4.3 ESP32 JSON-Format

GET /api/display/1 Response:

{
  "display": {
    "id": 1,
    "name": "Werkstatt",
    "width": 800,
    "height": 480,
    "refresh_sec": 300
  },
  "layout": {
    "type": "free"
  },
  "elements": [
    {
      "type": "label",
      "x": 400, "y": 20,
      "font_size": 28, "font_weight": "bold",
      "text_align": "center",
      "text": "Werkstatt Monitor"
    },
    {
      "type": "value",
      "x": 20, "y": 80,
      "width": 380, "height": 90,
      "font_size": 32, "font_weight": "bold",
      "icon": "thermometer",
      "style": "card",
      "label": "Temperatur",
      "value": "23.5",
      "unit": "°C"
    },
    {
      "type": "value",
      "x": 410, "y": 80,
      "width": 380, "height": 90,
      "font_size": 32, "font_weight": "bold",
      "icon": "droplet",
      "style": "card",
      "label": "Luftfeuchte",
      "value": "48",
      "unit": "%"
    },
    {
      "type": "line",
      "x": 0, "y": 60,
      "width": 800, "height": 2
    },
    {
      "type": "label",
      "x": 400, "y": 465,
      "font_size": 12, "text_align": "center",
      "text": "Aktualisiert: 06.04.2026 10:15"
    }
  ]
}

Für Grid-Layouts enthält layout zusätzlich grid_cols und grid_rows, und die Elemente werden automatisch positioniert.

5. Webinterface (React SPA)

5.1 Seiten

Seite Funktionalität
Dashboard Status-Kacheln (Broker, Datenpunkte, Displays), Event-Log
MQTT Broker CRUD für Broker, Verbindungstest, Status
Topics Topic-Liste mit Checkbox, Filter/Suche, Payload-Vorschau, JSON-Pfad
PostgreSQL CRUD für Connections + Queries, Test-Ausführung
Datenpunkte Tabelle aller Punkte, Label/Einheit/Transform bearbeiten
Displays Display-Verwaltung, Layout-Zuweisung, API-Token, Refresh-Intervall
Layouts Layout-Editor: Typ wählen (free/grid), Elemente positionieren, Datenpunkte zuweisen, Icons wählen, Style (card/plain)
Vorschau E-Paper-Simulation (schwarz/weiß, 800×480 proportional)

5.2 UI-Design

  • Sidebar-Navigation (fixiert, links)
  • Responsives Layout
  • Dunkle Sidebar (#1a1a2e), heller Content-Bereich
  • Status-Farben: Grün (verbunden/ok), Orange (warning), Rot (error)

5.3 Topic Discovery

  • Prefix-basiert als Default: User gibt Prefix ein (z.B. sensor/), System subscribed auf sensor/#
  • Optional: volle Discovery mit # (Wildcard)
  • Entdeckte Topics werden in Liste angezeigt mit Checkbox (auswählen/ignorieren)
  • Payload-Vorschau zeigt letzte Message + JSON-Pfad-Konfiguration

6. ESP32 Firmware

6.1 Hardware

  • Board: Waveshare e-Paper ESP32 Driver Board
  • Display: Waveshare 7.5" V2 (800×480, schwarz/weiß)
  • Interface: SPI

6.2 PlatformIO-Projekt

Libraries:

  • GxEPD2 — E-Paper Display-Treiber
  • ArduinoJson — JSON-Parsing
  • Adafruit GFX — Grafik-Primitives
  • WiFi (built-in) — WiFi-Stack
  • HTTPClient (built-in) — HTTP-Requests

6.3 Ablauf

1. Boot / Wake from Deep Sleep
2. WiFi verbinden (3 Versuche, sonst Fehlermeldung auf Display)
3. HTTP GET /api/display/{id}?token=xxx
4. JSON parsen
5. Display rendern:
   - Elemente iterieren
   - Je nach type: Text, Linie, Rechteck, Icon zeichnen
   - Card-Style: Rahmen mit abgerundeten Ecken
   - Grid-Mode: automatische Positionierung
6. Deep Sleep für refresh_sec Sekunden

6.4 Renderer

Der Renderer iteriert über das elements[]-Array und zeichnet je nach type:

  • label: Text an x/y mit font_size, font_weight, text_align
  • value: Label + Wert + Einheit, optional mit Icon und Card-Rahmen
  • line: Horizontale/vertikale Linie
  • rect: Rechteck (gefüllt oder Outline)

Icons: 12 vordefinierte Bitmaps (24×24 px) im Flash: thermometer, droplet, wind, pressure, sun, bolt, gauge, wifi, battery, check, warning, clock

6.5 Konfiguration (Captive Portal)

Beim ersten Start oder wenn kein WiFi konfiguriert:

  1. ESP32 startet als Access Point ("EPaper-Setup")
  2. Captive Portal mit Konfigurationsseite
  3. Eingabefelder: WiFi SSID, Passwort, API-URL, Display-ID, optionaler Token
  4. Gespeichert im NVS (Non-Volatile Storage)
  5. Reboot und Verbindung

6.6 Fehlerbehandlung

Situation Verhalten
WiFi-Fehler 3 Versuche, dann Fehlermeldung auf Display, Deep Sleep 60s
API-Fehler Letzte gültige Anzeige behalten, "Offline" im Footer, Deep Sleep 60s
JSON-Fehler Fehlermeldung auf Display, letzte Anzeige beibehalten

7. Deployment

  • Ziel: Neuer Proxmox LXC-Container
  • Provisionierung: Per /proxmox-lxc Skill
  • Deploy: Git Clone auf LXC, npm install, npm run build, systemd Service
  • Webinterface: React Build als Static Files im Express-Server
  • Port: 4000 (Backend + Frontend)

8. Projektstruktur

display/
├── middleware/                  # Node.js Backend
│   ├── src/
│   │   ├── modules/
│   │   │   ├── mqtt/           # MQTT Module
│   │   │   ├── postgres/       # PostgreSQL Module
│   │   │   ├── datapoints/     # Datapoint Manager
│   │   │   └── display/        # Display/Layout Module
│   │   ├── api/
│   │   │   ├── routes/         # Express Routes
│   │   │   ├── middleware/     # Auth, Error Handler
│   │   │   └── index.ts        # API Setup
│   │   ├── db/
│   │   │   ├── migrations/     # SQLite Migrations
│   │   │   └── index.ts        # DB Connection
│   │   └── index.ts            # Entry Point
│   ├── package.json
│   └── tsconfig.json
├── frontend/                    # React SPA
│   ├── src/
│   │   ├── pages/              # Dashboard, Broker, Topics, ...
│   │   ├── components/         # Shared Components
│   │   ├── api/                # API Client
│   │   └── App.tsx
│   ├── package.json
│   └── tsconfig.json
├── esp32/                       # PlatformIO Projekt
│   ├── src/
│   │   └── main.cpp
│   ├── include/
│   │   └── icons.h             # Icon Bitmaps
│   └── platformio.ini
└── docs/

9. MVP-Scope

Für den ersten Test:

  • 1 MQTT-Broker anbinden
  • 5 Datenpunkte konfigurieren
  • 1 Layout mit 5 Werten (gemischt: 2er-Reihen + 1er-Reihe)
  • 1 Display
  • ESP32 ruft Daten ab und zeigt sie an
  • Webinterface: Dashboard, Broker-Config, Topic-Auswahl, Datenpunkte, Layout-Editor, Vorschau