Toestandsmachine

Inzicht in de architectuur van de toestandsmachine in de OSSM-firmware

Architectuur van de toestandsmachine

De OSSM-firmware gebruikt een declaratieve toestandsmachine om het gedrag van het apparaat te beheren. Dit zorgt voor voorspelbare overgangen tussen bedieningsmodi en voorkomt ongeldige toestanden die onverwacht gedrag kunnen veroorzaken.

Toestandsdiagram

Het volgende diagram toont alle toestanden en overgangen in de OSSM-toestandsmachine:

Ontwerpoverzicht

De toestandsmachine wordt geïmplementeerd met Boost.SML (State Machine Language), een C++14-bibliotheek die alleen uit headers bestaat en een domeinspecifieke taal biedt voor het definiëren van toestandsmachines.

Waarom Boost.SML?

Boost.SML werd gekozen voor OSSM omdat het het volgende biedt:

  • Verificatie tijdens compilatie - Ongeldige overgangen worden gedetecteerd tijdens het compileren, niet tijdens de runtime
  • Geen runtime-overhead - Prestaties gelijkwaardig aan handgeschreven switch/case
  • Declaratieve syntaxis - De transitietabel leest als documentatie
  • Threadveiligheid - Ingebouwde ondersteuning voor gelijktijdige toegang via beleidsregels
  • Kleine footprint - Eén header, ~2000 regels code, geen afhankelijkheden

Syntaxis van overgangstabel

Elke rij in de overgangstabel volgt dit patroon:

source_state + event [guard] / action = target_state

Bijvoorbeeld:

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

Dit betekent: wanneer de toestand menu.idle actief is en er een buttonPress-gebeurtenis optreedt, gaat de toestand over naar simplePenetration als de guard isOption(Menu::SimplePenetration) true retourneert.

Voor een volledige tutorial over de syntaxis van transitietabellen, zie Boost.SML-zelfstudie.

Gebeurtenissen

Gebeurtenissen veroorzaken toestandsovergangen. Het zijn eenvoudige structuren die zijn gedefinieerd in Events.h en die vanuit elk deel van de codebase kunnen worden verzonden.

EvenementBeschrijvingTypische bron
ButtonPressEnkele klik op de knopKnop van de draai-encoder
LongPressKnop gedurende langere tijd vastgehoudenEncoderknop (vastgehouden)
DoublePressTwee snelle klikken op de knopKnop van de draai-encoder
DoneAsynchrone bewerking voltooidHoming-taak, preflightcontrole
ErrorBewerking misluktMislukte homing, slag te kort
EmergencyStopOnmiddellijke stopzetting vereistVeiligheidssystemen
HomeHomingsequentie aanvragenExterne opdracht

Gebeurtenissen dispatchen

Gebeurtenissen worden met behulp van de globale stateMachine-instantie gedispatcht:

#include "ossm/state/state.h"

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

De toestandsmachine wordt bij het opstarten geïnitialiseerd in state.cpp en als globale pointer beschikbaar gesteld.

Gebeurtenissen worden gedefinieerd als lege structuren. Het type zelf draagt de betekenis: er is geen gegevenspayload nodig.

Guards

Guards zijn voorwaardelijke controles die bepalen of een overgang moet plaatsvinden. Ze retourneren true om de overgang toe te staan of false om deze te blokkeren.

GuardBeschrijvingRetourneert true wanneer ...
isOnlineWiFi-verbinding controlerenHet apparaat met WiFi is verbonden
isUpdateAvailableOp firmware-updates controlerenDe server meldt dat een nieuwere versie beschikbaar is
isStrokeTooShortHet homing-resultaat validerenDe gemeten slag onder de minimumdrempel ligt
isOption(Menu)Het geselecteerde menu-item controlerenDe huidige menuselectie overeenkomt met de opgegeven optie
isPreflightSafeDe positie van de snelheidsknop validerenDe snelheidspotentiometer zich in de dode zone bevindt (veilig starten)
isFirstHomedEenmalige controle van de eerste homingDit de eerste geslaagde homing sinds het opstarten is
isNotHomedDe homingstatus controlerenHet apparaat niet is gehomed of de homing ongeldig is gemaakt

Implementatie van Guards

Guards worden gedefinieerd als constexpr-lambdas in de naamruimte guards. Ze roepen vooruit gedeclareerde implementatiefuncties aan om de header licht te houden:

// 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; };
    };
}

De feitelijke logica zit in guards.cpp, waardoor de compileertijden kort blijven en implementatiewijzigingen mogelijk zijn zonder de transitietabel opnieuw te compileren.

Zie de Boost.SML Guards-documentatie voor meer informatie over guard-patronen.

Acties

