Skip to content

Latest commit

 

History

History
881 lines (689 loc) · 32.3 KB

File metadata and controls

881 lines (689 loc) · 32.3 KB

ssfsm — Finite State Machine Framework

SSF

Event-driven finite state machine framework with timer support and optional hierarchical states.

Each state machine is a function (SSFSMHandler_t) that receives events via a switch on SSFSMEventId_t. The framework delivers SSF_SM_EVENT_ENTRY and SSF_SM_EVENT_EXIT automatically on state transitions triggered by SSFSMTran(). Timers post events after a configurable delay. One level of state hierarchy is supported: a child handler names its parent with SSF_SM_SUPER() in its default case, and unhandled events are forwarded automatically.

Dependencies | Notes | Configuration | API Summary | Function Reference | Detailed Example

Dependencies

Notes

  • SSFSMInit(), SSFSMDeInit(), SSFSMInitHandler(), SSFSMDeInitHandler(), and SSFSMTask() must all be called from the same single thread of execution unless SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1.
  • SSFSMPutEventData() and SSFSMPutEvent() may be called from any execution context (including ISRs) when SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1 and the OS primitives support it; otherwise they must be called from the same single-threaded context as the other functions.
  • SSFSMTran(), SSFSMStartTimerData(), SSFSMStartTimer(), SSFSMStopTimer(), SSF_SM_SUPER(), and SSF_SM_EVENT_DATA_ALIGN() are only valid when called from within a state handler during event processing.
  • SSF_SM_SUPER() must appear only in the default case of a child state handler; parent (super) state handlers must not name a further parent.
  • A state transition automatically stops all running timers for that state machine before delivering SSF_SM_EVENT_EXIT to the current state and SSF_SM_EVENT_ENTRY to the new state.
  • SSFSMStartTimer() with interval == 0 fires the event at the next SSFSMTask() call, making it useful as a deferred self-post.
  • SSFSMTask() returns true when there are still pending events to process; call it again immediately in that case rather than waiting on the wake primitive.
  • In multi-threaded builds, SSFSMTask() is typically run in a dedicated high-priority thread that blocks using SSF_SM_THREAD_WAKE_WAIT() between calls.
  • SSFSMList_t and SSFSMEventList_t enumerations are mandatory and must be defined in ssfoptions.h.

Configuration

All options are set in ssfoptions.h.

Option Default Description
SSF_SM_MAX_ACTIVE_EVENTS 3 Maximum number of simultaneously queued events across all state machines; increase if events are dropped under peak load
SSF_SM_MAX_ACTIVE_TIMERS 3 Maximum number of simultaneously running timers across all state machines

The following enumerations are required in ssfoptions.h:

/* State machine identifiers — one entry per state machine instance */
typedef enum
{
    SSF_SM_MIN = -1,     /* Required sentinel; must be first */
    SSF_SM_MY_APP_1,     /* User-defined state machines */
    SSF_SM_MY_APP_2,
    SSF_SM_MAX           /* Required sentinel; must be last */
} SSFSMList_t;

/* Event identifiers — shared across all state machines */
typedef enum
{
    SSF_SM_EVENT_MIN = -1,      /* Required sentinel; must be first */
    SSF_SM_EVENT_ENTRY,         /* Required: delivered on state entry */
    SSF_SM_EVENT_EXIT,          /* Required: delivered on state exit */
    SSF_SM_EVENT_SUPER,         /* Required: used internally for hierarchy probing */
    /* User-defined events follow */
    SSF_SM_EVENT_MY_DATA_RX,
    SSF_SM_EVENT_MY_TIMER,
    SSF_SM_EVENT_MAX            /* Required sentinel; must be last */
} SSFSMEventList_t;

Thread sync macros (required when SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1):

Macro Description
SSF_SM_THREAD_SYNC_DECLARATION Declare the mutex object
SSF_SM_THREAD_SYNC_INIT() Initialize the mutex
SSF_SM_THREAD_SYNC_DEINIT() De-initialize the mutex
SSF_SM_THREAD_SYNC_ACQUIRE() Acquire the mutex
SSF_SM_THREAD_SYNC_RELEASE() Release the mutex

