Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Fsm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ State::State(void (*on_enter)(), void (*on_state)(), void (*on_exit)(), const ch

Fsm::Fsm(State *initial_state)
: m_current_state(initial_state),
m_transitions(NULL),
m_num_transitions(0),
m_num_timed_transitions(0),
m_initialized(false)
Expand All @@ -35,21 +34,25 @@ Fsm::Fsm(State *initial_state)

Fsm::~Fsm()
{
free(m_transitions);
m_transitions = NULL;
// No dynamic memory to free anymore
}

void Fsm::add_transition(State *state_from, State *state_to, int event,
bool Fsm::add_transition(State *state_from, State *state_to, int event,
void (*on_transition)(), const char *name)
{
if (state_from == NULL || state_to == NULL)
return;
return false;

if (m_num_transitions >= MAX_TRANSITIONS) {
// Transition array is full
return false;
}

Transition transition = Fsm::create_transition(state_from, state_to, event,
on_transition, name);
m_transitions = (Transition *)realloc(m_transitions, (m_num_transitions + 1) * sizeof(Transition));
m_transitions[m_num_transitions] = transition;
m_num_transitions++;
return true;
}

TimedTransition *Fsm::add_timed_transition(State *state_from, State *state_to,
Expand Down
16 changes: 14 additions & 2 deletions Fsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,31 @@ struct TimedTransition

#define MAX_TIMED_TRANSITIONS 8

// Maximum number of regular transitions
// Can be overridden at compile time: -DMAX_TRANSITIONS=128
// Fixed-size array prevents memory leaks and fragmentation
#ifndef MAX_TRANSITIONS
#define MAX_TRANSITIONS 128
#endif

class Fsm
{
public:
Fsm(State *initial_state);
~Fsm();

void add_transition(State *state_from, State *state_to, int event,
// Add a transition between states
// Returns true on success, false if transition array is full
bool add_transition(State *state_from, State *state_to, int event,
void (*on_transition)(), const char *name);

TimedTransition *add_timed_transition(State *state_from, State *state_to,
unsigned long interval, void (*on_transition)(), const char *name);

State *getState() const { return m_current_state; }
int getNumTransitions() const { return m_num_transitions; }
int getMaxTransitions() const { return MAX_TRANSITIONS; }
int getAvailableTransitions() const { return MAX_TRANSITIONS - m_num_transitions; }

void check_timed_transitions();

Expand All @@ -76,7 +88,7 @@ class Fsm

private:
State *m_current_state;
Transition *m_transitions;
Transition m_transitions[MAX_TRANSITIONS];
int m_num_transitions;

TimedTransition m_timed_transitions[MAX_TIMED_TRANSITIONS];
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
An arduino library for implementing a finite state machine.

# Memory Efficient Design

This library uses fixed-size arrays to avoid memory fragmentation and leaks. By default:
- Maximum 128 transitions (can be customized with `-DMAX_TRANSITIONS=N`)
- Maximum 8 timed transitions

For projects needing more transitions, compile with: `-DMAX_TRANSITIONS=128` (or your desired limit)

# Usage

The `add_transition()` method now returns a boolean indicating success. Check the return value for critical applications:

```cpp
bool success = fsm.add_transition(&state1, &state2, EVENT, &callback, "transition_name");
if (!success) {
// Handle error: transition array is full
}

// Check available capacity
int available = fsm.getAvailableTransitions();
```

# Documentation

Other than the examples included in the library, the following pages might be
Expand All @@ -22,6 +44,16 @@ feature branch.

# Changelog

**2.3.0 - 06/09/2025**

* **MAJOR FIX**: Eliminated memory leak caused by dynamic allocation in `add_transition()`
* Changed from dynamic `realloc()` to fixed-size array for transitions (default: 128 max transitions)
* `add_transition()` now returns `bool` to indicate success/failure
* Added methods: `getNumTransitions()`, `getMaxTransitions()`, `getAvailableTransitions()`
* Maximum transitions can be customized at compile time with `-DMAX_TRANSITIONS=N`
* **BREAKING CHANGE**: `add_transition()` now returns bool instead of void
* Fixed memory fragmentation issues on Arduino with limited RAM

**2.2.0 - 25/10/2017**

* Add `on_state()` handler to states
Expand Down
45 changes: 45 additions & 0 deletions examples/error_handling/error_handling.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "Fsm.h"

// Example demonstrating error handling for transition limits

#define BUTTON_PRESS 1

State state1(NULL, NULL, NULL, "State1");
State state2(NULL, NULL, NULL, "State2");
Fsm fsm(&state1);

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

Serial.print("Maximum transitions: ");
Serial.println(fsm.getMaxTransitions());

// Add transitions with error checking
bool success = fsm.add_transition(&state1, &state2, BUTTON_PRESS, NULL, "1->2");
if (!success) {
Serial.println("ERROR: Failed to add transition 1->2");
}

success = fsm.add_transition(&state2, &state1, BUTTON_PRESS, NULL, "2->1");
if (!success) {
Serial.println("ERROR: Failed to add transition 2->1");
}

Serial.print("Transitions used: ");
Serial.println(fsm.getNumTransitions());
Serial.print("Transitions available: ");
Serial.println(fsm.getAvailableTransitions());
}

void loop()
{
fsm.run_machine();

// Simulate button press every 2 seconds
static unsigned long last_press = 0;
if (millis() - last_press > 2000) {
fsm.trigger(BUTTON_PRESS);
last_press = millis();
}
}
62 changes: 62 additions & 0 deletions examples/memory_test/memory_test.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "Fsm.h"

// Memory leak test - demonstrates the fix for the realloc() issue

State state1(NULL, NULL, NULL, "State1");
State state2(NULL, NULL, NULL, "State2");
State state3(NULL, NULL, NULL, "State3");
State state4(NULL, NULL, NULL, "State4");
State state5(NULL, NULL, NULL, "State5");

Fsm fsm(&state1);

void setup() {
Serial.begin(9600);
Serial.println("=== Arduino FSM Memory Leak Test ===");

// Report initial memory info
Serial.print("Max transitions supported: ");
Serial.println(fsm.getMaxTransitions());

// Add many transitions to simulate PowerFSM scenario
Serial.println("Adding 35 transitions to simulate PowerFSM...");

for (int i = 0; i < 35; i++) {
// Cycle through states to create many transitions
State* from = &state1;
State* to = &state2;

switch (i % 5) {
case 0: from = &state1; to = &state2; break;
case 1: from = &state2; to = &state3; break;
case 2: from = &state3; to = &state4; break;
case 3: from = &state4; to = &state5; break;
case 4: from = &state5; to = &state1; break;
}

bool success = fsm.add_transition(from, to, i, NULL, "test_transition");

if (!success) {
Serial.print("FAILED to add transition ");
Serial.println(i);
}

// Print progress every 10 transitions
if ((i + 1) % 10 == 0) {
Serial.print("Added ");
Serial.print(i + 1);
Serial.print(" transitions. Available: ");
Serial.println(fsm.getAvailableTransitions());
}
}

Serial.print("Final transitions count: ");
Serial.println(fsm.getNumTransitions());
Serial.print("Available transitions: ");
Serial.println(fsm.getAvailableTransitions());
}

void loop() {
// Nothing to do - this is just a setup test
delay(1000);
}
Loading