Sinelabore Homepage

Design Patterns for Code Generation#

Good generated code starts with deliberate design decisions — not with tweaking the code generator afterwards. This page collects the patterns that matter most when working with SinelaboreRT. Use it as a quick reference when planning a new state machine or reviewing an existing model.

The patterns below are not classical GoF design patterns (Strategy, Observer, …). Sinelabore replaces many of them by generating the state machine logic directly. Instead, this guide covers three layers:

  1. Architecture patterns — how state machines integrate into your system
  2. Code generation patterns — how generated code is structured and configured
  3. UML modeling patterns — how the state diagram should be built

For background on architecture alternatives see also the Designers Toolbox overview. For UML element details see State Machines in a Nutshell.

Decision Guide#

Use this flowchart to pick an architecture pattern first, then refine code generation and modeling choices.

flowchart TD
    Start([New state machine design]) --> RTOS{RTOS available?}
    RTOS -->|Yes| AO[RTOS Active Object<br/>one task per state machine]
    RTOS -->|No| LP{Low power required?}
    LP -->|Yes| LPE[Low-Power Event-Driven<br/>sleep until event or timeout]
    LP -->|No| IRQ{Run inside IRQ?}
    IRQ -->|Yes| IRQSM[State Machine in Interrupt]
    IRQ -->|No| Queue{Events from IRQ,<br/>timers, or other SMs?}
    Queue -->|Yes| MLQ[Main Loop + Event Queue]
    Queue -->|No| ML[Main Loop Polling]

    AO --> Inst{Multiple instances<br/>of same machine?}
    MLQ --> Inst
    LPE --> Inst
    ML --> Inst
    IRQSM --> Single[Single Instance pattern]

    Inst -->|Yes| Obj[Object pattern]
    Inst -->|No| Single
    Obj --> Hide{Hide implementation<br/>from other modules?}
    Single --> Hide
    Hide -->|Yes| Opq[Opaque Object pattern]
    Hide -->|No| Done([Configure backend<br/>and model transitions])
    Opq --> Done

1. Architecture Integration Patterns#

These patterns describe how generated state machines are called, how events arrive, and how timing is handled. Detailed examples and code snippets are in the Designers Toolbox overview.

Pattern When to use Key benefit Further reading
Main Loop (Polling) Small bare-metal systems, minimal RAM, no runtime framework Simple integration, low overhead Main loop section
Main Loop + Event Queue Events from IRQ handlers, timers, or cooperating state machines Event order preserved, decoupling of producer and consumer Event queue section
Low-Power Event-Driven Battery-powered MCUs (MSP430, PIC, …) CPU sleeps until work is pending MSP430, PIC
State Machine in IRQ Time-critical preprocessing (UART byte handling, signal filtering) Minimum latency IRQ section
RTOS Active Object Systems with FreeRTOS, embOS, RTEMS, VxWorks, … Scheduling, prioritization, proven runtime FreeRTOS, embOS, VxWorks
Cooperating State Machines Several machines exchange events (e.g. vending + storage) Modular design, clear responsibilities Event queue example
Time-Triggered Events Cyclic tasks, timeouts, signal patterns Predictable timing behavior Time-triggered events

Pattern notes#

Main Loop + Event Queue is the most versatile bare-metal pattern. Each state machine should have its own queue. IRQ handlers and timer callbacks push events; the main loop pulls and dispatches them.

RTOS Active Object maps one state machine to one task. The task blocks on a message queue until an event arrives, then calls the generated state machine function. The pattern is RTOS-independent — only queue and task APIs differ.

State Machine in IRQ requires backend configuration without instance parameters. Set StateMachineFunctionPrefixCFile and HsmFunctionWithInstanceParameters=no as described in the IRQ section.


2. Code Generation Patterns#

These patterns are selected through backend configuration (codegen.cfg). They apply primarily to the C backend but similar concepts exist for C++, C#, and other targets.

Pattern When to use Generated API shape Configuration hint
Object Pattern Multiple instances of the same state machine (channels, devices, sessions) void sm(INSTANCEDATA_T *inst, EVENT_T evt) Default for multi-instance designs
Single Instance Pattern Exactly one machine (IRQ handler, singleton hardware module) void sm(EVENT_T evt) HsmFunctionWithInstanceParameters=no
Opaque Object Pattern Hide state machine internals from other modules Public header exposes only init/call API; struct details hidden See C backend — Opaque object
Event-only Parameter Simple control logic, events carry no payload Event enum as function parameter Standard event dispatch
User Event Struct Event plus attached data (measurement value, command parameters) void sm(INSTANCEDATA_T *inst, USER_EVENT_T *data) Custom event struct in configuration
External Events Reuse legacy event definitions outside the UML model Events defined in hand-written headers Supported by C backend
Event-Driven Mode Classic reactive control (button pressed, door closed, …) Transitions triggered by discrete events Default operating mode
Condition-Driven Mode Boolean signal processing, PLC-style function blocks Transitions triggered by boolean expressions No dedicated event required; see signal forming blocks

