From f76ee18efc8d6d6396ab0aef7518f2ea6918cc37 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 6 Sep 2025 07:19:25 -0500 Subject: [PATCH] Remove dynamic allocations and set static max --- Fsm.cpp | 15 +- Fsm.h | 16 +- README.md | 32 ++++ examples/error_handling/error_handling.ino | 45 +++++ examples/memory_test/memory_test.ino | 62 +++++++ examples/timed_switchoff/Fsm.cpp | 175 ------------------- examples/timed_switchoff/timed_switchoff.ino | 6 +- library.properties | 4 +- 8 files changed, 167 insertions(+), 188 deletions(-) create mode 100644 examples/error_handling/error_handling.ino create mode 100644 examples/memory_test/memory_test.ino delete mode 100644 examples/timed_switchoff/Fsm.cpp 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