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:
- Architecture patterns — how state machines integrate into your system
- Code generation patterns — how generated code is structured and configured
- 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.cfg3. 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
elsepath - 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.