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.