diff --git a/Fsm.cpp b/Fsm.cpp
index 8800886..648952e 100644
--- a/Fsm.cpp
+++ b/Fsm.cpp
@@ -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)
@@ -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,
diff --git a/Fsm.h b/Fsm.h
index 8bb8a9c..270e718 100644
--- a/Fsm.h
+++ b/Fsm.h
@@ -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();
@@ -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];
diff --git a/README.md b/README.md
index 702ac8f..fa3bf39 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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
diff --git a/examples/error_handling/error_handling.ino b/examples/error_handling/error_handling.ino
new file mode 100644
index 0000000..6a66358
--- /dev/null
+++ b/examples/error_handling/error_handling.ino
@@ -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();
+ }
+}
diff --git a/examples/memory_test/memory_test.ino b/examples/memory_test/memory_test.ino
new file mode 100644
index 0000000..b81e557
--- /dev/null
+++ b/examples/memory_test/memory_test.ino
@@ -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);
+}
diff --git a/examples/timed_switchoff/Fsm.cpp b/examples/timed_switchoff/Fsm.cpp
deleted file mode 100644
index d2c34af..0000000
--- a/examples/timed_switchoff/Fsm.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-// This file is part of arduino-fsm.
-//
-// arduino-fsm is free software: you can redistribute it and/or modify it under
-// the terms of the GNU Lesser General Public License as published by the Free
-// Software Foundation, either version 3 of the License, or (at your option)
-// any later version.
-//
-// arduino-fsm is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
-// for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with arduino-fsm. If not, see .
-
-#include "Fsm.h"
-
-
-State::State(void (*on_enter)(), void (*on_state)(), void (*on_exit)())
-: on_enter(on_enter),
- on_state(on_state),
- on_exit(on_exit)
-{
-}
-
-
-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)
-{
-}
-
-
-Fsm::~Fsm()
-{
- free(m_transitions);
- free(m_timed_transitions);
- m_transitions = NULL;
- m_timed_transitions = NULL;
-}
-
-
-void Fsm::add_transition(State* state_from, State* state_to, int event,
- void (*on_transition)())
-{
- if (state_from == NULL || state_to == NULL)
- return;
-
- Transition transition = Fsm::create_transition(state_from, state_to, event,
- on_transition);
- m_transitions = (Transition*) realloc(m_transitions, (m_num_transitions + 1)
- * sizeof(Transition));
- m_transitions[m_num_transitions] = transition;
- m_num_transitions++;
-}
-
-
-void Fsm::add_timed_transition(State* state_from, State* state_to,
- unsigned long interval, void (*on_transition)(), const char *name)
-{
- if (state_from == NULL || state_to == NULL)
- return;
-
- Transition transition = Fsm::create_transition(state_from, state_to, 0,
- on_transition, name);
-
- TimedTransition timed_transition;
- timed_transition.transition = transition;
- timed_transition.start = 0;
- timed_transition.interval = interval;
-
- m_timed_transitions = (TimedTransition*) realloc(
- m_timed_transitions, (m_num_timed_transitions + 1) * sizeof(TimedTransition));
- m_timed_transitions[m_num_timed_transitions] = timed_transition;
- m_num_timed_transitions++;
-}
-
-
-Fsm::Transition Fsm::create_transition(State* state_from, State* state_to,
- int event, void (*on_transition)(), const char *name)
-{
- Transition t;
- t.state_from = state_from;
- t.state_to = state_to;
- t.event = event;
- t.on_transition = on_transition;
- t.name = name;
-
- return t;
-}
-
-void Fsm::trigger(int event)
-{
- if (m_initialized)
- {
- // Find the transition with the current state and given event.
- for (int i = 0; i < m_num_transitions; ++i)
- {
- if (m_transitions[i].state_from == m_current_state &&
- m_transitions[i].event == event)
- {
- Fsm::make_transition(&(m_transitions[i]));
- return;
- }
- }
- }
-}
-
-void Fsm::check_timed_transitions()
-{
- for (int i = 0; i < m_num_timed_transitions; ++i)
- {
- TimedTransition* transition = &m_timed_transitions[i];
- if (transition->transition.state_from == m_current_state)
- {
- if (transition->start == 0)
- {
- transition->start = millis();
- }
- else{
- unsigned long now = millis();
- if (now - transition->start >= transition->interval)
- {
- Fsm::make_transition(&(transition->transition));
- transition->start = 0;
- }
- }
- }
- }
-}
-
-void Fsm::run_machine()
-{
- // first run must exec first state "on_enter"
- if (!m_initialized)
- {
- m_initialized = true;
- if (m_current_state->on_enter != NULL)
- m_current_state->on_enter();
- }
-
- if (m_current_state->on_state != NULL)
- m_current_state->on_state();
-
- Fsm::check_timed_transitions();
-}
-
-void Fsm::make_transition(Transition* transition)
-{
-
- // Execute the handlers in the correct order.
- if (transition->state_from->on_exit != NULL)
- transition->state_from->on_exit();
-
- if (transition->on_transition != NULL)
- transition->on_transition();
-
- if (transition->state_to->on_enter != NULL)
- transition->state_to->on_enter();
-
- m_current_state = transition->state_to;
-
- //Initialice all timed transitions from m_current_state
- unsigned long now = millis();
- for (int i = 0; i < m_num_timed_transitions; ++i)
- {
- TimedTransition* ttransition = &m_timed_transitions[i];
- if (ttransition->transition.state_from == m_current_state)
- ttransition->start = now;
- }
-
-}
diff --git a/examples/timed_switchoff/timed_switchoff.ino b/examples/timed_switchoff/timed_switchoff.ino
index 657a435..eca3573 100644
--- a/examples/timed_switchoff/timed_switchoff.ino
+++ b/examples/timed_switchoff/timed_switchoff.ino
@@ -57,9 +57,9 @@ void setup()
pinMode(BUTTON_PIN, INPUT_PULLUP);
fsm.add_transition(&state_led_off, &state_led_on,
- BUTTON_EVENT, NULL);
- fsm.add_timed_transition(&state_led_on, &state_led_off, 3000, NULL);
- fsm.add_transition(&state_led_on, &state_led_off, BUTTON_EVENT, NULL);
+ BUTTON_EVENT, NULL, "button_off_to_on");
+ fsm.add_timed_transition(&state_led_on, &state_led_off, 3000, NULL, "timeout_on_to_off");
+ fsm.add_transition(&state_led_on, &state_led_off, BUTTON_EVENT, NULL, "button_on_to_off");
Serial.println("Setup END");
}
diff --git a/library.properties b/library.properties
index 9b7c873..e925273 100644
--- a/library.properties
+++ b/library.properties
@@ -1,9 +1,9 @@
name=arduino-fsm
-version=2.2.0
+version=2.3.0
author=Jon Black
maintainer=Jon Black
sentence=A library for implementing a finite state machine
-paragraph=Supports events for exiting and entering states.
+paragraph=Supports events for exiting and entering states. Fixed memory leak from dynamic allocation.
category=Other
url=https://github.com/jonblack/arduino-fsm
architectures=avr