Thread wake macros (required when SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1); implement with a counting semaphore capped at 1:

Macro Description
SSF_SM_THREAD_WAKE_DECLARATION Declare the wake semaphore
SSF_SM_THREAD_WAKE_INIT() Initialize the wake semaphore
SSF_SM_THREAD_WAKE_DEINIT() De-initialize the wake semaphore
SSF_SM_THREAD_WAKE_POST() Signal the FSM thread that an event is ready
SSF_SM_THREAD_WAKE_WAIT(timeout) Block the FSM thread until an event arrives or timeout elapses

API Summary

Definitions

Symbol Kind Description
SSFSMId_t Type (int8_t) Identifies a state machine instance; values come from SSFSMList_t
SSFSMEventId_t Type (int16_t) Identifies an event; values come from SSFSMEventList_t
SSFSMData_t Type (uint8_t) Element type for event data payloads
SSFSMDataLen_t Type (uint16_t) Length in bytes of an event data payload
SSFSMTimeout_t Type (SSFPortTick_t) Timer interval in system ticks; 0 fires at the next SSFSMTask() call
SSFSMHandler_t Function pointer State handler signature: void fn(SSFSMEventId_t eid, const SSFSMData_t *data, SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
SSF_SM_MAX_TIMEOUT Constant Maximum valid timer interval ((SSFSMTimeout_t)(-1))

Functions

Function Description
e.g. void SSFSMInit(maxEvents, maxTimers) Initialize the framework; must be called before any other ssfsm function
e.g. void SSFSMDeInit() De-initialize the framework and release all resources
e.g. void SSFSMInitHandler(smid, initial) Register a state machine and deliver SSF_SM_EVENT_ENTRY to the initial state
e.g. void SSFSMDeInitHandler(smid) Unregister a state machine
e.g. bool SSFSMTask(nextTimeout) Process all pending events and expired timers; returns time until next timer
e.g. void SSFSMPutEventData(smid, eid, data, dataLen) Post an event with a data payload to a state machine
e.g. void SSFSMPutEvent(smid, eid) Post an event without data (expands to SSFSMPutEventData with NULL/0)
e.g. void SSFSMTran(next) Trigger a state transition; valid only inside a state handler
e.g. void SSFSMStartTimerData(eid, interval, data, dataLen) Start a timer that posts an event with data; valid only inside a state handler
e.g. void SSFSMStartTimer(eid, to) Start a timer without data (expands to SSFSMStartTimerData with NULL/0)
e.g. void SSFSMStopTimer(eid) Cancel a running timer; valid only inside a state handler
e.g. void SSF_SM_SUPER(super) Name the parent state handler; place in the default case of a child handler
e.g. void SSF_SM_EVENT_DATA_ALIGN(v) Copy event data into a typed variable; valid only inside a state handler

Function Reference

void SSFSMInit(uint32_t maxEvents, uint32_t maxTimers);

Initializes the framework, allocating the internal event queue and timer table. Must be called once before SSFSMInitHandler(), SSFSMTask(), or any event-posting function. Pass values that match SSF_SM_MAX_ACTIVE_EVENTS and SSF_SM_MAX_ACTIVE_TIMERS from ssfoptions.h.

Parameter Direction Type Description
maxEvents in uint32_t Maximum simultaneously queued events. Must equal SSF_SM_MAX_ACTIVE_EVENTS.
maxTimers in uint32_t Maximum simultaneously running timers. Must equal SSF_SM_MAX_ACTIVE_TIMERS.

Returns: Nothing.

Example:

/* Initialize framework — values must match ssfoptions.h constants */
SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);
/* Framework ready; call SSFSMInitHandler() for each state machine */

void SSFSMDeInit(void);

De-initializes the framework and frees internal resources. After this call, no other ssfsm function may be used until SSFSMInit() is called again.

