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_stateZum Beispiel:
"menu.idle"_s + buttonPress[(isOption(Menu::SimplePenetration))] = "simplePenetration"_sDies 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.
| Ereignis | Beschreibung | Typische Quelle |
|---|---|---|
ButtonPress | Ein Klick auf eine einzelne Taste | Drehgeber-Taste |
LongPress | Taste für längere Zeit gedrückt gehalten | Drehgebertaste (gehalten) |
DoublePress | Zwei schnelle Tastenklicks | Drehgeber-Taste |
Done | Asynchroner Vorgang abgeschlossen | Homing-Aufgabe, Preflight-Prüfung |
Error | Der Vorgang ist fehlgeschlagen | Homing-Fehler, Hub zu kurz |
EmergencyStop | Sofortiger Stopp erforderlich | Sicherheitssysteme |
Home | Homing-Sequenz anfordern | Externer 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.
| Guard | Beschreibung | Gibt true zurück, wenn ... |
|---|---|---|
isOnline | WiFi-Verbindung prüfen | Das Gerät mit WiFi verbunden ist |
isUpdateAvailable | Auf Firmware-Updates prüfen | Der Server eine neuere verfügbare Version meldet |
isStrokeTooShort | Homing-Ergebnis validieren | Der gemessene Hub unter dem Mindestschwellenwert liegt |
isOption(Menu) | Ausgewählten Menüpunkt prüfen | Die aktuelle Menüauswahl der angegebenen Option entspricht |
isPreflightSafe | Position des Geschwindigkeitsreglers prüfen | Das Geschwindigkeitspotentiometer im toten Bereich liegt (sicherer Start) |
isFirstHomed | Einmalige Prüfung des ersten Homings | Dies das erste erfolgreiche Homing seit dem Booten ist |
isNotHomed | Homing-Status prüfen | Das 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
| Aktion | Beschreibung |
|---|---|
drawHello | Begrüßungs-/Startbildschirm anzeigen |
(drawMenu) | Hauptmenü rendern |
drawPlayControls | Geschwindigkeits-/Hub-/Tiefensteuerung anzeigen |
drawPatternControls | Musterauswahl-Benutzeroberfläche anzeigen |
drawPreflight | Warnung „Geschwindigkeit zum Starten reduzieren“ anzeigen |
drawHelp | Hilfe-/Supportinformationen anzeigen |
drawWiFi | WiFi-Konfigurationsbildschirm anzeigen |
drawUpdate | Bildschirm „Nach Updates suchen“ anzeigen |
drawNoUpdate | Meldung „Firmware ist aktuell“ anzeigen |
drawUpdating | Update-Fortschritt anzeigen |
drawError | Fehlerstatus mit Meldung anzeigen |
Bewegungsaktionen
| Aktion | Beschreibung |
|---|---|
startHoming | Homing-Sequenz starten |
clearHoming | Referenzierungsstatusvariablen zurücksetzen |
startSimplePenetration | Aufgabe im Modus Simple Penetration starten |
startStrokeEngine | Aufgabe im Modus Stroke Engine starten |
startStreaming | Aufgabe im Modus Streaming starten |
emergencyStop | Motor zwangsweise stoppen und Ausgänge deaktivieren |
Einstellungen-Aktionen
| Aktion | Beschreibung |
|---|---|
resetSettingsStrokeEngine | Standardwerte für Stroke Engine initialisieren (Geschwindigkeit=0, Hub=50, Tiefe=10, Empfindung=50) |
resetSettingsSimplePen | Standardwerte für Simple Penetration initialisieren (Geschwindigkeit=0, Hub=0, Tiefe=50) |
incrementControl | Steuerparameter durchlaufen (Hub → Tiefe → Empfindung) |
setHomed | Gerät als erfolgreich gehomt markieren |
setNotHomed | Homing ungültig machen (vor der Wiedergabe ist ein erneutes Homing erforderlich) |
Systemaktionen
| Aktion | Beschreibung |
|---|---|
restart | ESP32 neu starten |
resetWiFi | Gespeicherte WiFi-Anmeldeinformationen löschen |
updateOSSM | Firmware-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
Ordnerstruktur
Verstehen Sie die Organisation und Designphilosophie des Quellcodes.
Boost.SML-Tutorial
Schritt-für-Schritt-Anleitung zum Erstellen von Zustandsmaschinen mit Boost.SML.
Boost.SML-Benutzerhandbuch
Vollständige Referenz für alle Boost.SML-Funktionen.
Betriebsmodi
Erfahren Sie mehr über die Modi „Simple Penetration“, „Stroke Engine“ und „Streaming“.