- C 64.1%
- C++ 35.4%
- Python 0.5%
Temperatur gegen relative Feuchte im Behaglichkeitsfeld nach Frank (Leusden/Freymark), Eckpunkte aus Dentel/Dietrich, HCU 2005, Abb. 7 ausgemessen. Punkt mit Spur der letzten Stunde, Urteil (behaglich / noch behaglich / zu kalt / zu warm / zu feucht / zu trocken) und Taupunkt. Teilbild einmal je Minute, eigene Task auf Kern 0 wie die Kugel am LCD-Board. Eigenerwaermung: Der Sensor misst die Platine, waehrend einer Aufnahme bis 6,5 K ueber dem Raum. climateRoom() zieht sie ab, gemessen am 27.09.2026 (Raum konstant 21,7 C, 65 Werte ueber 32 min): dT(t) = 6,46 - 1,71 e^(-t/65 s) - 3,20 e^(-t/666 s) Die Feuchte wird ueber den Taupunkt auf die Raumtemperatur umgerechnet. Stand: Anzeige und Urteil am Geraet gesehen (Foto), kein hoerbares Knacken in der Aufnahme. Die Korrektur ist nur an der eigenen Messreihe angepasst, eine Gegenprobe steht aus. VORLAEUFIG drin: die Messreihe nach /klima.csv (Portal /klima.csv) -- wird nach der Gegenprobe entfernt. |
||
|---|---|---|
| bringup-lcd154 | ||
| case V1.0 | ||
| firmware 1.0 | ||
| geraete | ||
| tools | ||
| .gitignore | ||
| README.md | ||
| START HERE.pdf | ||
Pala Note
Dokumentation und Weiterentwicklungs-Basis für Pala Note von Paul Lagier — ein ESP32-S3-basiertes Sprachnotiz-Gerät mit E-Paper-Display: Aufnahme auf SD-Karte, Transkription im eigenen Netz, Anzeige/Verwaltung der Notizen auf dem Gerät.
Die Firmware bedient zwei Geräte aus einem Quellbaum. Was nur eines davon
betrifft — gemessene Hardware, Pins, Bedienung, Stromsparen — steht in
geraete/; hier steht, was für beide gilt.
Herkunft & Lizenz
- Design und Firmware v1.0 stammen von Paul Lagier (gekauftes Projekt).
- Offizielle, stets aktuelle Bauanleitung: https://paullagier.craft.me/PP8UjYeYGviIqj
- Lizenz laut Anbieter: nur für den persönlichen Gebrauch ("For personal use only"). Details dazu stehen im Abschnitt „Safety & License“ der Online-Anleitung — vor jeder Weitergabe oder Veröffentlichung dort nachlesen.
- Dieses Repo liegt privat auf eigener Infrastruktur (Forgejo hinter VPN) und dient der eigenen Dokumentation, Reparatur und Weiterentwicklung — keine Weitergabe der Originaldateien vorgesehen.
Die zwei Geräte
Seit 25.09.2026 liegen beide vor. Sie sind nicht austauschbar: gleicher SoC, gleiche Audio-Chips, gleiches SD-Interface — aber kein gemeinsamer Pin.
| ePaper v1.0 | Waveshare LCD-1.54 | |
|---|---|---|
| Rolle | das vorgesehene Zielgerät | Ersatz, um überhaupt entwickeln zu können |
| Profil | pio run -e epaper |
pio run -e lcd154 |
| SoC | ESP32-S3-PICO-1 (LGA56) | ESP32-S3 (QFN56) |
| Flash | 8 MB eingebettet | 16 MB |
| MAC | 70:04:1d:d8:20:cc |
28:84:85:91:c0:8c |
| Display | E-Paper 200×200 | ST7789 240×240 |
| Taster | zwei | drei |
| Extras | RTC, SHTC3, getrennte Abschaltpfade | Beleuchtung, Lagesensor, Power-Latch |
Beide melden sich als /dev/cu.usbmodem1101 — der Portname sagt nicht,
welches Gerät hängt. Unterschieden werden sie an der MAC, auszulesen mit
esptool flash-id. Das ist keine Kleinigkeit: ein mit dem falschen Profil
geflashtes Gerät kann Schaden nehmen (das ePaper-Profil treibt GPIO 42 und 17
als Ausgang; auf dem LCD-Board sind das I²C-SDA und SD-D0).
Ordnerstruktur
README.md dieses Dach: beide Geräte, Bauen, gemeinsame Funktionen
geraete/epaper.md Pala Note v1.0 — Messwerte, Eigenheiten, Stand
geraete/lcd154.md Waveshare LCD-1.54 — dito
firmware 1.0/pala_note/ Arduino-Sketch (.ino) + Board-Support-Code (src/)
firmware 1.0/werks-backup/ Werks-Firmware des ePaper-Geräts (nicht im Git)
bringup-lcd154/ eigenständiges Messprojekt für das LCD-Board
case V1.0/ Pauls 3D-Druckdateien (.3mf) und Assembled.step --
am Geraet nicht verbaut, es steckt im Waveshare-Gehaeuse
START HERE.pdf Anbieter-Einstiegsblatt mit Link zur Bauanleitung
secrets.h ist per .gitignore ausgeschlossen und wird nie committet. Vor dem
ersten Bauen secrets.h.example nach secrets.h kopieren und ausfüllen.
Darin stehen nur noch die WLAN-Daten, und auch die bloß als Rückfall: Im Betrieb werden WLAN (Settings → wifi) und WireGuard (Settings → vpn) am Gerät eingerichtet und liegen im NVS. Der OpenAI-Schlüssel ist entfallen — die Transkription läuft im eigenen Netz.
Board-Profile
Umgeschaltet wird an einer Stelle, in firmware 1.0/pala_note/config.h:
#define PALA_BOARD PALA_EPAPER // oder PALA_LCD154
Die Pins und Fähigkeiten stehen in boards/board_epaper.h und
boards/board_lcd154.h. Das ePaper-Profil ist Wert für Wert identisch mit dem
Auslieferungsstand — nachgeprüft, indem der Präprozessor beide Stände expandiert
und alle 48 Makros verglichen wurden.
Warnung: Ein mit dem falschen Profil geflashtes Gerät kann Schaden nehmen. Das ePaper-Profil treibt GPIO 42 und 17 als Ausgang; auf dem LCD-Board sind das I²C-SDA und SD-D0.
Bauen und Flashen
Die Firmware baut mit PlatformIO, ohne Arduino IDE — der Sketch-Ordner bleibt daneben unverändert als Arduino-Sketch nutzbar.
cd "firmware 1.0/pala_note"
pio run -e epaper # Pala Note v1.0 (E-Paper)
pio run -e lcd154 -t upload # Waveshare-Board, bauen und flashen
pio device monitor -b 115200
Das Environment setzt PALA_BOARD, in config.h muss dafür nichts geändert
werden.
Status
- ePaper-Gerät: geflasht und in Betrieb. Display, I²C, Codec, SD-Karte,
Aufnahme, Transkription (übers Heim-WLAN) und Portal sind am Gerät
bestätigt; der Tunnel mit einer echten Notiz noch nicht.
Offene Punkte in
geraete/epaper.md. - LCD-Board: die weiter entwickelte Basis — Bedienung, Statuszeile,
Portal, WireGuard, Whisper und Energiesparen laufen dort.
Siehe
geraete/lcd154.md. - Firmware und Gehäuse des Auslieferungsstands v1.0 bleiben inhaltlich unverändert erhalten; alles Neue kommt als Board-Profil-Schicht dazu.
Funktionen der Firmware
Alles in diesem Abschnitt gilt für beide Geräte. Was nur eines betrifft,
steht in geraete/.
Einstellungen: seitenweise
Mit sechs Einträgen wurden die Zeilen so flach, dass die Schrift kaum noch Luft
hatte. Die Liste zeigt jetzt vier je Seite; jeder weitere Eintrag öffnet
automatisch eine neue. Der Cursor läuft wie bisher zyklisch durch alle Einträge,
die Seite folgt ihm — die Seitenzahl steht in der Kopfzeile hinter dem Titel
(einstellungen 1/2). Weitere Einträge brauchen also keine Anpassung der
Geometrie mehr.
Sprache
Die Oberfläche war ein Mischmasch: Das Original war englisch, die späteren
Ergänzungen deutsch. Jetzt laufen alle Anzeigetexte über T() aus
src/app/lang.cpp — eine Tabelle je Sprache, Vorgabe Deutsch, umschaltbar
unter Settings → sprache und im NVS gemerkt.
Umlaute: Die GFX-Zeichensätze enthalten nur ASCII. Statt einen eigenen Satz
zu pflegen, bildet utf8ToInternal() die UTF-8-Folgen auf freie Steuercodes ab;
gezeichnet wird die Glyphe des Grundbuchstabens, die zwei Punkte kommen aus
drawGlyphFont() dazu — Größe und Abstand aus der Glyphenbreite, damit es in
jeder Schriftgröße stimmt. Das kostet kein Flash. Das scharfe S wird zu ss:
Es bräuchte eine eigene Glyphe und kommt in den Oberflächentexten nicht vor.
Nicht übersetzt werden bewusst: die Tags (Nutzerdaten, stehen so auf der
Karte und in jeder Notiz — übersetzte Tags würden bestehende Notizen von ihrem
Tag trennen), die Wortmarke „pala“, die Sprachkürzel de/en und die kurzen
Fehlermeldungen (NO WIFI, SD ERR …), die eher Diagnosecodes als Sätze sind.
Einen neuen Text aufzunehmen heißt: ID in lang.h ans Ende der Aufzählung
(vor S_COUNT), beide Fassungen in lang.cpp an dieselbe Stelle in beiden
Tabellen.
Ruhebildschirm: der Ring zeigt die Karte
Der große Ring zeigte den Ladezustand — den nennt inzwischen die Statuszeile. Jetzt zeigt er den freien Platz auf der SD-Karte: Der Bogen läuft im Uhrzeigersinn und wird kürzer, je weniger frei ist — voller Ring heißt leere Karte. Bewusst ohne Zahlenwert; der Ring ist die Anzeige.
Der Wert wird gepuffert: usedBytes() liest die Belegungstabelle und dauert auf
großen Karten spürbar, das gehört nicht in jeden Bildschirmaufbau.
WLAN einrichten am Gerät
Bis hierher waren SSID und Passwort fest einkompiliert (secrets.h) — das Gerät
lief also nur dort, wo es gebaut wurde. Jetzt: Settings → wifi.
Das Gerät sucht die Netze in Reichweite und zeigt sie nach Signalstärke sortiert; ein Stern markiert gesicherte Netze. Nach der Auswahl:
- offenes Netz oder Passwort schon gespeichert → verbindet sofort.
- sonst → das Gerät macht für die Dauer der Einrichtung einen eigenen
Access Point
PalaNote-Setupauf. Telefon damit verbinden, im Browser192.168.4.1öffnen, Passwort eintippen. Das Gerät speichert es im NVS, schaltet den Access Point ab und verbindet sich.
Warum das Passwort nicht am Gerät eingegeben wird: Mit drei Tasten bliebe nur, sich durch ein Alphabet zu blättern. Bei ~70 möglichen Zeichen und einem üblichen Passwort sind das mehrere hundert Tastendrücke — für etwas, das man bei jedem Ortswechsel neu braucht. Der Umweg übers Telefon dauert zwanzig Sekunden.
Gespeichert wird ein Netz (Preferences, Namespace palawifi). Solange nichts
gespeichert ist, greifen weiterhin die Werte aus secrets.h — bestehende
Installationen ändern ihr Verhalten also nicht.
Notizportal — im WLAN oder als eigener Access Point
Settings → transfer startet die Weboberfläche: Notizen lesen, Tags vergeben und löschen, Aufnahmen anhören, alles als Text exportieren.
Das Gerät versucht zuerst das bekannte WLAN. Findet es keines — unterwegs der Normalfall —, macht es einen eigenen Access Point auf, statt abzubrechen:
| Netz | PalaNote (TRANSFER_AP_NAME) |
| Passwort | notizbuch (TRANSFER_AP_PASS) |
| Adresse | 192.168.4.1 |
Die mittlere Taste schaltet zwischen beiden um, ohne das Portal zu verlassen. Im WLAN bleibt das Tablet nebenbei online, als Access Point funktioniert es überall.
Das AP-Passwort steht im Quelltext und auf dem Bildschirm. Es schützt nicht vor jemandem, der das Gerät in der Hand hält — es verhindert nur, dass ein offenes Netz mit den eigenen Notizen in der Gegend steht.
iOS-Eigenheit: iPads verlassen Netze ohne Internetzugang mitunter von selbst. Falls die Verbindung abreißt, im WLAN-Menü beim Heimnetz „Automatisch verbinden“ vorübergehend abschalten.
Transkription im eigenen Netz
v1.0 schickte die Aufnahmen an die OpenAI-Whisper-API. Jetzt gehen sie an einen
eigenen whisper.cpp-Dienst auf der DS1621 (10.47.11.10:8095, Endpunkt
/inference) — im Heimnetz direkt, unterwegs durch den WireGuard-Tunnel. Damit
verlässt keine Sprachnotiz mehr das eigene Netz, und es braucht keinen
API-Schlüssel im Gerät.
Der Dienst antwortet in derselben Form wie OpenAI ({"text": "..."}), deshalb
blieb die Auswertung unverändert. Unterschiede: kein TLS (eigenes Netz), und
statt eines Modellnamens die Felder response_format und language — welches
Modell geladen ist, entscheidet der Dienst.
Am Dienst gemessen (ggml-small-q5_1): Die Dauer hängt nicht an der Audiolänge, sondern an der Zahl der 30-Sekunden-Fenster. Die Steigung ist allerdings steiler als am 11.09.2026 angenommen — nachgemessen am 26.09.2026 mit erzeugten Tönen:
| Audio | Fenster | Dauer | je Fenster |
|---|---|---|---|
| Testton 1 s | 1 | 19,2 / 21,2 / 18,6 s | ~20 s |
| Testton 90 s | 3 | 60,5 s | 20,2 s |
| echte Notiz 0,6 s | 1 | 25,8 s | 25,8 s |
| echte Notiz 2,2 s | 1 | 34,9 s | 34,9 s |
| dichte Sprache 241 s | 8 | 325,8 s | 40,7 s |
Der Inhalt entscheidet, nicht nur die Fensterzahl. Ein Ton ohne Sprache kostet rund 20 s je Fenster, dichte Sprache ohne Pausen das Doppelte. Die frühere Angabe „alle rund 12–13 s“ blieb an kurzen Proben hängen.
Daraus folgt die Zeitgrenze: REC_MAX_MS erlaubt zehn Minuten je Abschnitt,
das sind zwanzig Fenster und damit rund 814 s. WHISPER_TIMEOUT_MS steht
deshalb auf 1200 s — vorher 240 s, was eine Notiz in voller Länge
unbemerkt hätte scheitern lassen.
Offene Frage, die die Messung aufwirft: Eine Notiz in voller Länge macht
das Gerät rund 14 Minuten unbedienbar, weil autoSyncTick() die Schleife
blockiert. Ob REC_MAX_MS deshalb kürzer sein sollte, ist noch nicht
entschieden.
Über WHISPER_LOCAL 0 in config.h lässt sich der alte Weg zu OpenAI
reaktivieren.
Funk und Tunnel: bei Bedarf, nicht auf Vorrat
Das Gerät ist im Leerlauf nicht im WLAN — Funk ist nach dem Display der größte Verbraucher. Es schaltet ihn ein, wenn er gebraucht wird: beim Auto-Sync, beim Notizportal, beim Zeitabgleich.
Solange WLAN steht, hält autoVpnTick() den Tunnel selbsttätig aufrecht; ohne
WLAN wird der Funk dafür nicht eigens geweckt. Beide Abgleichswege — der
automatische wie der über Menü → abgleich — bauen den Tunnel vor dem
Transkribieren auf. Vor dem Transkribieren wird
der Tunnel sichergestellt, weil der Whisper-Dienst unterwegs nur darüber
erreichbar ist.
Damit ist das Schloss die meiste Zeit offen — nicht weil etwas kaputt wäre, sondern weil nichts läuft. Deshalb zeigt die Statuszeile in diesem Fall ein Zz an Stelle von Funkbalken und Schloss: ruhend, nicht gestört.
WireGuard
Settings → vpn. Da für den Tunnel ohnehin WLAN stehen muss, läuft das
Einrichtungsformular im WLAN statt über einen eigenen Access Point — das Gerät
zeigt seine IP, das Telefon muss das Netz nicht wechseln. Endpunkt, Port,
eigene VPN-Adresse und der öffentliche Schlüssel des Servers sind vorbelegt
(config.h, nichts davon ist geheim); einzutippen sind nur privater Schlüssel
und Preshared Key. Beide landen im NVS, nie im Quelltext.
Ist bereits ein Schlüssel hinterlegt, verbindet das Gerät direkt, ohne das Formular zu zeigen. Das Schloss in der Statuszeile schließt sich, sobald der Tunnel steht.
Die Bibliothek liegt als Kopie unter lib/WireGuard-ESP32/ statt in
lib_deps — ihr Arduino-Wrapper setzt den Preshared Key hart auf NULL,
obwohl der Kern ihn unterstützt. Begründung und die eigenen Änderungen stehen
in lib/WireGuard-ESP32/HERKUNFT.md.
Kosten im Speicher, am Board gemessen: +1,0 KB RAM, +26,3 KB Flash.
Der Tunnel steht (verifiziert 11.09.2026, Handshake gegen den realen Peer).
Die Probe zielt auf 10.13.13.1 im VPN-Subnetz und wertet die Antwortzeit aus:
Eine sofortige Ablehnung beweist, dass Pakete hin und zurück laufen — auch wenn
dort kein Dienst lauscht. Ein Ziel im Heimnetz taugt dafür nicht, weil ein
direkt verbundenes Netz die Standardroute durch den Tunnel schlägt.
Drei Fallen lagen auf dem Weg, alle in lib/WireGuard-ESP32/HERKUNFT.md bzw.
im Projekt-Memory festgehalten: ein Absturz der Bibliothek (Zufallsgenerator zu
spät eingerichtet), eine Gegenprobe, die sich selbst betrog, und ein fehlendes
[Peer] in wg0.conf, das zwei Server-Einträge verschmolz.
Automatisches Transkribieren
Einen Stapel Notizen von Hand zu synchronisieren dauert Minuten und blockiert solange die Bedienung. Deshalb arbeitet das Gerät sie im Ruhezustand selbst ab — eine je Durchgang, nach 30 s ohne Eingabe. Ein Durchgang dauert rund 13 s, danach ist das Gerät wieder frei; die nächste Notiz folgt im nächsten Durchlauf. Nach einem Fehlschlag (Dienst weg, kein WLAN) legt es fünf Minuten Pause ein.
Der Sync wartet auf eine eigene Zeitmarke (lastInputMs), die nur echte
Tastendrücke zurücksetzen — lastActivityMs wird vom Abdunkeln zurückgesetzt
und hätte den Sync dauerhaft ausgebremst.
Schalter: AUTO_SYNC in config.h.
Noch offen:
- Laufzeit messen. Bisher nur geschätzt.