Code Generation - igor-krechetov/hsmcpp Wiki

Original URL: https://github.com/igor-krechetov/hsmcpp/wiki/Code-Generation

Overview

In general it's totally fine to define HSM structure manually in code. But in real projects we often have to deal with:

To help deal with these issues hsmcpp library comes with scxml2gen utility. It uses state machines defined in SCXML format and allows to:

SCXML

From Wikipedia:

SCXML stands for State Chart XML: State Machine Notation for Control Abstraction. It is an XML-based markup language that provides a generic state-machine-based execution environment based on Harel statecharts.

SCXML is able to describe complex finite state machines. For example, it is possible to describe notations such as sub-states, parallel states, synchronization, or concurrency, in SCXML.

SCXML format specification can be found on W3 official website.

Limitations and requirements for SCXML files

SCXML format was designed to describe both structure and implementation inside a single file. But since we are using it only to define structure a lot of SCXML features will be ignored.

Supported SCXML tags and attributes

Any other tags and attributes will be ignored during parsing.

Specifying callbacks

There are 5 types of callbacks that could be defined for HSM:

<state id="state_1">
    <invoke srcexpr="onState1"/>
</state>
<state id="state_1">
    <onentry>
        <script>onEnteringState1</script>
    </onentry>
</state>
<state id="state_1">
    <onexit>
        <script>onExitingState1</script>
    </onexit>
</state>
<state id="state_1">
    <transition event="NEXT_STATE" target="state_2">
        <script>onNextStateTransition</script>
    </transition>
</state>
<state id="state_1">
    <transition event="NEXT_STATE" cond="checkNextStateTransition" target="state_2"/>
</state>

Callback names should comply with C++ identifier naming rules.

SCXML editors

There doesn't seem to be too many available free SCXML editors. Here are the ones worth mentioning:

Editing HSM in scxmlgui

Using Qt Creator

Basics of how to define a state machine in Qt Creator can be checked here:

Now let's go through working with hsmcpp specific features.

Specifying callbacks in editor

There are 4 types of callbacks (see States and Transitions for details):

They are specified as a C++ callback name which will be generated in HSM class.

State changed callback can be specified in state->Invoke->srcexp or state->Invoke->src: State callback

State entering callback can be specified in state->onentry->script->Content: State entering callback

State exiting callback can be specified in state->onexit->script->Content: State exiting callback

Transition callback can be specified in transition->script->Content: State exiting callback

Working with timers and actions

State actions in HSM provide following operations:

Actions can be specified in:

Timer start action

Command format is:

Using any of the above timer actions will result in availability of timer event. Name format: ON_TIMER_<timer_id> Timer transition

Conditional transitions

Condition for transitions can be specified in transition->cond: Conditional transition

Possible formats:

Conditional entry points

Hsmcpp library supports conditional entry points, but defining them through editor directly is not possible.

First, create your substates, define an entry point and add a single transition. Multiple entries

