Zustandsmaschine

Architektur der Zustandsmaschine der OSSM-Firmware verstehen

Architektur der Zustandsmaschine

Die OSSM-Firmware verwendet eine deklarative Zustandsmaschine, um das Geräteverhalten zu verwalten. Dies gewährleistet vorhersehbare Übergänge zwischen den Betriebsmodi und verhindert ungültige Zustände, die zu unerwartetem Verhalten führen könnten.

Zustandsdiagramm

Das folgende Diagramm zeigt alle Zustände und Übergänge in der OSSM-Zustandsmaschine:

Designübersicht

Die Zustandsmaschine wird mithilfe von Boost.SML (State Machine Language) implementiert, einer C++14-Bibliothek, die ausschließlich aus Headern besteht und eine domänenspezifische Sprache zum Definieren von Zustandsmaschinen bereitstellt.

Warum Boost.SML?

Boost.SML wurde für OSSM ausgewählt, weil es Folgendes bietet:

  • Überprüfung zur Kompilierungszeit – Ungültige Übergänge werden zur Kompilierungszeit und nicht zur Laufzeit abgefangen
  • Kein Laufzeit-Overhead – Leistung entspricht handgeschriebener switch/case-Logik
  • Deklarative Syntax – Die Übergangstabelle liest sich wie eine Dokumentation
  • Thread-Sicherheit – Integrierte Unterstützung für gleichzeitigen Zugriff über Richtlinien
  • Geringer Platzbedarf – Einzelner Header, ca. 2000 Codezeilen, keine Abhängigkeiten

Syntax der Übergangstabelle

Jede Zeile in der Übergangstabelle folgt diesem Muster:

source_state + event [guard] / action = target_state

Zum Beispiel:

"menu.idle"_s + buttonPress[(isOption(Menu::SimplePenetration))] = "simplePenetration"_s

Dies bedeutet: Wenn im Zustand menu.idle ein buttonPress-Ereignis auftritt und der Guard isOption(Menu::SimplePenetration) true zurückgibt, wird in den Zustand simplePenetration gewechselt.

Eine vollständige Anleitung zur Übergangstabellensyntax finden Sie im Boost.SML-Tutorial.

Ereignisse

Ereignisse lösen Zustandsübergänge aus. Es handelt sich um einfache Strukturen, die in Events.h definiert sind und von überall in der Codebasis ausgelöst werden können.

EreignisBeschreibungTypische Quelle
ButtonPressEin Klick auf eine einzelne TasteDrehgeber-Taste
LongPressTaste für längere Zeit gedrückt gehaltenDrehgebertaste (gehalten)
DoublePressZwei schnelle TastenklicksDrehgeber-Taste
DoneAsynchroner Vorgang abgeschlossenHoming-Aufgabe, Preflight-Prüfung
ErrorDer Vorgang ist fehlgeschlagenHoming-Fehler, Hub zu kurz
EmergencyStopSofortiger Stopp erforderlichSicherheitssysteme
HomeHoming-Sequenz anfordernExterner Befehl

Ereignisse auslösen

Ereignisse werden mithilfe der globalen stateMachine-Instanz ausgelöst:

#include "ossm/state/state.h"

// From anywhere in the codebase
stateMachine->process_event(ButtonPress{});
stateMachine->process_event(Done{});

Die Zustandsmaschine wird beim Start in state.cpp initialisiert und als globaler Zeiger bereitgestellt.

Ereignisse werden als leere Strukturen definiert. Der Typ selbst trägt die Bedeutung – es sind keine Nutzdaten erforderlich.

Guards

Guards sind bedingte Prüfungen, die bestimmen, ob ein Übergang stattfinden soll. Sie geben true zurück, um den Übergang zuzulassen, oder false, um ihn zu blockieren.

