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.

Class diagram that 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 the 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 handover:

template <typename T> int processEvent(const TESTCASE_EVENT_T msg, const T& userdata);

Without any parameter:

int processEvent(void);

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 Generated 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 your needs.

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 = YES

State machine of a microwave oven used as example state machine with regions. Below generated code from this machine is listed.

An excerpt of the generated code is listed here:

oven::oven() { m_initialized=static_cast<std::uint16_t>(0U); initialize(); } /* State names */ const std::array<std::string, 9> stateNames = { \ "Inactive","Active","LightOn","LightOff","HighPower","LowPower","CookingPause","Cooking","RadiatorOff"}; /* Event names */ const std::array<std::string, 8> eventNames = { \ "evPwrLow","evTimeout","evDec","evDoorOpen","evDoorClosed","evPwrHigh","evInc","NO_MSG"}; auto oven::getNameByState(const States state) const -> std::string { return (static_cast<const std::string>(stateNames[static_cast<std::uint32_t>(state)])); } auto oven::getNameByEvent(const OVEN_EVENT_T evt) const -> std::string { return (static_cast<const std::string>(eventNames[static_cast<std::uint16_t>(evt)])); } // Initialize method. Must be called once to init the machine void oven::initialize(){ //call on entry code of default states if(m_initialized==static_cast<std::uint16_t>(0U)){ m_initialized=static_cast<std::uint16_t>(1U); //Create copy of statevar stateVarsCopy = stateVars; // Set state vars to default states stateVarsCopy.stateVar = States::Inactive; /* set init state of top state */ stateVarsCopy.stateVarLight = States::LightOn; /* set init state of Light */ stateVarsCopy.stateVarPower = States::LowPower; /* set init state of Power */ stateVarsCopy.stateVarRadioator = States::RadiatorOff; /* set init state of Radioator */ timer.create(OVEN_EVENT_T::evTimeout); uint8_t testResult = hardwareTest(); if(testResult==0){ stateVarsCopy.stateVar = States::Active; }else{ stateVarsCopy.stateVar = States::Inactive; } // Copy state variables back stateVars = stateVarsCopy; } } // State machine event handler auto oven::processEvent(const OVEN_EVENT_T msg) -> std::uint16_t { auto evConsumed = static_cast<std::uint16_t>(0U); //Create copy of statevar stateVarsCopy = stateVars; switch (stateVars.stateVar) { case States::Active: /* calling region code */ evConsumed |= ovenLight(msg); evConsumed |= ovenPower(msg); evConsumed |= ovenRadioator(msg); /* Check if event was already processed */ if(evConsumed==static_cast<std::uint16_t>(0)){ if(msg==OVEN_EVENT_T::evDec){ /* Transition from Active to Active */ validationHandler(States::Active, States::Active); /* Action code for transition */ timer.dec(); /* adjust state variables */ stateVarsCopy.stateVar = States::Active; }else if(msg==OVEN_EVENT_T::evInc){ /* Transition from Active to Active */ validationHandler(States::Active, States::Active); /* Action code for transition */ timer.inc(); /* adjust state variables */ stateVarsCopy.stateVar = States::Active; }else{ /* Intentionally left blank */ } /*end of event selection */ } break; /* end of case Active */ case States::Inactive: break; /* end of case Inactive */ default: /* Intentionally left blank */ break; } /* end switch stateVar_root */ // Copy state variables back stateVars = stateVarsCopy; return (evConsumed); } // end processEvent /* Region code for state Light */ std::uint16_t oven::ovenLight(OVEN_EVENT_T msg){ std::uint16_t evConsumed = 0U; switch (stateVars.stateVarLight) { case States::LightOff: if(msg==OVEN_EVENT_T::evDoorClosed){ if(timer.preset()>0){ /* Transition from LightOff to LightOn */ validationHandler(States::LightOff, States::LightOn); evConsumed=static_cast<std::uint16_t>(1U); /* Action code for transition */ switchLight(T_LIGHT::L_ON); /* adjust state variables */ stateVarsCopy.stateVarLight = States::LightOn; }else{ /* Intentionally left blank */ } /*end of event selection */ }else{ /* Intentionally left blank */ } /*end of event selection */ break; /* end of case LightOff */ case States::LightOn: if(msg==OVEN_EVENT_T::evDoorOpen){ if(timer.preset()==0){ /* Transition from LightOn to LightOff */ validationHandler(States::LightOn, States::LightOff); evConsumed=static_cast<std::uint16_t>(1U); /* Action code for transition */ switchLight(T_LIGHT::L_OFF); /* adjust state variables */ stateVarsCopy.stateVarLight = States::LightOff; }else{ /* Intentionally left blank */ } /*end of event selection */ }else{ /* Intentionally left blank */ } /*end of event selection */ break; /* end of case LightOn */ default: /* Intentionally left blank */ break; } /* end switch stateVar_root */ return evConsumed; }

The complete code is available in the examples folder of the code generator download.

The generated code does not rely on a 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.

Typical usage pattern of state machines in embedded real-time systems. Machines run in their own thread of control. Communication between machines is realised with queues for decoupling reasons.

std::thread eventHandlerThread() { my_oven* machine = new my_oven(); machine->initialize(); while (true) { OVEN_EVENT_T msg; q.waitAndPop(msg); machine->processEvent(msg); } }

Summary

The C++ backend has proven itself with many users and guarantees the always consistent code quality through the generator. You can fully concentrate on the respective application. Download the demo and tryout the microwave_ea9_using_regions_cpp11 example.