Returns: Nothing.

Example:

SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);
/* ... initialize handlers and run ... */
SSFSMDeInit();
/* Internal resources freed; SSFSMInit() required before further use */

void SSFSMInitHandler(SSFSMId_t smid, SSFSMHandler_t initial);

Registers a state machine identified by smid and sets initial as the current state. Immediately delivers SSF_SM_EVENT_ENTRY to initial. If initial has a parent state (established through SSF_SM_SUPER()), SSF_SM_EVENT_ENTRY is delivered to the parent first, then to initial.

Parameter Direction Type Description
smid in SSFSMId_t State machine identifier from SSFSMList_t. Must be between SSF_SM_MIN+1 and SSF_SM_MAX-1.
initial in SSFSMHandler_t Pointer to the initial state handler function. Must not be NULL.

Returns: Nothing.

Example:

static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                        SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);

SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);

/* Register state machine; SSF_SM_EVENT_ENTRY is delivered to IdleHandler() immediately */
SSFSMInitHandler(SSF_SM_STATUS_LED, IdleHandler);

/* Superloop */
while (true)
{
    SSFSMTask(NULL);
}

void SSFSMDeInitHandler(SSFSMId_t smid);

Unregisters the state machine identified by smid and cancels all its pending timers and events. After this call the slot may be re-registered with SSFSMInitHandler().

Parameter Direction Type Description
smid in SSFSMId_t State machine identifier to unregister.

Returns: Nothing.

Example:

static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                        SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);

SSFSMInitHandler(SSF_SM_STATUS_LED, IdleHandler);
/* ... */
SSFSMDeInitHandler(SSF_SM_STATUS_LED);
/* State machine unregistered; pending timers and events cancelled */

bool SSFSMTask(SSFSMTimeout_t *nextTimeout);

Processes all pending events and fires any expired timers by invoking the appropriate state handlers. Must be called repeatedly from the framework's execution context.

Parameter Direction Type Description
nextTimeout out SSFSMTimeout_t * Receives the number of system ticks until the next timer expires, or SSF_SM_MAX_TIMEOUT if no timers are running. Pass NULL in single-threaded superloop builds.

Returns: true if there are additional pending events that should be processed immediately (call SSFSMTask() again without waiting); false when no more events are queued and the caller may safely block for up to *nextTimeout ticks.

Example:

/* Single-threaded superloop */
while (true)
{
    SSFSMTask(NULL);
}

/* Multi-threaded: FSM thread blocks between calls using the wake primitive */
SSFSMTimeout_t to;
while (true)
{
    if (SSFSMTask(&to) == false)
    {
        /* No more pending events — block until next timer or SSFSMPutEvent() wakes us */
        SSF_SM_THREAD_WAKE_WAIT(to);
    }
}

void SSFSMPutEventData(SSFSMId_t smid, SSFSMEventId_t eid, const SSFSMData_t *data,
                       SSFSMDataLen_t dataLen);

Enqueues an event for state machine smid. If data is non-NULL, up to dataLen bytes are copied into the event queue entry and delivered to the handler as the data/dataLen parameters. May be called from any context when SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1.

Parameter Direction Type Description
smid in SSFSMId_t Target state machine identifier.
eid in SSFSMEventId_t Event identifier from SSFSMEventList_t. Must not be SSF_SM_EVENT_ENTRY, SSF_SM_EVENT_EXIT, or SSF_SM_EVENT_SUPER.
data in const SSFSMData_t * Pointer to the event data payload. Pass NULL when there is no data.
dataLen in SSFSMDataLen_t Number of bytes of event data. Must be 0 when data is NULL.

Returns: Nothing.

Example:

/* Post an event carrying a 16-bit received-length payload */
uint16_t rxLen = 42u;
SSFSMPutEventData(SSF_SM_STATUS_LED, SSF_SM_EVENT_RX_DATA,
                  (SSFSMData_t *)&rxLen, (SSFSMDataLen_t)sizeof(rxLen));

