No description
  • Python 75.3%
  • HTML 14.7%
  • Nix 9%
  • Makefile 1%
Find a file
2026-08-03 17:51:52 +02:00
firmware Extend gag carousel interaction window 2026-08-03 17:51:52 +02:00
server Add varied gag carousel 2026-08-03 17:36:57 +02:00
tests Add varied gag carousel 2026-08-03 17:36:57 +02:00
tools Handle EU daylight saving time automatically 2026-08-03 17:08:03 +02:00
.gitignore Firmware: WLAN-Daten auslagern, Serveradresse, RTC-Bootstrap reparieren 2026-08-02 20:45:38 +02:00
AGENTS.md Extend gag carousel interaction window 2026-08-03 17:51:52 +02:00
CLAUDE.md Spruch: eigener Vorwaerm-Dienst um 05:00, mehr Token, zweiter Anlauf 2026-08-02 23:10:02 +02:00
config.example.json Spruch: Renderer fragt das Modell nicht mehr selbst 2026-08-03 00:07:48 +02:00
flake.lock Nix: nur Linux-Pakete bauen, Firmware-Tools (esptool/mpremote) + Thonny in devShell 2026-08-02 18:52:45 +02:00
flake.nix Add robust Inkplate firmware installer 2026-08-03 14:58:06 +02:00
Makefile Handle EU daylight saving time automatically 2026-08-03 17:08:03 +02:00
nc.env.example Inkplate 10 Familien-Dashboard: Renderer + MicroPython-Firmware 2026-08-02 18:33:58 +02:00
nixos-module.nix Add button-controlled dashboard pages 2026-08-03 14:36:28 +02:00
README.md Extend gag carousel interaction window 2026-08-03 17:51:52 +02:00
renderer.nix Linken Wake-Taster als Geheimknopf nutzen 2026-08-03 13:04:36 +02:00
requirements.txt Inkplate 10 Familien-Dashboard: Renderer + MicroPython-Firmware 2026-08-02 18:33:58 +02:00

Inkplate 10 Familien-Dashboard

Batteriebetriebenes E-Paper-Dashboard (Inkplate 10) an der Wand: zeigt Wetter, Nextcloud-Kalender und Nextcloud-Todos. Der Server rendert das Dashboard als PNG; das Inkplate zieht (nicht pusht) das Bild stündlich 0620 Uhr und schläft dazwischen tief — das ist der Schlüssel zur Batterielaufzeit.

┌────────────────────────────┐          ┌───────────────────────────────┐
│ LAN-Render-Server (NixOS)  │          │ Inkplate 10                   │
│  Open-Meteo (Wetter)       │          │  MicroPython                  │
│  Nextcloud CalDAV          │  HTTP    │  RTC-Wecker → Bild holen →    │
│  (Kalender + Todos)        │  GET     │  zeichnen → Deep-Sleep        │
│  → dashboard.png (1200×825)│  <----   │  (nur 0620 Uhr, sonst schlaf)│
│  statischer Server :8080   │          ⚟───  22 µA im Tiefschlaf        │
└────────────────────────────┘          └───────────────────────────────┘
  Push wäre falsch: der ESP32 müsste wach bleiben und die Batterie leert
  sich. Pull = robuster UND batteriefreundlich.

Layout

flake.nix                  # devShell + Renderer + NixOS-Modul
renderer.nix               # das Renderer-Paket (Python + Chromium + Fonts)
nixos-module.nix           # systemd-Timer (0620 Uhr) + statischer Server
config.example.json        # Vorlage: Wetter-ORT, Nextcloud, Kalender-Filter
nc.env.example             # Secrets-Vorlage (nie committen!)
firmware/main.py           # MicroPython-Client für den Inkplate 10
server/inkplate_server/    # config, fetch, ics, render, serve, templates
Makefile                   # make render / make serve / make dev

1. Konfiguration

