UI-Bibliothek

Reine Rendering-Bibliothek für OSSM-Bildschirme – Textseiten, Zeichenketten, Symbole und visuelle Tests

Überblick

Die lib/ui-Bibliothek ist eine reine Rendering-Ebene, die OSSM-Bildschirme in einen U8g2-Puffer zeichnet. Sie hat keine Hardwareabhängigkeiten, kein FreeRTOS und keine Mutexe – sie benötigt lediglich einen u8g2_t*-Zeiger und zeichnet Pixel. Thread-Sicherheit und Anzeige-E/A werden vom Anzeigedienst verwaltet.

Diese Trennung bedeutet, dass die Bibliothek nativ (x86) für automatisierte visuelle Tests ohne ESP32 ausgeführt werden kann.

Abhängigkeiten: U8g2, QRCode

Quelle: Software/lib/ui/src/

TextPage

TextPage ist die primäre Struktur für inhaltsgesteuerte Bildschirme. Sie umfasst Hilfeseiten, Fehlerbildschirme, Aktualisierungsstatus, WiFi-Einrichtung, Kopplung – alles, was hauptsächlich aus Text mit optionalen QR-Codes besteht.

struct TextPage {
    const char* title      = nullptr;
    const char* subtitle   = nullptr;
    const char* body       = nullptr;
    const char* bottomText = nullptr;
    const char* qrUrl      = nullptr;
    uint8_t qrVersion      = 3;
    int qrScale            = 2;
    bool centerBody        = false;
    int scrollPercent      = -1;
};

Alle Felder sind optional. Stellen Sie nur das ein, was Sie brauchen.

FeldTypZweck
titleconst char*Fetter Text oben, mit einer Trennlinie unten
subtitleconst char*Mittlerer oder fetter Text unter dem Titel; automatische Aufteilung, wenn zu breit
bodyconst char*Text mit automatischem Zeilenumbruch; unterstützt \n als Zeilenumbruch
bottomTextconst char*Am unteren Bildschirmrand angeheftet (y=62)
qrUrlconst char*Unten rechts gerenderter QR-Code; schränkt die Textbreite ein
qrVersionuint8_tQR-Version (Standard 3)
qrScaleintQR-Pixelskala (Standard 2)
centerBodyboolTextkörper zentrieren
scrollPercentintBildlaufanzeige anzeigen (0–100); -1 zum Ausblenden

Rendering-Reihenfolge

drawTextPage() rendert in dieser Reihenfolge:

  1. Gesamten Bildschirm löschen (Kopfzeile + Seite + Fußzeile)
  2. QR-Code – untere rechte Ecke, reduziert die verfügbare Textbreite
  3. Titel – Fettschrift, gefolgt von einer horizontalen Trennlinie
  4. Untertitel – versucht zuerst die Medium-Schriftart; fällt auf Fettdruck zurück; wird auf zwei Zeilen aufgeteilt, wenn er immer noch zu breit ist
  5. Körper – mit drawWrappedText() umbrochen, wenn ein Titel vorhanden ist; zentrierter Titelstil (drawStr::title), wenn der Text das einzige Feld ist
  6. Unterer Text – auf y=62 fixiert
  7. Bildlaufanzeige – Bildlaufleiste am rechten Rand, wenn scrollPercent >= 0

Verwendung

// Use a predefined page
ui::drawTextPage(&u8g2, ui::pages::helpPage);

// Or build one inline
ui::TextPage page;
page.title = "Custom Title";
page.body = "Some content\nwith line breaks";
page.scrollPercent = 50;
ui::drawTextPage(&u8g2, page);

Vordefinierte Seiten

TextPages.h definiert statische TextPage-Instanzen im ui::pages-Namespace. Diese referenzieren Zeichenketten aus Strings.h:

SeiteVerwendete Felder
helpPagetitle, body, bottomText, qrUrl
updateCheckingPagebody
noUpdatePagebody, bottomText
updatingPagetitle, body
errorPagetitle
wifiDisconnectedPagetitle, body, bottomText, qrUrl
wifiConnectedPagetitle, body, bottomText
pairingPagetitle, body

