docs: add design specification for ESP32 E-Paper display system
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8d5e4106d6
commit
7ffad8364b
@ -0,0 +1,437 @@
|
||||
# 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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
Loading…
x
Reference in New Issue
Block a user