Language: EN

libreria-arduino-statemachine

Arduino StateMachine Library

The StateMachine library implements a finite state machine that can run on a processor such as Arduino.

The state machine is initialized by indicating the number of states and transitions. Both states and transitions are identified by an 8-bit integer (0 to 255). States symbolize the different configurations in which the machine can be. Transitions link two states, one from which the transition originates and one to which it leads, and have an associated trigger condition.

The state machine has a current state. To update the state, we must frequently call the Update() function, which checks the transitions with the current state as input, and their associated conditions.

If any of the transitions associated with the current state meets the trigger condition, the machine moves to the final state of the activated transition.

Transitions can optionally execute a callback function associated with the trigger. On the other hand, states can have an entering or leaving callback action.

To configure the machine, we must properly configure the transitions. To do this, we can use the SetTransition(…) functions, explicitly indicating the number of the transition to configure. We can also use the AddTransition(…) functions, which increment an internal counter from 0 to numTransitions-1, making it easier to use for the initial configuration of the machine.

In usage, it is convenient to create enumerations for states, and it is possible for transitions.

Usage Manual

The StateMachine class can be instantiated through its constructor,

StateMachine(uint8_t numStates, uint8_t numTransitions);

Using StateMachine

// Configure a transition
void SetTransition(uint8_t transition, uint8_t inputState, uint8_t outputState, StateMachineCondition condition);
void SetTransition(uint8_t transition, uint8_t inputState, uint8_t outputState, StateMachineCondition condition, StateMachineAction action);
  
// Configure a transition, uses an internal counter. Useful for initial setup.
uint8_t AddTransition(uint8_t inputState, uint8_t outputState, StateMachineCondition condition);
uint8_t AddTransition(uint8_t inputState, uint8_t outputState, StateMachineCondition condition, StateMachineAction action);

// Disable a transition
void RemoveTransition(uint8_t transition);
  
// Configure entering and leaving actions for a state
void SetOnEntering(uint8_t state, StateMachineAction action);
void SetOnLeaving(uint8_t state, StateMachineAction action);
void ClearOnEntering(uint8_t state);
void ClearOnLeaving(uint8_t state);
  
// Change the machine to a state
void SetState(uint8_t state, bool launchLeaving, bool launchEntering);
uint8_t GetState() const;
  
// Update the state of the machine
// This function has to be called frequently from the main loop
bool Update();

Examples

The StateMachine library includes the following examples to illustrate its usage.

  • StateMachine: Example that shows the usage of StateMachine.
#include "StateMachineLib.h"

// Enumerations to facilitate usage
enum State
{
  PositionA = 0,
  PositionB = 1,
  PositionC = 2,
  PositionD = 3
};

enum Input
{
  Reset = 0,
  Forward = 1,
  Backward = 2,
  Unknown = 3,
};

StateMachine stateMachine(4, 9);
Input input;

// Taken as an independent function for clarity
void setupStateMachine()
{
  // Configure transitions
  // Example with lambda functions
  stateMachine.AddTransition(PositionA, PositionB, []() { return input == Forward; });

  stateMachine.AddTransition(PositionB, PositionA, []() { return input == Backward; });
  stateMachine.AddTransition(PositionB, PositionC, []() { return input == Forward; });
  stateMachine.AddTransition(PositionB, PositionA, []() { return input == Reset; });

  stateMachine.AddTransition(PositionC, PositionB, []() { return input == Backward; });
  stateMachine.AddTransition(PositionC, PositionD, []() { return input == Forward; });
  stateMachine.AddTransition(PositionC, PositionA, []() { return input == Reset; });

  stateMachine.AddTransition(PositionD, PositionC, []() { return input == Backward; });
  stateMachine.AddTransition(PositionD, PositionA, []() { return input == Reset; });

  // Configure state events
  // Example with regular functions
  stateMachine.SetOnEntering(PositionA, outputA);
  stateMachine.SetOnEntering(PositionB, outputB);
  stateMachine.SetOnEntering(PositionC, outputC);
  stateMachine.SetOnEntering(PositionD, outputD);

  // Example with lambda functions
  stateMachine.SetOnLeaving(PositionA, []() {Serial.println("Leaving A"); });
  stateMachine.SetOnLeaving(PositionB, []() {Serial.println("Leaving B"); });
  stateMachine.SetOnLeaving(PositionC, []() {Serial.println("Leaving C"); });
  stateMachine.SetOnLeaving(PositionD, []() {Serial.println("Leaving D"); });
}