GuardBeschreibungGibt true zurück, wenn ...
isOnlineWiFi-Verbindung prüfenDas Gerät mit WiFi verbunden ist
isUpdateAvailableAuf Firmware-Updates prüfenDer Server eine neuere verfügbare Version meldet
isStrokeTooShortHoming-Ergebnis validierenDer gemessene Hub unter dem Mindestschwellenwert liegt
isOption(Menu)Ausgewählten Menüpunkt prüfenDie aktuelle Menüauswahl der angegebenen Option entspricht
isPreflightSafePosition des Geschwindigkeitsreglers prüfenDas Geschwindigkeitspotentiometer im toten Bereich liegt (sicherer Start)
isFirstHomedEinmalige Prüfung des ersten HomingsDies das erste erfolgreiche Homing seit dem Booten ist
isNotHomedHoming-Status prüfenDas Gerät nicht gehomt wurde oder das Homing ungültig gemacht wurde

Implementierung von Guards

Guards werden als constexpr-Lambdas im guards-Namespace definiert. Sie rufen vorwärts deklarierte Implementierungsfunktionen auf, um den Header schlank zu halten:

// In guards.h - forward declaration
bool ossmIsPreflightSafe();

namespace guards {
    // Constexpr lambda wraps the implementation
    constexpr auto isPreflightSafe = []() {
        return ossmIsPreflightSafe();
    };

    // Parameterized guard using nested lambda
    constexpr auto isOption = [](Menu option) {
        return [option]() { return ossmGetMenuOption() == option; };
    };
}

Die eigentliche Logik befindet sich in guards.cpp, wodurch die Kompilierungszeiten kurz bleiben und Implementierungsänderungen möglich sind, ohne dass die Übergangstabelle neu kompiliert werden muss.

Weitere Informationen zu Guard-Mustern finden Sie in der Boost.SML-Guard-Dokumentation.

Aktionen

Aktionen sind Funktionen, die bei Zustandsübergängen ausgeführt werden. Sie führen zu Nebeneffekten wie der Aktualisierung der Anzeige, dem Starten von Motoren oder dem Zurücksetzen von Einstellungen.

Anzeigeaktionen

AktionBeschreibung
drawHelloBegrüßungs-/Startbildschirm anzeigen
(drawMenu)Hauptmenü rendern
drawPlayControlsGeschwindigkeits-/Hub-/Tiefensteuerung anzeigen
drawPatternControlsMusterauswahl-Benutzeroberfläche anzeigen
drawPreflightWarnung „Geschwindigkeit zum Starten reduzieren“ anzeigen
drawHelpHilfe-/Supportinformationen anzeigen
drawWiFiWiFi-Konfigurationsbildschirm anzeigen
drawUpdateBildschirm „Nach Updates suchen“ anzeigen
drawNoUpdateMeldung „Firmware ist aktuell“ anzeigen
drawUpdatingUpdate-Fortschritt anzeigen
drawErrorFehlerstatus mit Meldung anzeigen

Bewegungsaktionen

AktionBeschreibung
startHomingHoming-Sequenz starten
clearHomingReferenzierungsstatusvariablen zurücksetzen
startSimplePenetrationAufgabe im Modus Simple Penetration starten
startStrokeEngineAufgabe im Modus Stroke Engine starten
startStreamingAufgabe im Modus Streaming starten
emergencyStopMotor zwangsweise stoppen und Ausgänge deaktivieren

Einstellungen-Aktionen

AktionBeschreibung
resetSettingsStrokeEngineStandardwerte für Stroke Engine initialisieren (Geschwindigkeit=0, Hub=50, Tiefe=10, Empfindung=50)
resetSettingsSimplePenStandardwerte für Simple Penetration initialisieren (Geschwindigkeit=0, Hub=0, Tiefe=50)
incrementControlSteuerparameter durchlaufen (Hub → Tiefe → Empfindung)
setHomedGerät als erfolgreich gehomt markieren
setNotHomedHoming ungültig machen (vor der Wiedergabe ist ein erneutes Homing erforderlich)