#define SSFSMPutEvent(smid, eid) SSFSMPutEventData(smid, eid, NULL, 0)

Convenience macro for posting an event without a data payload. Expands to SSFSMPutEventData() with NULL and 0 for the data parameters. All threading constraints of SSFSMPutEventData() apply.

Example:

/* Post an event without a data payload */
SSFSMPutEvent(SSF_SM_STATUS_LED, SSF_SM_EVENT_RX_DATA);

void SSFSMTran(SSFSMHandler_t next);

Requests a transition to the state handler next. Valid only when called from within a state handler during event processing. The framework delivers SSF_SM_EVENT_EXIT to the current state (and its parent if applicable), stops all running timers for the machine, then delivers SSF_SM_EVENT_ENTRY to next (and its parent if applicable). Must not be called from within the SSF_SM_EVENT_ENTRY or SSF_SM_EVENT_EXIT event cases.

Parameter Direction Type Description
next in SSFSMHandler_t Pointer to the target state handler function. Must not be NULL.

Returns: Nothing.

Example:

static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                         SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);

static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                        SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_ENTRY:
        /* Turn LED off on entry */
        break;
    case SSF_SM_EVENT_EXIT:
        break;
    case SSF_SM_EVENT_RX_DATA:
        /* Transition: EXIT fires for IdleHandler, ENTRY fires for BlinkHandler */
        SSFSMTran(BlinkHandler);
        break;
    default:
        break;
    }
}

void SSFSMStartTimerData(SSFSMEventId_t eid, SSFSMTimeout_t interval,
                         const SSFSMData_t *data, SSFSMDataLen_t dataLen);

Starts or restarts a timer that posts event eid to the owning state machine after interval system ticks. If a timer for eid is already running, it is restarted with the new interval and data. An interval of 0 posts the event at the next SSFSMTask() call. Valid only when called from within a state handler.

Parameter Direction Type Description
eid in SSFSMEventId_t Event to post when the timer expires. Must be a user-defined event from SSFSMEventList_t.
interval in SSFSMTimeout_t Delay in system ticks. 0 fires at the next SSFSMTask() call; SSF_SM_MAX_TIMEOUT is the maximum.
data in const SSFSMData_t * Data payload to deliver with the timer event. Pass NULL when there is no data.
dataLen in SSFSMDataLen_t Number of bytes of data. Must be 0 when data is NULL.

Returns: Nothing.

Example:

static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                         SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_ENTRY:
    {
        /* Start a 1-second timer carrying a blink-count payload */
        uint8_t count = 5u;
        SSFSMStartTimerData(SSF_SM_EVENT_BLINK_TIMER, SSF_TICKS_PER_SEC,
                            (SSFSMData_t *)&count, (SSFSMDataLen_t)sizeof(count));
        break;
    }
    default:
        break;
    }
}

#define SSFSMStartTimer(eid, to) SSFSMStartTimerData(eid, to, NULL, 0)

Convenience macro for starting a timer without a data payload. Expands to SSFSMStartTimerData() with NULL and 0 for the data parameters. Valid only when called from within a state handler.

Example:

static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                        SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);

static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                         SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_ENTRY:
        /* Fire immediately at next SSFSMTask(); idle timeout after 10 seconds */
        SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, 0);
        SSFSMStartTimer(SSF_SM_EVENT_IDLE_TIMER,  SSF_TICKS_PER_SEC * 10u);
        break;
    case SSF_SM_EVENT_BLINK_TIMER:
        /* Toggle LED and reschedule for 1-second repeat */
        SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, SSF_TICKS_PER_SEC);
        break;
    case SSF_SM_EVENT_IDLE_TIMER:
        SSFSMTran(IdleHandler);
        break;
    default:
        break;
    }
}

void SSFSMStopTimer(SSFSMEventId_t eid);