Acties zijn functies die tijdens toestandsovergangen worden uitgevoerd. Ze veroorzaken bijwerkingen zoals het bijwerken van het display, het starten van motoren of het resetten van instellingen.

Weergaveacties

ActieBeschrijving
drawHelloWelkomst-/opstartscherm weergeven
(drawMenu)Het hoofdmenu weergeven
drawPlayControlsSnelheids-/slag-/dieptebediening weergeven
drawPatternControlsPatroonselectie-UI weergeven
drawPreflightDe waarschuwing "snelheid verlagen om te starten" weergeven
drawHelpHelp-/ondersteuningsinformatie weergeven
drawWiFiHet WiFi-configuratiescherm weergeven
drawUpdateHet scherm "controleren op updates" weergeven
drawNoUpdateHet bericht "firmware is up-to-date" weergeven
drawUpdatingUpdatevoortgang weergeven
drawErrorFouttoestand met bericht weergeven

Bewegingsacties

ActieBeschrijving
startHomingDe homingsequentie starten
clearHomingVariabelen van de homingtoestand resetten
startSimplePenetrationDe taak in de modus Simple Penetration starten
startStrokeEngineDe taak in de modus Stroke Engine starten
startStreamingDe taak in de modus Streaming starten
emergencyStopDe motor geforceerd stoppen en de uitgangen uitschakelen

Instellingsacties

ActieBeschrijving
resetSettingsStrokeEngineStandaardwaarden voor Stroke Engine initialiseren (snelheid=0, slag=50, diepte=10, sensatie=50)
resetSettingsSimplePenStandaardwaarden voor Simple Penetration initialiseren (snelheid=0, slag=0, diepte=50)
incrementControlDe bedieningsparameters doorlopen (slag → diepte → sensatie)
setHomedHet apparaat als succesvol gehomed markeren
setNotHomedHoming ongeldig maken (opnieuw homen vereist voordat een bewegingsmodus kan worden gestart)

Systeemacties

ActieBeschrijving
restartDe ESP32 opnieuw starten
resetWiFiOpgeslagen WiFi-inloggegevens wissen
updateOSSMDe firmware-update downloaden en installeren

Actie-implementatie

Acties worden gedefinieerd als constexpr-lambdas in de naamruimte actions. Net als Guards delegeren ze naar vooruit gedeclareerde implementatiefuncties:

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

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

De implementatiefuncties in actions.cpp roepen de juiste functiemodules op:

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

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

Dit patroon houdt de toestandsmachine losgekoppeld van specifieke implementaties en maakt het mogelijk dat functiemodules onafhankelijk worden ontwikkeld.

Acties mogen de hoofdthread niet blokkeren. Langdurige bewerkingen moeten in plaats daarvan FreeRTOS-taken starten.

Zie de Documentatie over Boost.SML-acties voor meer informatie over actiepatronen.

Threadveiligheid

De OSSM-toestandsmachine is geconfigureerd met threadveilige beleidsregels om gebeurtenissen van meerdere FreeRTOS-taken af te handelen:

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

Hierbij wordt een recursieve mutex gebruikt om toestandsovergangen te beschermen, zodat gebeurtenissen veilig vanuit de volgende contexten kunnen worden gedispatcht:

  • De hoofdlus
  • Knop-interrupt-handlers
  • BLE-opdrachthandlers
  • Achtergrondtaken

De toestandsmachine wordt geïnitialiseerd in state.cpp:

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

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

Zie de documentatie over Boost.SML-beleidsregels voor meer informatie over threadveiligheidsbeleid.

Toestandslogging

De firmware bevat een StateLogger die alle toestandsovergangen registreert voor foutopsporing:

sml::logger<StateLogger>

Dit geeft transitie-informatie door via ESP-IDF-logging, waardoor het gedrag van de toestandsmachine tijdens de ontwikkeling eenvoudig kan worden gevolgd.

Architectuurnotities

De toestandsmachine volgt een ontkoppelde modulaire architectuur:

  • Definitie van de toestandsmachine (machine.h) bevat alleen de overgangstabel
  • Acties en Guards zijn constexpr-lambda's die delegeren aan implementatiefuncties
  • Functiemodules (zoals pages::, simple_penetration::, stroke_engine::) bevatten de daadwerkelijke logica
  • Globale toestandsstructuren beheren de applicatietoestand in plaats van klasseleden

De klasse OSSM in ossm/OSSM.h blijft behouden voor achterwaartse compatibiliteit met de verwerking van BLE-opdrachten. Nieuwe functies moeten gebruik maken van staatloze naamruimtefuncties die werken op basis van de globale toestand.

Zie Mappenstructuur voor een compleet overzicht van de broncodeorganisatie.

Verder lezen

Op deze pagina