Systemaktionen

AktionBeschreibung
restartESP32 neu starten
resetWiFiGespeicherte WiFi-Anmeldeinformationen löschen
updateOSSMFirmware-Update herunterladen und installieren

Implementierung von Aktionen

Aktionen werden als constexpr-Lambdas im actions-Namespace definiert. Wie Guards delegieren sie an vorwärts deklarierte Implementierungsfunktionen:

// In actions.h - forward declarations
void ossmDrawHello();
void ossmEmergencyStop();

namespace actions {
    constexpr auto drawHello = []() { ossmDrawHello(); };
    constexpr auto emergencyStop = []() { ossmEmergencyStop(); };
}

Die Implementierungsfunktionen in actions.cpp rufen die entsprechenden Funktionsmodule auf:

// In actions.cpp
void ossmEmergencyStop() {
    stepper->forceStop();
    stepper->disableOutputs();
}

void ossmDrawHello() {
    pages::drawHello();  // Delegates to pages namespace
}

Dieses Muster hält die Zustandsmaschine von bestimmten Implementierungen entkoppelt und ermöglicht die unabhängige Entwicklung von Funktionsmodulen.

Aktionen dürfen den Hauptthread nicht blockieren. Lang laufende Vorgänge sollten stattdessen FreeRTOS-Aufgaben starten.

Weitere Informationen zu Aktionsmustern finden Sie in der Dokumentation zu Boost.SML-Aktionen.

Thread-Sicherheit

Die OSSM-Zustandsmaschine ist mit Thread-Sicherheitsrichtlinien konfiguriert, um Ereignisse aus mehreren FreeRTOS-Aufgaben zu verarbeiten:

sml::sm<OSSMStateMachine, sml::thread_safe<ESP32RecursiveMutex>, sml::logger<StateLogger>>

Dabei wird ein rekursiver Mutex verwendet, um Zustandsübergänge zu schützen und eine sichere Ereignisverteilung aus folgenden Kontexten zu ermöglichen:

  • Hauptschleife
  • Tasten-Interrupt-Handler
  • BLE-Befehlshandler
  • Hintergrundaufgaben

Die Zustandsmaschine wird in state.cpp initialisiert:

// Global state machine instance
StateMachine* stateMachine = nullptr;

void initStateMachine() {
    stateMachine = new StateMachine{};
    stateMachine->process_event(Done{});  // Trigger initial transition
}

Weitere Informationen zu Thread-Sicherheitsrichtlinien finden Sie in der Dokumentation zu Boost.SML-Policies.

Zustandsprotokollierung

Die Firmware enthält einen StateLogger, der alle Zustandsübergänge zum Debuggen protokolliert:

sml::logger<StateLogger>

Dadurch werden Übergangsinformationen über die ESP-IDF-Protokollierung ausgegeben, sodass das Verhalten der Zustandsmaschine während der Entwicklung leicht verfolgt werden kann.

Architekturnotizen

Die Zustandsmaschine folgt einer entkoppelten modularen Architektur:

  • Zustandsmaschinendefinition (machine.h) enthält nur die Übergangstabelle
  • Aktionen und Guards sind constexpr-Lambdas, die an Implementierungsfunktionen delegieren
  • Funktionsmodule (wie pages::, simple_penetration::, stroke_engine::) enthalten die eigentliche Logik
  • Globale Zustandsstrukturen verwalten den Anwendungsstatus anstelle von Klassenmitgliedern

Die OSSM-Klasse in ossm/OSSM.h wird aus Gründen der Abwärtskompatibilität mit der BLE-Befehlsverarbeitung beibehalten. Neue Funktionen sollten zustandslose Namespace-Funktionen verwenden, die auf dem globalen Zustand basieren.

Eine vollständige Übersicht über die Quellcodeorganisation finden Sie unter Ordnerstruktur.

Weiterführende Literatur

Auf dieser Seite