Cancels the timer for event eid on the owning state machine if it is running. Has no effect if no timer for eid is active. Valid only when called from within a state handler. Note that SSFSMTran() automatically stops all running timers, so explicit cancellation is only needed when stopping a timer without transitioning.

Parameter Direction Type Description
eid in SSFSMEventId_t Event identifier of the timer to cancel.

Returns: Nothing.

Example:

static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                         SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_RX_DATA:
        /* Cancel the idle timeout; keep blinking */
        SSFSMStopTimer(SSF_SM_EVENT_IDLE_TIMER);
        /* Note: SSFSMTran() would cancel all timers automatically */
        break;
    default:
        break;
    }
}

#define SSF_SM_SUPER(super) *superHandler = (SSFVoidFn_t)super;

Names the parent (super) state handler for the current child state. Must appear in the default case of a child state handler; writes super into the superHandler output parameter of the handler function. When an event is not handled by the child, the framework re-delivers it to the parent. Parent handlers must not call SSF_SM_SUPER(). Valid only when called from within a state handler.

Parameter Description
super The parent state handler function (SSFSMHandler_t). The framework will route unhandled events to this handler.

Example:

static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                        SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);

static void ChildHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                         SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_ENTRY:
        break;
    case SSF_SM_EVENT_EXIT:
        break;
    case SSF_SM_EVENT_RX_DATA:
        /* Handle event specific to this child state */
        break;
    default:
        /* Route all other events to ParentHandler for processing */
        SSF_SM_SUPER(ParentHandler);
        break;
    }
}

static void ParentHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                          SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_ENTRY:
        break;
    case SSF_SM_EVENT_EXIT:
        break;
    case SSF_SM_EVENT_BLINK_TIMER:
        /* Shared behavior: handled for any child that names ParentHandler as super */
        SSFSMTran(IdleHandler);
        break;
    default:
        /* Parent handlers must NOT call SSF_SM_SUPER() */
        break;
    }
}

#define SSF_SM_EVENT_DATA_ALIGN(v) { \
    SSF_ASSERT((sizeof(v) >= dataLen) && (data != NULL)); \
    memcpy(&(v), data, dataLen); }

Safely copies the event data payload into a typed variable v. Asserts that sizeof(v) >= dataLen and that data is non-NULL. Accesses data and dataLen from the enclosing state handler's parameter scope; valid only when called from within a state handler.

Parameter Description
v A variable whose address receives the event data via memcpy. Must be large enough to hold dataLen bytes.

Example:

static void BlinkHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                         SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);

static void IdleHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                        SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_RX_DATA:
    {
        uint16_t rxLen;
        /* Safely copy the event data payload into a typed variable */
        SSF_SM_EVENT_DATA_ALIGN(rxLen);
        /* rxLen now holds the value posted via SSFSMPutEventData() */
        SSFSMTran(BlinkHandler);
        break;
    }
    default:
        break;
    }
}

<a id="detailed-example"></a>

## [↑](#ssfsm--finite-state-machine-framework) Detailed Example

This example builds a two-state LED blinker from scratch. The RED state blinks a RED LED at
1 Hz; the GREEN state blinks a GREEN LED at 2 Hz. A `GOOD` event moves the machine from RED
to GREEN, and a `BAD` event moves it back. The system starts in the RED state.

### Configuration (`ssfoptions.h`)

Two enumerations are required. `SSFSMList_t` names every state machine instance in the systemhere just one, `SSF_SM_LED`. `SSFSMEventList_t` declares every event. The first three
entries (`ENTRY`, `EXIT`, `SUPER`) are mandatory framework events; the three that follow are
application-defined:

- `SSF_SM_EVENT_BLINK_TIMER` — the periodic half-period tick used by both states to drive the
  LED toggle
- `SSF_SM_EVENT_GOOD` — posted from outside the state machine to trigger the REDGREEN
  transition
- `SSF_SM_EVENT_BAD` — posted from outside to trigger the GREENRED transition