Now you need to save the file and open SCXML file in some text editor (Qt Creator doesn't allow editing SCXML files directly). Find your section. Original XML

Modify it to look like this (ignore qt:editorinfo tag). Add as many transitions as you need. Modified XML

After reloading SCXML file in Qt Creator your HSM should look like this. Conditional entry points

Using scxml2gen

Command line arguments

scxml2gen works in two modes:

To get a list of supported arguments run:

scxml2gen.py -h
usage: scxml2gen.py [-h] (-code | -plantuml) -scxml SCXML [-class_name CLASS_NAME] [-class_suffix CLASS_SUFFIX] [-template_hpp TEMPLATE_HPP] [-template_cpp TEMPLATE_CPP] [-dest_hpp DEST_HPP] [-dest_cpp DEST_CPP]
                    [-dest_dir DEST_DIR] [-out OUT]

State machine code/diagram generator

optional arguments:
  -h, --help            show this help message and exit
  -code                 generate C++ code based on hsmcpp library. Supported arguments: -class_name, -class_suffix, -template_hpp, -template_cpp, -dest_hpp, -dest_cpp, -dest_dir
  -plantuml             generate plantuml state diagram
  -scxml SCXML, -s SCXML
                        path to state machine in SCXML format
  -class_name CLASS_NAME, -c CLASS_NAME
                        class name used in generated code
  -class_suffix CLASS_SUFFIX, -cs CLASS_SUFFIX
                        suffix to append to class name (default: Base)
  -template_hpp TEMPLATE_HPP, -thpp TEMPLATE_HPP
                        path to HPP template file
  -template_cpp TEMPLATE_CPP, -tcpp TEMPLATE_CPP
                        path to CPP template file
  -dest_hpp DEST_HPP, -dhpp DEST_HPP
                        path to file in which to store generated HPP content (default: ClassSuffixBase.hpp)
  -dest_cpp DEST_CPP, -dcpp DEST_CPP
                        path to file in which to store generated CPP content (default: ClassSuffixBase.cpp)
  -dest_dir DEST_DIR, -d DEST_DIR
                        path to folder where to store generated files (ignored if -dest_hpp and -dest_cpp are provided)
  -out OUT, -o OUT      path for storing generated Plantuml file (only for -plantuml)

Code generation example

Let's look at the sample command to generate HSM from SCXML:

python3 ./tools/scxml2gen/scxml2gen.py -code -s ./examples/02_generated/02_generated.scxml -c SwitchHsm -thpp ./tools/scxml2gen/template.hpp -tcpp ./tools/scxml2gen/template.cpp -d ./

This will generate two files:

SwitchHsmBase.hpp

// Content of this file was generated

#ifndef __GEN_HSM_SWITCHHSMBASE__
#define __GEN_HSM_SWITCHHSMBASE__

#include <hsmcpp/hsm.hpp>

enum class SwitchHsmStates
{
    On,
    Off,
};

enum class SwitchHsmEvents
{
    SWITCH,
};

class SwitchHsmBase: public HierarchicalStateMachine<SwitchHsmStates, SwitchHsmEvents>
{
public:
    SwitchHsmBase();
    virtual ~SwitchHsmBase();

protected:
    void configureHsm();

// HSM state changed callbacks
protected:
    virtual void onOff(const VariantList_t& args) = 0;
    virtual void onOn(const VariantList_t& args) = 0;

// HSM state entering callbacks
protected:

// HSM state exiting callbacks
protected:

// HSM transition callbacks
protected:

// HSM transition condition callbacks
protected:
};

#endif // __GEN_HSM_SWITCHHSMBASE__

SwitchHsmBase.cpp

// Content of this file was generated

#include "SwitchHsmBase.hpp"

SwitchHsmBase::SwitchHsmBase()
    : HierarchicalStateMachine<SwitchHsmStates, SwitchHsmEvents>(SwitchHsmStates::On)
{
    configureHsm();
}

SwitchHsmBase::~SwitchHsmBase()
{}

void SwitchHsmBase::configureHsm()
{
    registerState<SwitchHsmBase>(SwitchHsmStates::On, this, &SwitchHsmBase::onOn, nullptr, nullptr);
    registerState<SwitchHsmBase>(SwitchHsmStates::Off, this, &SwitchHsmBase::onOff, nullptr, nullptr);


    registerTransition<SwitchHsmBase>(SwitchHsmStates::On, SwitchHsmStates::Off, SwitchHsmEvents::SWITCH, this, nullptr);
    registerTransition<SwitchHsmBase>(SwitchHsmStates::Off, SwitchHsmStates::On, SwitchHsmEvents::SWITCH, this, nullptr);
}

Using custom templates

scxml2gen comes with a predefined template for hpp and cpp files:

It it doesnt satisfy your project needs you can define your own. Currently scxml2gen supports following variables:

Variables can be referenced in two ways:

// Content of this file was generated

#ifndef __GEN_HSM_%CLASS_NAME%__
#define __GEN_HSM_%CLASS_NAME%__

#include <hsmcpp/hsm.hpp>

enum class @[email protected]
{
    @[email protected]
};

...

Integrating code generation to project

Ideally, code generation should be integrated into a build process to prevent any need for copy-pasting. Example of how to do so can be found in /examples/02_generated.

To make invoking scxml2gen during build more convenient two CMake functions are provided:

They will be automatically available to you when including root CMakeLists.txt file in your project.

Here is an example CMake script to build generate and build a simple HSM. Important points here are:

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})

Implementation itself is very similar to a HelloWorld example, but now we don't need to manually register HSM structure.

Suggestion:

#include <chrono>
#include <thread>
#include <hsmcpp/HsmEventDispatcherSTD.hpp>
#include "gen/SwitchHsmBase.hpp"

using namespace std::chrono_literals;

class SwitchHsm: public SwitchHsmBase
{
public:
    virtual ~SwitchHsm(){}

// HSM state changed callbacks
protected:
    void onOff(const VariantList_t& args) override
    {
        printf("Off\n");
        std::this_thread::sleep_for(1000ms);
        transition(SwitchHsmEvents::SWITCH);
    }

    void onOn(const VariantList_t& args) override
    {
        printf("On\n");
        std::this_thread::sleep_for(1000ms);
        transition(SwitchHsmEvents::SWITCH);
    }
};

int main(const int argc, const char**argv)
{
    std::shared_ptr<hsmcpp::HsmEventDispatcherSTD> dispatcher = std::make_shared<hsmcpp::HsmEventDispatcherSTD>();
    SwitchHsm hsm;

    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.