Generating C++ Code#
Sinelabore supports C++ code generation. By default, the code generator uses only a subset of C++ to account for resource-constrained systems. In fact, only classes, public/protected/private member variables and methods are used. For more powerful systems, code generation using features of C++11 is supported if enabled (–std=c++11 for g++ or clang++). The various parameters allow you to customise the generated code to suit your needs. The quality of the generated code has been proven in practice by my many demanding customers using various lint-like tools without any problems.
To generate C++ code call the code generator with the command line flag -l cppx.
The generated code does not follow the state pattern as you might expect (if you are familiar with common design patterns). The reason is that the machine code is completely generated and no hand-coding is involved. The following class diagram shows the structure of the generated classes.
The code generator supports code generation from models with regions and all other relevant state machine features. An example of a simplified microwave oven model is shown below. Regions allow the expression of parallelism within a state. A state may have two or more regions. A region contains states and transitions. Imagine how much effort it would be to write the code for this machine by hand. And even worse, how difficult it would be to maintain it over time as new states/events are added.
The signature of the event handler can be adapted to your own needs in a flexible way:
Basic signature:
int processEvent(const TESTCASE_EVENT_T msg);Using a template parameter that allows elegant and flexible parameter passing:
template <typename T>
int processEvent(const TESTCASE_EVENT_T msg, const T& userdata);Without any parameter:
int processEvent(void);A template parameter can also be the only argument. The event is then a member of that argument. CppxTemplateMsgAccessor names it. The default is userdata.msg.
template <typename T>
int processEvent(const T& userdata);Events that carry data#
An event can carry named fields. The fields belong to the event name, so every transition on that name uses the same fields. The type is C++ type text, such as unsigned or const char*, and is copied into the generated struct as written. The model’s header includes whatever that type needs. An event with no fields stays an enumerator.
In the built-in editor the Event Overview shows the fields after the event name. Edit payload… opens the table of names and types. The transition dialog shows that payload under the event text, and Edit… opens the same table. A conditional event, marked with the checkbox or a leading #, carries no fields.
The diagram file stores the table. Each field has a name and a type. The name is an identifier, unique for that event. Avoid C++ keywords and the names the generated code uses itself: id, data, msg and evConsumed. Generation does not check them; the compiler reports a clash.
The generator writes one struct per event that has fields, and one message struct. The id member is id. Each such event is one member of the union, so the fields of that event are live together. With UseEnumBaseTypes the enumerator is TESTCASE_EVENT_T::ev3.
struct TESTCASE_EV3_T {
unsigned no;
float val;
};
struct TESTCASE_MSG_T {
TESTCASE_EVENT_T id;
union {
TESTCASE_EV3_T ev3;
} data;
};Inside the event branch the generator binds every field, and a guard, an action, or a choice the transition enters uses the name:
if (msg.id == ev3) {
const auto no = msg.data.ev3.no;
const auto val = msg.data.ev3.val;
if (no == 4) {
/* ... */
}
}Entry, exit and do code also run on other paths, such as initialization and history. A value they need is stored from the transition action. A region handler sees the same message.
The four signatures above keep their shape. The event value is the message.
TESTCASE_MSG_T msg{};
msg.id = ev3;
msg.data.ev3.no = 4;
msg.data.ev3.val = 1.5f;
machine.processEvent(msg);machine.processEvent(msg, context);struct Packet { TESTCASE_MSG_T msg; };
Packet packet{};
packet.msg = msg;
machine.processEvent(packet);machine.msg = msg;
machine.processEvent();Command-line parameter -Trace enables trace code generation.
All details about the C++ backend are available in section “Generating C++ Code” of the manual.
Model Example and Generating Code#
Assume that C++11 features are to be used to generate the code for the oven example shown. The following parameters have been set in the configuration file according to the requirements.
UseEnumBaseTypes = YES
EnumBaseTypeForEvents = std::uint16_t
EnumBaseTypeForStates = std::uint32_t
ReturnAndProcessEventTypeOfStateMachine = std::uint16_t
InitializedFlagTypeOfStateMachine = std::uint16_t
VisibilityInitializedVar = private:
VisibilityStateVars = private:
CallInitializeInCtor = yes
NotUseRedundantVoidArgument=YES
EnableTrailingReturnType=YES
UseStdLibrary = YESThe usual example state diagram modeling a hierarchical microwave oven is shown below. Imagine the effort to manually write the code for this machine. And even worse how difficult it would be to maintain it over time when new states/events are added.

The complete code is available in the examples folder on our GitHub site.
The generated code does not rely on any library. It can be easily integrated into different system designs. An example is shown below. In this case, the state machine runs in a separate thread and receives events via an event queue. This is a typical usage pattern in embedded real-time systems.
See how easy it is to integrate the generated state machine code into an RTOS task. Depending on the RTOS, the framework code will always look a bit different.
std::thread eventHandlerThread() {
my_oven* machine = new my_oven();
machine->initialize();
while (true) {
OVEN_EVENT_T msg;
q.waitAndPop(msg);
machine->processEvent(msg);
}
}Showing the state on a PC#
The code generator can show where a machine running on your target is. Started with -V, it draws the state diagram and highlights the states the target reports. A report is a short text:
from=oven;state=Cooking,LampOnfrom is the name of the machine, as the viewer knows it: its model file name, or the name given with -SIN. state lists the innermost active states, one per active region. The generated class provides both parts: getInnermostActiveStates() returns the states, and getNameByState() their names.
#include <string>
#include "oven_ext.h"
#include "oven.h"
// Your transport to the PC: UDP, UART, CAN, ...
void send(const char* text);
// Report the innermost active states. Call it after each processEvent().
void reportState(const oven& machine)
{
std::string report = "from=oven;state=";
const char* separator = "";
for (auto state : machine.getInnermostActiveStates()) {
report += separator;
report += machine.getNameByState(state);
separator = ",";
}
send(report.c_str());
}This is the form for UseForwardListForInnermostStates=yes, where getInnermostActiveStates() returns a std::forward_list. Otherwise it returns a std::pair of a fixed-size array and the number of states in it:
const auto active = machine.getInnermostActiveStates(); // states, count
for (std::size_t i = 0; i < active.second; ++i) {
if (i > 0) {
report += ",";
}
report += machine.getNameByState(active.first[i]);
}In the event loop above, the report follows each event:
machine->processEvent(msg);
reportState(*machine);Each report is complete, so the viewer is right again after the next one. Send a report after every state change, and best also periodically, so a lost message or a restart of the PC does not leave a wrong picture.
On the PC, the viewer receives the reports over UDP. A target with a network interface sends them there directly; otherwise a small program on the PC receives them over the serial or CAN link and forwards each report as one UDP datagram. The settings in codegen.cfg and the start of the viewer are described with the integrated editor:
java -jar codegen.jar -V -p ssc -o oven oven.xmlSummary#
The C++ backend has proven itself with many users and guarantees consistently high code quality through the generator. You can fully concentrate on the respective application. Download the demo and try out the microwave_ea9_using_regions_cpp11 example.