Beispieldefinition:

static const TextPage helpPage = {
    .title = helpTitle,
    .body = helpBody,
    .bottomText = helpBottom,
    .qrUrl = helpQr,
};

Zeichenketten

Alle UI-Zeichenketten befinden sich in Strings.h unter dem Namespace ui::strings. Sie werden mit PROGMEM im Flash gespeichert:

static const char helpTitle[] PROGMEM = "Get Help";
static const char helpBody[] PROGMEM = "On Discord,\nor GitHub";

Progmem.h definiert PROGMEM als No-Op auf Nicht-Arduino-Plattformen, sodass die Zeichenketten sowohl auf ESP32 als auch auf nativen Test-Builds kompiliert werden.

Kategorien

KategorieBeispiele
BrandingresearchAndDesire, kinkyMakers
Allgemeine Benutzeroberflächeerror, homing, idle, restart, settings, skip
Menü / ModisimplePenetration, strokeEngine, streaming, pairing, update, wifi
Bewegungssteuerungspeed, stroke, sensation, depth, buffer, accel, max
WarnungenspeedWarning, homingTookTooLong, strokeTooShort
HilfehelpTitle, helpBody, helpBottom, helpQr
AktualisierungupdateChecking, noUpdateBody, updatingTitle, updatingBody
WiFiwifiSetup, wifiBody, wifiBottom, wifiQr, wifiConnected
KopplungpairingTitle, pairingBody
MusterpatternName0patternName6, patternDesc0patternDesc6

Musterarrays

Namen und Beschreibungen der Stroke Engine-Muster sind indizierte Arrays:

static const char* const strokeEngineNames[7] = {
    patternName0, patternName1, patternName2,
    patternName3, patternName4, patternName5,
    patternName6,
};

static const char* const strokeEngineDescriptions[7] = {
    patternDesc0, patternDesc1, patternDesc2,
    patternDesc3, patternDesc4, patternDesc5,
    patternDesc6,
};

Menüpunkte

MenuItems.h ordnet Menu::-Enumerationswerte den Anzeigezeichenfolgen zu:

static const char* menuStrings[Menu::NUM_OPTIONS] = {
    ui::strings::simplePenetration, ui::strings::strokeEngine,
    ui::strings::streaming,         ui::strings::pairing,
    ui::strings::update,            ui::strings::wifiSetup,
    ui::strings::helpTitle,         ui::strings::restart,
};

Bilder und Symbole

Symbole (Schriftzeichen)

Statussymbole verwenden die Symbolschriftart Siji. Glyphen werden in DisplayConstants.h definiert:

namespace IconGlyph {
    constexpr uint16_t WIFI_OFF        = 0xe218;
    constexpr uint16_t WIFI_CONNECTING = 0xe219;
    constexpr uint16_t WIFI_CONNECTED  = 0xe21a;
    constexpr uint16_t WIFI_ERROR      = 0xe21b;
    constexpr uint16_t BLE_CONNECTED   = 0xe00b;
    constexpr uint16_t BLE_SMALL       = 0xe0b0;
    constexpr uint16_t EXCLAMATION     = 0xe0b3;
}

drawHeaderIcons() zeigt den WiFi- und BLE-Status in der oberen rechten Ecke des Bildschirms an. Fehlerzustände überlagern die Basisglyphe mit zusätzlichen Pixeln (Ausrufezeichen).

Logos (XBM-Bitmaps)

Logos sind PROGMEM XBM-Byte-Arrays in Logos.h:

  • RDLogo – Research & Desire, 57x50 Pixel
  • KMLogo – Kinky Makers, 50x50 Pixel

Sie werden über die Struktur LogoData und drawLogo() gezeichnet:

struct LogoData {
    const char* title;
    const uint8_t* bitmap;
    int w, h, x, y;
};

Hello-Animation

HelloAnimation.h enthält vorberechnete Frames mit Y-Positionen für die Boot-Animation „OSSM“. Jeder Frame gibt Y-Offsets pro Buchstabe an:

struct HelloFrame {
    int heights[4];  // Y position for O, S, S, M
};

