Generating CPP 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 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
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.
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.