void setup() 
{
  Serial.begin(9600);

  Serial.println("Starting State Machine...");
  setupStateMachine();  
  Serial.println("Start Machine Started");

  stateMachine.SetState(PositionA, false, true);
}

void loop() 
{
  // Receive a command via serial port
  input = static_cast<Input>(readInput());
  
  // Update the state of the machine
  stateMachine.Update();
}

// Helper function that simulates the reception of an event
int readInput()
{
  Input currentInput = Input::Unknown;
  if (Serial.available())
  {
    char incomingChar = Serial.read();

    switch (incomingChar)
    {
      case 'R': currentInput = Input::Reset;   break;
      case 'A': currentInput = Input::Backward; break;
      case 'D': currentInput = Input::Forward; break;
      default: break;
    }
  }

  return currentInput;
}

// State actions
// Display the state of the example
void outputA()
{
  Serial.println("A   B   C   D");
  Serial.println("X            ");
  Serial.println();
}

void outputB()
{
  Serial.println("A   B   C   D");
  Serial.println("    X        ");
  Serial.println();
}

void outputC()
{
  Serial.println("A   B   C   D");
  Serial.println("        X    ");
  Serial.println();
}

void outputD()
{
  Serial.println("A   B   C   D");
  Serial.println("            X");
  Serial.println();
}
  • Inheritance: Example that shows the usage of StateMachine through a derived class.
#include "StateMachineLib.h"

enum State
{
  PositionA = 0,
  PositionB = 1,
  PositionC = 2,
  PositionD = 3
};

enum Input
{
  Reset = 0,
  Forward = 1,
  Backward = 2,
  Unknown = 3,
};

Input input;

class MyStateMachine : public StateMachine
{
public:
  MyStateMachine() : StateMachine(4, 9)
  {
    setupStateMachine();
  }

  void setupStateMachine()
  {
    AddTransition(PositionA, PositionB, []() { return input == Forward; });

    AddTransition(PositionB, PositionA, []() { return input == Backward; });
    AddTransition(PositionB, PositionC, []() { return input == Forward; });
    AddTransition(PositionB, PositionA, []() { return input == Reset; });

    AddTransition(PositionC, PositionB, []() { return input == Backward; });
    AddTransition(PositionC, PositionD, []() { return input == Forward; });
    AddTransition(PositionC, PositionA, []() { return input == Reset; });

    AddTransition(PositionD, PositionC, []() { return input == Backward; });
    AddTransition(PositionD, PositionA, []() { return input == Reset; });

    SetOnEntering(PositionA, []()
    {
      Serial.println("A   B   C   D");
      Serial.println("X            ");
      Serial.println();
    });

    SetOnEntering(PositionB, []()
    {
      Serial.println("A   B   C   D");
      Serial.println("X            ");
      Serial.println();
    });

    SetOnEntering(PositionC, []()
    {
      Serial.println("A   B   C   D");
      Serial.println("        X    ");
      Serial.println();
    });

    SetOnEntering(PositionD, []()
    {
      Serial.println("A   B   C   D");
      Serial.println("            X");
      Serial.println();
    });

    SetOnLeaving(PositionA, []() {Serial.println("Leaving A"); });
    SetOnLeaving(PositionB, []() {Serial.println("Leaving B"); });
    SetOnLeaving(PositionC, []() {Serial.println("Leaving C"); });
    SetOnLeaving(PositionD, []() {Serial.println("Leaving D"); });
  }
};

MyStateMachine myStateMachine;

void setup() 
{
  Serial.begin(9600);

  myStateMachine.SetState(State::PositionA, false, true);
}

void loop() 
{
  input = static_cast<Input>(readInput());
  myStateMachine.Update();
}

int readInput()
{
  Input currentInput = Input::Unknown;
  if (Serial.available())
  {
    char incomingChar = Serial.read();

    switch (incomingChar)
    {
      case 'R': currentInput = Input::Reset;   break;
      case 'A': currentInput = Input::Backward; break;
      case 'D': currentInput = Input::Forward; break;
      default: break;
    }
  }

  return currentInput;
}

Installation

  • Download the latest version from GitHub
  • Unzip the file
  • Copy to your libraries folder (usually My Documents\Arduino\libraries)
  • Restart the Arduino IDE

github-full