drawHelloFrame() rendert einen einzelnen Frame. Die Testsuite fügt alle Frames zu einem GIF zusammen.

Andere Zeichenfunktionen

Die vollständige Rendering-API (ui.h):

FunktionZweck
drawTextPage()Eine TextPage-Struktur rendern
drawMenu()Scrollbares Menü mit Auswahlhervorhebung
drawPlayControls()Geschwindigkeits-/Hub-/Empfindungsbalken für Betriebsmodi
drawPreflight()Sicherheitsüberprüfung vor der Bewegung (Geschwindigkeitsknopf auf Null)
drawHeaderIcons()WiFi- und BLE-Statussymbole
drawQR()Eigenständiger QR-Code
drawHelloFrame()Einzelner Boot-Animationsrahmen
drawLogo()XBM-Logo mit Titel

Hilfs-Namespaces in DrawExtensions.h:

  • drawStr::centered() – horizontal zentrierter Text
  • drawStr::multiLine() – mehrzeiliger Text mit Zeilenumbruch und UTF-8-Unterstützung
  • drawStr::title() – Fett zentrierter Text an fester Position
  • drawShape::scroll() – Bildlaufleistenanzeige
  • drawShape::settingBar() – beschrifteter vertikaler Balken mit Füllstand
  • drawShape::settingBarSmall() – kompakter vertikaler Balken (keine Beschriftung)

Testen und visuelle Ausgabe

Die Anzeigebibliothek verfügt über native Tests, die jede Bildschirmvariante rendern und Bilder zur visuellen Überprüfung exportieren.

Tests ausführen

pio test -e test -f test_display

Für die PNG-Konvertierung muss ImageMagick (magick) installiert sein. Ohne sie werden weiterhin PBM-Dateien generiert, PNGs werden jedoch übersprungen.

Wie es funktioniert

  1. Tests erstellen eine rein softwarebasierte U8g2-SSD1306-128x64-Anzeige (keine Hardware, No-op-I2C)
  2. Jeder Test ruft ui::draw*()-Funktionen auf, um in den Puffer zu rendern
  3. savePBM() schreibt den Puffer als PBM P4-Bitmap
  4. Nachdem alle Tests abgeschlossen sind, konvertiert ImageMagick PBM in PNG (invertierte Farben, 400 % Skalierung).
  5. Hello-Animationsframes werden zu einem GIF zusammengesetzt

Ausgabestruktur

test/test_display/_output/
├── pbm/                        # Raw bitmaps (generated by tests)
│   ├── textpage/
│   │   ├── combos/             # Field combinations (title+body, all fields, etc.)
│   │   ├── scroll/             # Scroll positions (0%, 50%, 100%)
│   │   ├── stress/             # Edge cases (UTF-8, long strings)
│   │   ├── qr/                 # QR code variants
│   │   ├── body/               # Body rendering with/without title
│   │   ├── subtitle/           # Subtitle font sizing
│   │   ├── pages/              # All predefined pages
│   │   └── patterns/           # Stroke engine pattern descriptions
│   ├── header_icons/           # WiFi × BLE status combinations
│   ├── logos/                  # RD logo, KM logo
│   ├── hello/                  # Animation frames
│   ├── menu/                   # Menu rendering variants
│   └── play_controls/          # Preflight, play modes
└── png/                        # Same structure, converted to PNG

Testabdeckung

TestdateiWas es abdeckt
test_textpage.cppTextPage-Feldkombinationen, Stresstests, QR, Untertitelüberlauf, Textlayout
test_pages.cppAlle vordefinierten Seiten, Beschreibungen der Stroke Engine-Muster
test_scroll.cppBildlaufleiste bei Grenz- und Randfallwerten
test_header_icons.cppAlle 20 WiFi × BLE-Statuskombinationen
test_logos.cppRD-Logo, KM-Logo
test_hello.cppAlle Hello-Animationsframes, GIF-Generierung
test_menu.cppMenü mit verschiedenen Elementanzahlen, langem Text und Zeilenumbrüchen
test_play_controls.cppSteuerung für Preflight, Simple Penetration, Stroke Engine und Streaming

Auf dieser Seite