Skip to content

FantasyFactory/EventStateMachine

Repository files navigation

EventStateMachine

License: MITArduinoPlatformIO

A flexible and powerful event-driven State Machine library for Arduino with cross-platform support (ESP8266, ESP32, AVR, RP2040, and others).

Read this in Italian

Features

  • Cross-platform: Works on ESP8266, ESP32, AVR (Arduino Uno/Mega/Nano), RP2040 (Raspberry Pi Pico), and other Arduino-compatible boards
  • Hardware timer support: Uses native hardware timers on ESP and RP2040 for reliable timeouts even when loop is blocked
  • Advanced event handling: Multiple callbacks for each state event (enter, exit, during)
  • Configurable timeouts: Set multiple timeouts per state with specific callbacks
  • Global transition handlers: Customizable hooks before and after state changes
  • Memory efficient: Uses static arrays on AVR to minimize RAM usage, dynamic vectors on platforms with more memory
  • Easy integration with persistent storage: Example of saving and recovering state from flash memory
  • Robust error handling: Verification of state validity and callbacks

Platform Support

PlatformTimer TypeTimeout ReliabilitySTL Support
ESP8266Hardware (Ticker)Guaranteed*Yes
ESP32Hardware (Ticker)Guaranteed*Yes
RP2040Hardware (Pico SDK)Guaranteed*Yes
AVRSoftware (millis)Depends on update()**No (static arrays)
OthersSoftware (millis)Depends on update()**Configurable

* Timeouts fire even if the main loop is blocked ** Timeouts only fire when update() is called

Requirements

  • Arduino-compatible board (ESP8266, ESP32, AVR, RP2040, etc.)
  • Arduino IDE 1.8.0+ or PlatformIO
  • (Optional) LittleFS for state persistence example (ESP/RP2040 only)

Installation

Arduino IDE - Library Manager (Recommended)

  1. Open Arduino IDE
  2. Go to Sketch > Include Library > Manage Libraries...
  3. Search for "EventStateMachine"
  4. Select the library and click Install

PlatformIO

Add to your platformio.ini:

lib_deps = FantasyFactory/EventStateMachine

Or use the PlatformIO Library Manager in VS Code.

Manual Installation

  1. Download the library as a ZIP file from GitHub
  2. Arduino IDE: Sketch > Include Library > Add .ZIP Library...
  3. Select the downloaded ZIP file

Core Concepts

States and Callbacks

EventStateMachine uses a callback system to handle state events:

  • onEnter: Executed when entering a state
  • onState: Executed continuously while in a state (in the update() method)
  • onExit: Executed when exiting a state
  • onTimeout: Executed when a configured timeout expires

Timeouts

Each state can have multiple timeouts with different durations and callbacks:

// Add a 5-second timeout to the RUNNING state stateMachine.addTimeout(STATE_RUNNING, 5000, onRunningTimeout);

Global Transition Handlers

You can register functions that will be called before and after every state transition:

// Add a handler to save every transition stateMachine.addAfterStateChangeHandler(saveStateTransition);

Basic Usage

#include<EventStateMachine.h>// Define statesenum States{STATE_IDLE, STATE_RUNNING, STATE_ERROR, NUM_STATES }; // Create state machine EventStateMachine stateMachine(NUM_STATES); // Callback when entering RUNNING statevoidonEnterRunning(uint8_t current, uint8_t previous){Serial.println("Entering RUNNING state"); digitalWrite(LED_BUILTIN, HIGH)} voidsetup(){Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); // Add callbacks stateMachine.addOnEnter(STATE_RUNNING, onEnterRunning); // Set initial state stateMachine.setState(STATE_IDLE)} voidloop(){// Update state machine (required for onState callbacks and millis-based timeouts) stateMachine.update(); // Change state based on external conditionsif (/* some condition */){stateMachine.setState(STATE_RUNNING)} }

Complete API

Constructor

// Create a state machine with the specified number of statesEventStateMachine(uint8_t numberOfStates);

State Configuration

// Configure a state in a single callvoidconfigureState( uint8_t state, // State to configureunsignedlong timeout = 0, // Optional timeout (ms) StateCallback onEnter = nullptr, // Entry callback StateFunction onState = nullptr, // During callback StateCallback onExit = nullptr, // Exit callback StateCallback onTimeout = nullptr// Timeout callback ); // Methods to add individual callbacksbooladdTimeout(uint8_t state, unsignedlong timeout, StateCallback onTimeout); booladdOnEnter(uint8_t state, StateCallback onEnter); booladdOnState(uint8_t state, StateFunction onState); booladdOnExit(uint8_t state, StateCallback onExit); // Methods to remove callbacksboolremoveTimeout(uint8_t state, unsignedlong timeout); boolremoveOnEnter(uint8_t state, StateCallback onEnter); boolremoveOnState(uint8_t state, StateFunction onState); boolremoveOnExit(uint8_t state, StateCallback onExit);