```c
typedef enum
{
    SSF_SM_MIN = -1,
    SSF_SM_LED,          /* single LED blinker instance */
    SSF_SM_MAX
} SSFSMList_t;

typedef enum
{
    SSF_SM_EVENT_MIN = -1,
    SSF_SM_EVENT_ENTRY,          /* required: delivered on state entry */
    SSF_SM_EVENT_EXIT,           /* required: delivered on state exit */
    SSF_SM_EVENT_SUPER,          /* required: used internally for hierarchy */
    SSF_SM_EVENT_BLINK_TIMER,    /* half-period tick; drives LED toggle */
    SSF_SM_EVENT_GOOD,           /* external trigger: RED -> GREEN */
    SSF_SM_EVENT_BAD,            /* external trigger: GREEN -> RED */
    SSF_SM_EVENT_MAX
} SSFSMEventList_t;

State Handlers

Each state is a plain C function with the SSFSMHandler_t signature. The framework calls it with the current event ID; the switch dispatches to the appropriate case.

A 1 Hz blink toggles every 500 ms (one half-period). A 2 Hz blink toggles every 250 ms. Both states reuse SSF_SM_EVENT_BLINK_TIMER — this is safe because SSFSMTran() automatically cancels all running timers before the EXIT/ENTRY sequence, so a timer from the departing state can never fire in the arriving state.

/* ledblink.c */
#include "ssf.h"

/* Half-periods for each blink rate (assumes SSF_TICKS_PER_SEC == 1000):
 *   1 Hz  ->  500 ms half-period  (RED)
 *   2 Hz  ->  250 ms half-period  (GREEN)                                */
#define RED_HALF_PERIOD_TICKS   (SSF_TICKS_PER_SEC / 2u)
#define GREEN_HALF_PERIOD_TICKS (SSF_TICKS_PER_SEC / 4u)

/* Forward declarations so each handler can name the other as a transition target */
static void BlinkRedHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                            SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);
static void BlinkGreenHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                              SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler);

/* ── RED state: blinks RED LED at 1 Hz ─────────────────────────────── */
static void BlinkRedHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                            SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_ENTRY:
        /* State just became active. Turn the RED LED on and arm the half-period
           timer. The first BLINK_TIMER event will arrive in 500 ms, toggling the
           LED off, giving a symmetric 1 Hz square wave. */
        HalRedLedOn();
        SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, RED_HALF_PERIOD_TICKS);
        break;
    case SSF_SM_EVENT_EXIT:
        /* State is leaving. Extinguish the LED so the hardware is dark during
           the transition; the arriving state's ENTRY will control it from there. */
        HalRedLedOff();
        break;
    case SSF_SM_EVENT_BLINK_TIMER:
        /* Half-period elapsed: toggle the LED and immediately re-arm the timer
           for the next half-period. Repeats indefinitely at 1 Hz. */
        HalRedLedToggle();
        SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, RED_HALF_PERIOD_TICKS);
        break;
    case SSF_SM_EVENT_GOOD:
        /* External caller signals a GOOD condition. SSFSMTran() fires
           SSF_SM_EVENT_EXIT here then SSF_SM_EVENT_ENTRY in BlinkGreenHandler. */
        SSFSMTran(BlinkGreenHandler);
        break;
    default:
        break;
    }
}

/* ── GREEN state: blinks GREEN LED at 2 Hz ──────────────────────────── */
static void BlinkGreenHandler(SSFSMEventId_t eid, const SSFSMData_t *data,
                              SSFSMDataLen_t dataLen, SSFVoidFn_t *superHandler)
{
    switch (eid)
    {
    case SSF_SM_EVENT_ENTRY:
        /* Turn the GREEN LED on and arm the 250 ms half-period timer for 2 Hz. */
        HalGreenLedOn();
        SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, GREEN_HALF_PERIOD_TICKS);
        break;
    case SSF_SM_EVENT_EXIT:
        /* Extinguish the GREEN LED on departure. */
        HalGreenLedOff();
        break;
    case SSF_SM_EVENT_BLINK_TIMER:
        /* Toggle and re-arm for a continuous 2 Hz square wave. */
        HalGreenLedToggle();
        SSFSMStartTimer(SSF_SM_EVENT_BLINK_TIMER, GREEN_HALF_PERIOD_TICKS);
        break;
    case SSF_SM_EVENT_BAD:
        /* External caller signals a BAD condition: return to the RED state. */
        SSFSMTran(BlinkRedHandler);
        break;
    default:
        break;
    }
}

Initialization and Run Loop

void LedBlinkInit(void)
{
    /* Initialize the framework with the pool sizes declared in ssfoptions.h. */
    SSFSMInit(SSF_SM_MAX_ACTIVE_EVENTS, SSF_SM_MAX_ACTIVE_TIMERS);

    /* Register the LED state machine. SSFSMInitHandler() delivers
       SSF_SM_EVENT_ENTRY to BlinkRedHandler immediately before returning,
       so the RED LED is already on and the first timer is already armed
       by the time this function returns. */
    SSFSMInitHandler(SSF_SM_LED, BlinkRedHandler);
}

/* Single-threaded superloop — call from main() after LedBlinkInit() */
void LedBlinkRun(void)
{
    while (true)
    {
        SSFSMTask(NULL);
    }
}

SSFSMTask() checks the event queue and fires any expired timers on every call. In a single-threaded build it can be called as fast as the loop allows; the framework only invokes a handler when there is actually something to deliver.

Triggering Transitions

From anywhere in the application — a button ISR, a network callback, or another state machine — posting one of the external events causes the next SSFSMTask() call to deliver it:

/* Signal a GOOD condition; the machine will leave the RED state on the next task call */
SSFSMPutEvent(SSF_SM_LED, SSF_SM_EVENT_GOOD);

/* Signal a BAD condition; the machine will leave the GREEN state on the next task call */
SSFSMPutEvent(SSF_SM_LED, SSF_SM_EVENT_BAD);

SSFSMPutEvent() only enqueues the event — it does not invoke the handler directly. The actual transition, including the EXIT and ENTRY calls, happens inside the next SSFSMTask() invocation. When SSF_CONFIG_ENABLE_THREAD_SUPPORT == 1, SSFSMPutEvent() is safe to call from any execution context, including interrupt service routines.

Event Sequence Walkthrough

The table below traces every framework action from power-on through one complete RED → GREEN → RED cycle. Steps 2–3 and 7–8 repeat at their respective rates until an external event arrives.

Step Event delivered Handler Action
1 SSF_SM_EVENT_ENTRY BlinkRedHandler RED LED on; 500 ms timer started
2 SSF_SM_EVENT_BLINK_TIMER BlinkRedHandler RED LED off; 500 ms timer restarted
3 SSF_SM_EVENT_BLINK_TIMER BlinkRedHandler RED LED on; 500 ms timer restarted
4 SSF_SM_EVENT_GOOD (external) BlinkRedHandler SSFSMTran(BlinkGreenHandler) called
5 SSF_SM_EVENT_EXIT BlinkRedHandler RED LED off; all timers stopped
6 SSF_SM_EVENT_ENTRY BlinkGreenHandler GREEN LED on; 250 ms timer started
7 SSF_SM_EVENT_BLINK_TIMER BlinkGreenHandler GREEN LED off; 250 ms timer restarted
8 SSF_SM_EVENT_BLINK_TIMER BlinkGreenHandler GREEN LED on; 250 ms timer restarted
9 SSF_SM_EVENT_BAD (external) BlinkGreenHandler SSFSMTran(BlinkRedHandler) called
10 SSF_SM_EVENT_EXIT BlinkGreenHandler GREEN LED off; all timers stopped
11 SSF_SM_EVENT_ENTRY BlinkRedHandler RED LED on; 500 ms timer started

The EXIT step always fires before ENTRY, ensuring neither LED is ever on simultaneously during a transition and that the departing state's blink timer is always cancelled before the arriving state starts its own.