cp config.example.json config.json     # URL + Wetter-Koordinaten anpassen
cp nc.env.example nc.env               # NEXTCLOUD_USER / NEXTCLOUD_PASSWORD
  • Wetter: Open-Meteo, kostenlos, kein API-Key (Koordinaten + tz_name). Neben Temperatur und Wettercode nutzt das Dashboard die stündliche Niederschlagswahrscheinlichkeit für das stärkste verbleibende Regenfenster.
  • Nextcloud: App-Passwort! (Einstellungen → Sicherheit → App-Passwörter)
  • calendar_filter: Teilstrings gegen Kalendername/-ID, z.B. ["familie", "todo"]. Leer = alle. Dein Setup: familie_shared_by_harald → "Familie", todo_shared_by_harald → "Todo - Harald und Meike".
  • Secrets kommen optional aus der Umgebung: NEXTCLOUD_USER / NEXTCLOUD_PASSWORD (Alias für NEXTCLOUD_USERNAME).
  • Ein laufender ganztägiger Termin, dessen Titel auf ferien endet (Sommerferien, Pfingstferien, …), ersetzt den Stundenplan automatisch durch einen Countdown bis zum exklusiven DTEND/ersten Schultag.

2. Lokal rendern (auf dem Mac)

make setup                       # venv mit jinja2 + tzdata
# Chromium/Chrome nötig — auf macOS am einfachsten:
export CHROMIUM_BIN="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
make render                      # → build/dashboard.png (1200×825)
make serve                       # liefert es auf 0.0.0.0:8080 aus
open build/dashboard.png         # Ergebnis ansehen

Renderer-Kern: python -m inkplate_server.render --config config.json --out build/dashboard.png.

3. NixOS-Server (später)

# configuration.nix
{
  inputs.inkplate.url = "github:Harald/inkplate";   # oder lokaler Pfad
  config.services.inkplate = {
    enable = true;
    configFile = "/etc/inkplate/config.json";
    secretsFile = "/etc/inkplate/nc.env";   # NEXTCLOUD_USER/NEXTCLOUD_PASSWORD
    schedule = [ "*-*-* 06..20:00:00" ];    # stündlich 0620 Uhr
  };
}
  • systemd-Timer rendert stündlich; ein statischer Dateiserver liefert dashboard.png auf 0.0.0.0:8080 aus (Port via listenPort).
  • Ausgabeverzeichnis: /var/lib/inkplate (via StateDirectory).
  • Chromium braucht Fonts — das Paket setzt eine fontconfig mit DejaVu/Liberation; für eigene Glyphen dort Fonts ergänzen.

4. Firmware (Inkplate 10)

  1. MicroPython flashen: Inkplate 10 braucht das MikroPython mit dem Inkplate-Modul (Anleitung: docs.soldered.com/inkplate/10 → MicroPython → Setup).
  2. firmware/main.py anpassen: SSID, PASSWORD, SERVER (IP des LAN-Servers).
  3. Als main.py auf den ESP32 laden (SD-Karte oder Soldered-Tool). Nach jedem Wake läuft main.py neu — das Skript entscheidet selbst, ob es ein Update-Zeitpunkt ist (0620 Uhr), und schläft sonst bis zum nächsten Slot.

Für den direkten Upload am Mac gibt es einen robusten Helfer, weil ein normales mpremote cp das sofort wieder schlafende Gerät leicht verpasst:

nix develop --command make firmware-install

Er erkennt den USB-UART automatisch und hält ihn während Hardware-Reset, REPL-Abfangen, API-Probe, atomarem main.py-Upload und Neustart durchgehend offen. Sind mehrere passende Boards angeschlossen, kann der Port mit PORT=/dev/cu.usbserial-210 festgelegt werden.

Beim Bildabruf hängt die Firmware die mit read_battery() (ältere Bibliothek; Fallback readBattery()) gemessene Spannung als Millivolt an die URL (dashboard.png?battery_mv=3870&format=.png). Der scheinbar redundante format-Wert ist nötig, weil der Inkplate-Treiber das Bildformat am Ende der gesamten URL erkennt. Der Server rendert daraus ein vierstufiges Batteriesymbol; eine fehlende Messung verhindert den Bildabruf nicht.