Global Transition Handlers

voidaddBeforeStateChangeHandler(GlobalStateCallback handler); voidaddAfterStateChangeHandler(GlobalStateCallback handler); boolremoveBeforeStateChangeHandler(GlobalStateCallback handler); boolremoveAfterStateChangeHandler(GlobalStateCallback handler);

Control and Execution

// Change the current statevoidsetState(uint8_t newState); // Perform an update cycle (call this in loop())voidupdate(); // Enable/disable debug messages on serial portvoidsetDebug(bool enable); // Set singleton instance (auto-called in constructor, override for multiple instances)voidsetInstance();

Informational Methods

// Get the current stateuint8_tgetCurrentState() const; // Get the previous stateuint8_tgetPreviousState() const; // Check if the state has just changedboolisStateChanged() const; // Get the time spent in the current state (in ms)unsignedlongtimeInCurrentState() const; // Get the total number of statesuint8_tgetNumStates() const; // Check if using hardware timer (ESP/RP2040) or software polling (AVR)staticboolhasHardwareTimer();

AVR Configuration

On AVR platforms (Arduino Uno, Mega, Nano, etc.), the library uses static arrays instead of dynamic vectors to save memory. You can configure the maximum sizes before including the library:

// Optional: Override defaults before including the library #defineESM_MAX_TIMEOUTS_PER_STATE2// Default: 4 #defineESM_MAX_CALLBACKS_PER_STATE2// Default: 4 #defineESM_MAX_GLOBAL_HANDLERS2// Default: 4 #include<EventStateMachine.h>

Examples

The library includes three complete examples:

BasicStateMachine

Demonstrates basic state machine usage with three states and essential callbacks. Controls the built-in LED based on state and responds to serial commands.

MultipleCallbacks

Illustrates how to use multiple callbacks for each type of state event:

  • Multiple entry callbacks for a state
  • Multiple during callbacks for a state
  • Multiple exit callbacks for a state
  • Multiple timeouts with different durations

StateRecovery

Shows how to implement state persistence using LittleFS (ESP/RP2040 only). Allows you to:

  • Save each state transition to a log file
  • Recover the last saved state after a restart
  • View the history of transitions

Design Considerations

Timer Strategies

The library uses different timer implementations depending on the platform:

Hardware Timers (ESP8266, ESP32, RP2040)

  • Timeouts use hardware interrupts via Ticker (ESP) or Pico SDK alarms (RP2040)
  • Callbacks execute asynchronously, even if loop() is blocked
  • Ideal for safety-critical timeouts (e.g., preventing a state from getting stuck)

Software Polling (AVR and others)

  • Timeouts are checked during update() using millis()
  • Accuracy depends on how frequently update() is called
  • Blocking code (e.g., delay()) will delay timeout execution

You can check at runtime which timer type is being used:

if (EventStateMachine::hasHardwareTimer()){Serial.println("Using hardware timers - timeouts are reliable")} else{Serial.println("Using millis polling - call update() frequently!")}

Memory Usage

  • ESP/RP2040: Uses C++ std::vector for flexible, dynamic callback storage
  • AVR: Uses fixed-size arrays to minimize RAM usage on memory-constrained devices

Troubleshooting

Timeouts not firing

On hardware timer platforms (ESP/RP2040):

  • Timeouts should fire reliably even with blocking code
  • Make sure the timeout callback is not null
  • Verify the timeout duration is > 0

On millis-based platforms (AVR):

  • Ensure update() is called frequently in loop()
  • Avoid long delay() calls that block execution
  • Consider using non-blocking patterns (e.g., millis() timing)

Callbacks not executed

  • Ensure the state is valid (< numStates)
  • Verify callbacks have been properly registered
  • Check that the state machine instance is not being recreated

Compilation errors on AVR

  • If you get "out of memory" errors, reduce ESM_MAX_* values
  • The library disables STL features on AVR automatically

Contributing

Contributions are welcome! Feel free to open issues or submit pull requests on GitHub.

License

This library is released under the MIT License. See the LICENSE file for details.

Credits

Created by Corrado Casoni ([email protected]), May 2025.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages