Build Integration
Integrating code generation to project
Ideally, state machine code generation should be integrated into your build process to prevent any need for copy-pasting. This can be achieved using scxml2gen tool. Example of how to do it can be found in /examples/02_generated.
To make invoking scxml2gen during build more convenient a couple of CMake functions are provided:
Function |
Description |
Arguments |
|
---|---|---|---|
Name |
Description |
||
generateHsm |
Generates hpp and cpp file in destDirectory. |
genTarget |
new target name (used later for add_dependencies() call) |
scxml |
path to scxml file |
||
className |
class name to use when generating code (default suffix will be added) |
||
destDirectory |
path to directory where to save generated files |
||
outSrcVariableName |
name of the variable where to store path to generated cpp file |
||
generateHsmEx |
Extended version of generateHsm which allows to provide custom template and destination files path |
genTarget |
new target name (used later for add_dependencies() call) |
scxml |
path to scxml file |
||
className |
class name to use when generating code (default suffix will be added) |
||
classSuffix |
suffix to append to class name when generating code |
||
destDirectory |
path to directory where to save generated files |
||
templateHpp templateCpp |
path to HPP and CPP templates |
||
destHpp destCpp |
destination path for generated HPP and CPP files |
||
generateHsmDiagram |
Generates PlantUML state diagram. |
genTarget |
new target name (used later for add_dependencies() call) |
scxml |
path to scxml file |
||
destFile |
path to file where to save generated diagram |
They will be automatically available to you when including root CMakeLists.txt file in your project.
Here is an example of CMake script to build generate and build a simple HSM. Important points here are:
using add_dependencies(). generateHsm() just and a custom target, so without anyone depending on it nothing will be generated.
the last argument to generateHsm() must be a string with a variable name (not variable itself!).
adding generated cpp file to your executable.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${HSMCPP_CXX_FLAGS}")
# ======================================================
if (HSMBUILD_DISPATCHER_STD)
set(BINARY_NAME_02 "02_generated")
# create folder for generated files
set(GEN_DIR ${CMAKE_BINARY_DIR}/gen)
file(MAKE_DIRECTORY ${GEN_DIR})
generateHsm(GEN_02_HSM ./02_generated.scxml "SwitchHsm" ${GEN_DIR} "GEN_OUT_SRC")
add_executable(${BINARY_NAME_02} 02_generated.cpp ${GEN_OUT_SRC})
add_dependencies(${BINARY_NAME_02} GEN_02_HSM)
target_include_directories(${BINARY_NAME_02}
PRIVATE
${HSMCPP_STD_INCLUDE}
${CMAKE_BINARY_DIR}
)
target_link_libraries(${BINARY_NAME_02} PRIVATE ${HSMCPP_STD_LIB})
target_compile_options(${BINARY_NAME_02} PRIVATE ${HSMCPP_STD_CXX_FLAGS})
endif()
Implementation itself is very similar to a Hello World! example, but now we don’t need to manually register HSM structure.
Suggestion:
use override keyword for callbacks. This will save you a lot of effort if callback gets renamed/removed from HSM definition.
you can use protected inheritance for generated class (SwitchHsmBase) if you want to prevent clients (of SwitchHsm) to directly access HSM API.
you can generate your files anywhere, but doing it inside your build folder will prevent you from accidentally submitting them.
#include <chrono>
#include <hsmcpp/HsmEventDispatcherSTD.hpp>
#include <thread>
#include "gen/SwitchHsmBase.hpp"
using namespace hsmcpp;
class SwitchHsm : public SwitchHsmBase {
public:
virtual ~SwitchHsm() {}
// HSM state changed callbacks
protected:
void onOff(const VariantVector_t& args) override {
(void)printf("Off\n");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
transition(SwitchHsmEvents::SWITCH);
}
void onOn(const VariantVector_t& args) override {
(void)printf("On\n");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
transition(SwitchHsmEvents::SWITCH);
}
};
int main(const int argc, const char** argv) {
std::shared_ptr<HsmEventDispatcherSTD> dispatcher = HsmEventDispatcherSTD::create();
SwitchHsm hsm;
if (true == hsm.initialize(dispatcher)) {
hsm.transition(SwitchHsmEvents::SWITCH);
dispatcher->join();
}
return 0;
}
Generating PlantUML diagrams
PlantUML is an amazing tool that allows creating a lot of different diagram types using text files. Since I couldn’t find any way to automatically generate images based on SCXML or export them to PlantUML format I added additional functionality to scxml2gen application.
To generate a PlantUML file from SCXML simply call:
python3 ./tools/scxml2gen/scxml2gen.py -plantuml -s ./tests/scxml/multilevel.scxml -o ./multilevel.plantuml
You can also use CMake function generateHsmDiagram() to do it automatically during build. You can check example of its usage in /examples/04_history/CMakeLists.txt.