Der linke Gehäusetaster ist der aktive-low Wake-Taster auf GPIO36. Die Firmware registriert ihn als externe Deep-Sleep-Weckquelle und zeigt nach dem Aufwachen lokal ein kleines Kästchen mit drei Punkten in der Bildschirmmitte. Ein kurzer Druck blättert in der Reihenfolge Dashboard → Wetter → Details; die gewählte Seite bleibt über den Deep-Sleep erhalten. Ein weiterer Druck, nachdem das Kästchen sichtbar ist, öffnet die Quatschseite. Dort lädt jeder kurze Druck einen weiteren Gag aus dem vorgerenderten Vorrat und startet das 30-sekündige Bedienfenster erst nach dem fertigen Display-Refresh neu. Langes Halten beendet das Karussell sofort; nach fünf Minuten endet es spätestens. Wird der Knopf beim Aufwachen lange gehalten, erscheint für 30 Sekunden die Technikseite. Beide Sonderseiten kehren danach zur gewählten normalen Seite zurück. Timer-Wake und Power/Reset beginnen wieder beim Dashboard.

Der Timer inkplate-gags lässt das lokale LLM morgens standardmäßig eine Mischung aus zwölf Quatschmeldungen, Bewegungsaktionen, Zungenbrechern, Geheimaufträgen, Scherzfragen und einer freien Überraschung erzeugen und rendert alle PNGs vor. Der Tastendruck wartet dadurch weder auf das Modell noch auf Chromium. Misslingt das Vorwärmen, bleibt der vorige Vorrat erhalten; ohne Vorrat greift die eingebaute Liste. Jeder neue Lauf bekommt einen zufälligen Seed und die Texte des vorigen Pools als Negativbeispiele, damit nicht täglich dieselben Ideen wiederkommen. Stündliche Timer-Wakes überspringen die Sonderseiten vollständig.

Für das Rückmelde-Kästchen wechselt die Firmware kurz in den 1-Bit-Modus und zeichnet nur Rahmen und Punkte per Partial-Update über das bestehende Bild. Danach geht es für die Serverbilder zurück in den 2-Bit-Graustufenmodus. Auf dem echten Gerät wurden 214 ms reine Zeichenzeit beziehungsweise 588 ms für die gesamte Display-Sequenz gemessen.

Wichtige API (verifiziert): from inkplate10 import Inkplate, Inkplate(Inkplate.INKPLATE_2BIT), inkplate.begin(), inkplate.draw_image(url, 0, 0, dither=True, kernel_type=Inkplate.KERNEL_FLOYD_STEINBERG), inkplate.display(), inkplate.rtc_get_data() → dict.

5. Powerbank-Gotcha (du hattest eine alte Powerbank angekündigt)

Problem: Powerbanks mit Auto-Off schalten ab, weil das E-Paper im Schlaf nur ~22 µA zieht. Lösungen, einfach bis aufwendig:

  1. Eine Powerbank ohne Auto-Off / mit Konstantstrom-Modus verwenden.
  2. Kleinen Dauer-Verbrauch: ein Schalter-Widerstand/LED-Dummy in den USB (ca. 50 mA).
  3. Den JST-Eingang des Inkplate direkt mit einem LiPo versorgen (MCP73831-Lader ist schon auf dem Board) — dann entfällt die Powerbank-Fehlerei ganz.

Einschränkungen

  • Kalender-Recurrence: nur DAILY / WEEKLY (mit BYDAY und INTERVAL), begrenzt durch UNTIL/COUNT. MONTHLY/YEARLY/RDATE werden ignoriert (für v1 ok). Verschobene und abgesagte Einzeltermine einer Serie (RECURRENCE-ID) werden berücksichtigt.
  • Vom Server geholt wird nur das Nötige: Termine per CalDAV-time-range im Anzeigefenster, Todos ohne COMPLETED — sonst lädt jeder Render den kompletten Kalenderverlauf (1,1 MB).
  • Zeitzonen: TZID und UTC (Z) werden korrekt in lokale Zeit umgerechnet.
  • NTP-Sync einmal täglich; die Umstellung zwischen MEZ und MESZ berechnet die Firmware nach der EU-Regel automatisch.
  • PNG maximal ~800 kB — unser graustufenbasiertes Dashboard ist weit darunter.