Example: Object vs. Single Instance#

/* Object pattern — multiple instances possible */
void oven(OVEN_INSTANCEDATA_T *instanceVar, OVEN_EVENT_T msg);

OVEN_INSTANCEDATA_T ovenA = OVEN_INSTANCEDATA_INIT;
OVEN_INSTANCEDATA_T ovenB = OVEN_INSTANCEDATA_INIT;
oven(&ovenA, evStart);
oven(&ovenB, evStart);

/* Single instance — typical for IRQ context */
#pragma vector=UART0TX_VECTOR
__interrupt void uart_irq(void) {
    uart_sm(evRxByte);
}

Generate a full configuration template with:

java -cp path_to_bin_folder/* codegen.Main -l cx -gencfg > codegen.cfg

3. UML Modeling Patterns#

How you draw the state diagram directly affects code size, readability, and correctness of the generated output. See State Machines in a Nutshell for syntax details.

Pattern Purpose Modeling rule
Hierarchy instead of duplication Share entry/exit behavior and transitions across similar modes Place common behavior in a superstate; specialize in substates
Default State in Composites Unambiguous startup inside nested states Every composite state needs exactly one default (initial) substate
Entry / Exit actions One-time setup and teardown when entering or leaving a state Use for hardware init, LED on/off, resource allocation
Do activity Continuous evaluation while a state is active Runs before transition guards are checked; keep it short
Inner events Handle events without re-running entry/exit Use when the state context must not change
Choice pseudostate Runtime branching without a dedicated event Startup self-test, mode selection; always provide an else path
Init-to-Choice Determine initial substate at runtime Connect initial pseudostate to a choice; see choices section
History state Resume previous substate after interruption Use when returning to a composite must restore context
Regions Orthogonal (parallel) behavior within one state Independent substates run concurrently; see regions section
Guard on transition Conditional reaction to an event Syntax: event[guard]/action — prefer guards over extra states
Junction Shared action on converging transitions Place common action on the single outgoing transition
Final state Explicit non-reactive end state Machine stops reacting; re-init required to restart
Timer / after / at-time Timeouts and periodic behavior See time-triggered events

Transition syntax reminder#

Event-based transition:

evDoorClosed[timer_preset() > 0]/timer_start()

Condition-based transition (no event):

[DI0 == true]/led_on()

Modeling depth#

The code generator is tested with up to four hierarchy levels. If your diagram goes deeper, reconsider the design — flatten or split into cooperating state machines.

Keep all actions (entry, exit, do, transition) short and non-blocking. Long-running work belongs outside the state machine, triggered by an action that starts it and completed via a callback event.


4. Application Patterns (Examples)#

These real-world examples combine architecture, backend, and modeling choices. Use them as templates for your own designs.

Application Patterns used Example
Room / light control Event-driven, main loop or RTOS Light controller, Room temperature
Industrial I/O Condition-driven, boolean signals Signal forming blocks, PLCOpen block
Protocol / timing Timer events, hierarchical states DCF77 radio clock
Multi-machine coordination Event queues, cooperating SMs Sump controller
GUI / HMI Event-driven, periodic refresh Simple GUI design
High availability Error detection, consistency checks High availability
Model-based testing Trace output, test automation Model-based testing
STM32 / CubeMX RTOS active object with CMSIS-RTOS CubeMX integration

5. Anti-Patterns#

Avoid these common mistakes. They lead to bloated generated code, subtle runtime bugs, or models that are hard to maintain.

Anti-pattern Problem Better approach
Deep hierarchy (>4 levels) Hard to read, untested nesting depth Flatten or split into cooperating machines
Blocking actions in entry/do/transition Missed deadlines, IRQ issues, RTOS starvation Start async work, complete via callback event
One state per boolean signal State explosion, unnecessary entry/exit Use guards on transitions or condition-driven mode
Missing default state in composite Undefined startup behavior Always set initial substate
Missing else on choice No guaranteed path at runtime Add default branch on every choice
Events posted from IRQ without queue safety Lost or corrupted events Use IRQ-safe queue primitives; one queue per SM
Global variables instead of instance data Not re-entrant, breaks multi-instance Use Object pattern with instance struct
Duplicated transitions across substates Larger code, inconsistent behavior Move shared logic to superstate
Long do-activities Transition evaluation delayed Poll in do, defer heavy work to main context
Chain of choices Not supported by the code generator Restructure with separate choices

Run the built-in model check before code generation to catch structural issues early. For static analysis of generated C/C++ code see Static Source Code Checkers.


Quick Checklist#

Before generating code, verify:

  • Architecture pattern chosen (main loop, queue, RTOS, IRQ, low power)
  • Backend pattern matches instance count (Object, Single Instance, Opaque)
  • Event mode matches problem (event-driven vs. condition-driven)
  • Composite states have default substates
  • Choices have an else path
  • Actions are short and non-blocking
  • Hierarchy depth ≤ 4 levels
  • Model check passed

This page is a living catalog. As new backend features and examples are added, corresponding patterns will be documented here.