Skip to content

How to write HFSM Code

William Emfinger edited this page Apr 23, 2018 · 20 revisions

Table of Contents

Attribute descriptions

  • State Machine
    • Includes: this attribute is included in the generated states header file outside of the StateMachine namespace. It is where you would add any headers that your HFSM code may need for platform specific libraries.
    • Declarations: this attribute is added within the generated HFSM class within the StateMachine namespace, specifically into its generated states header file. It is where you would add any member functions or data that all states should be able to access. More about how to properly define and access them is described below.
    • Definitions: this attribute is added within the generated HFSM class within the StateMachine namespace, specifically into its generated states source file. It is where you should define any functions or variables that were declared in Declarations. More about how to properly define and access them is described below.
    • Initialization: this attribute is added within the generated HFSM class' initialize function defined within the generated states source file. It is where you should write any initialization code that should happen first before anything else in the HFSM executes.
  • State
    • Entry: this attribute represents the body of a function that is executed any time a state is entered. That function exists within the state's class definition in the generated states source file.
    • Exit: this attribute represents the body of a function that is executed any time a state is exited. That function exists within the state's class definition in the generated states source file.
    • Tick: this attribute represents the body of a function that is executed any time a state is ticked. That function exists within the state's class definition in the generated states source file.
  • Transition
    • Action: this attribute represents code that will be generated into multiple different handleEvent state class functions at different levels of the class tree structure depending on the structure of the HFSM and its transition pathways.

Declaring / Defining / Using HFSM-wide members

Because of the structure of the generated code (i.e. that each state is declared as a class within the scope of its parent state's class), each state has a different this pointer - so there are a few options for how to define functions and variables that are accessible within the state functions (e.g. Entry, Exit, Tick).

  1. Referring to the generated members using the pointer to the HFSM class object, which is generated as {{{HFSM sanitized name}}}_root, e.g. for a HFSM named Motor HFSM, the root pointer to the HFSM class object would be Motor_HFSM_root. Note: the name of the HFSM is sanitized, so that spaces are convered to underscores.
  2. Defining the members statically, such that they are immediately accessible to all classes within the HFSM class.
  • Declarations:
  • Definitions:
  • Using the members:

Integrating HFSM generated code into your own codebase

When you generate code for the HFSM using the SoftwareGenerator plugin, you can set Generate Test Bench to true, which will generate an additional Makefile and test.cpp which can be built using gcc to do simple testing of your code. This test bench also acts as an example for how to integrate the HFSM generated code into your own project.

Generally, you will want to execute the HFSM following this pattern (in this case the HFSM is named Example hfsm):

int main( int argc, char** argv ) {
  // initialize the HFSM
  Example_hfsm_root->initialize();
  
  while ( true ) {
    // generate any events you need to based on your peripherals / gpio / etc. (or in interrupts)
    // e.g. using 
    eventFactory->spawnEvent( StateMachine::Event::Type::Start );
    // where Start is the event name from the model
    // NOTE: the event is allocated on the HEAP with using "new"

    // then execute the state machine
    StateMachine::Event* e = eventFactory->getNextEvent();
    while (e != nullptr) {

      // tell the HFSM to handle the event
      bool handled = Example_hfsm_root->handleEvent( e );

      // the event factory allocated the event when it was spawned, but does not own the event
      // so we must tell the event factory that we are done with the event so that it can delete it!
      eventFactory->consumeEvent( e );

      // now see if there are any other events (might have been generated within the HFSM)
      e = eventFactory->getNextEvent();
    }
  } 
}

ROS

If you use the tick event and want to use the periodicity, you might structure your event handling like so (this is how ROSMOD generates the HFSM (in this case Example hfsm) into a ros::Timer with a dynamic frequency):

StateMachine::Event* e = nullptr;
// process all pending events
while ( (e = eventFactory->getNextEvent()) != nullptr ) {
  Example_hfsm_root->handleEvent( e );
  eventFactory->consumeEvent( e );
}
// run the HFSM tick event
Example_hfsm_root->tick();
// process all events that may have been generated by the tick event
while ( (e = eventFactory->getNextEvent()) != nullptr ) {
  Example_hfsm_root->handleEvent( e );
  eventFactory->consumeEvent( e );
}
// update the timer period according to new active state
ros::Duration newPeriod = ros::Duration( Example_hfsm_root->getActiveLeaf()->getTimerPeriod() );
Example_hfsm_timer.stop();
if (!Example_hfsm_root->hasStopped()) {
  Example_hfsm_timer.setPeriod( newPeriod );
  Example_hfsm_timer.start();
}

FreeRTOS

If you're using FreeRTOS, here is an example of how you might want to integrate the HFSM generated code as a Timer on your system (the HFSM is called Example hfsm):

#include "Example_hfsm_Events.hpp"
#include "Example_hfsm_GeneratedStates.hpp"

#include <string>
#include <iostream>

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"

#define MS_TO_TICKS( xTimeInMs ) (uint32_t)( ( ( TickType_t ) xTimeInMs * configTICK_RATE_HZ ) / ( TickType_t ) 1000 )

void TimerFunction( TimerHandle_t xTimer ) {

  StateMachine::Event* e = eventFactory->getNextEvent();

  // handle all events that were generated from the system while we slept
  while (e != nullptr) {
    bool handled = Example_hfsm_root->handleEvent( e );
    // free the memory that was allocated during "spawnEvent"
    eventFactory->consumeEvent( e );
    e = eventFactory->getNextEvent();
  }

  // tick the state machine
  Example_hfsm_root->tick();

  // handle all events that were generated from the tick event
  while (e != nullptr) {
    bool handled = Example_hfsm_root->handleEvent( e );
    // free the memory that was allocated during "spawnEvent"
    eventFactory->consumeEvent( e );
    e = eventFactory->getNextEvent();
  }

  if ( Example_hfsm_root->hasStopped() ) {
    // the HFSM has reached the top-level END STATE, so we should stop!
    xTimerStop( xTimer, 0 ); // block ticks of 0
  } else {
    // we're still running the HFSM, update the timer period accordingly
    double newTimerPeriod = Example_hfsm_root->getActiveLeaf()->getTimerPeriod();

    // change the period of the timer if we're changing states
    xTimerChangePeriod( xTimer,
			MS_TO_TICKS( newTimerPeriod ),
			100);  // block for up to 100 ticks
  }
}

int main( int argc, char** argv ) {

  // initialize the HFSM
  Example_hfsm_root->initialize();
  
  double newTimerPeriod = Example_hfsm_root->getActiveLeaf()->getTimerPeriod();

  // set up timer / task to run the HFSM according to freeRTOS
  xTimerCreate
    ("Example_hfsm_timerFunction", // name of the timer (should be short)
     MS_TO_TICKS( newTimerPeriod ), // period of the timer in ticks
     pdTRUE, // auto-reload the timer function
     ( void * ) 0, // id of the timer (passed to the callback function)
     TimerFunction // function the timer runs
     );
  
  return 0;
};

ESP32 with FreeRTOS

If you want to use it with a build system such as for the ESP32 running FreeRTOS, here is an example of how you might want to integrate the HFSM generated code as a Timer on your system (the HFSM is called Example hfsm):

You would probably want to make your HFSM a component, with the directory structure of:

components
├── Example_hfsm
│   ├── component.mk
│   ├── Example_hfsm_GeneratedStates.cpp
│   └── include
│       ├── DeepHistoryState.hpp
│       ├── Example_hfsm_Events.hpp
│       ├── Example_hfsm_GeneratedStates.hpp
│       ├── ShallowHistoryState.hpp
│       └── StateBase.hpp
.
.
.

And then you might want to create a Timer for this HFSM in another component, in this case the HFSM_Timer component:

components
├── HFSM_Timer
│   ├── component.mk
│   ├── HFSM_Timer.cpp
│   └── include
│       └── HFSM_Timer.hpp
.
.
.

HFSM_Timer.hpp

#ifndef __HFSM_Timer__INCLUDE_GUARD
#define __HFSM_Timer__INCLUDE_GUARD

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"

void HFSM_Timer_init( void );

#endif // __HFSM_Timer__INCLUDE_GUARD

HFSM_Timer.cpp

#include "Example_hfsm_Events.hpp"
#include "Example_hfsm_GeneratedStates.hpp"

#include "HFSM_Timer.hpp"

#define NUM_TIMERS 1

/* An Array to hold handles to the created timers */
TimerHandle_t xTimers[ NUM_TIMERS ];

#define MS_TO_TICKS( xTimeInMs ) (uint32_t)( ( ( TickType_t ) xTimeInMs * configTICK_RATE_HZ ) / ( TickType_t ) 1000 )

void TimerFunction( TimerHandle_t xTimer ) {

  StateMachine::Event* e = eventFactory->getNextEvent();

  // handle all events that were generated from the system while we slept
  while (e != nullptr) {
    bool handled = Example_hfsm_root->handleEvent( e );
    // free the memory that was allocated during "spawnEvent"
    eventFactory->consumeEvent( e );
    e = eventFactory->getNextEvent();
  }

  // tick the state machine
  Example_hfsm_root->tick();

  // handle all events that were generated from the tick event
  while (e != nullptr) {
    bool handled = Example_hfsm_root->handleEvent( e );
    // free the memory that was allocated during "spawnEvent"
    eventFactory->consumeEvent( e );
    e = eventFactory->getNextEvent();
  }

  if ( Example_hfsm_root->hasStopped() ) {
    // the HFSM has reached the top-level END STATE, so we should stop!
    xTimerStop( xTimer, 0 ); // block ticks of 0
  } else {
    // we're still running the HFSM, update the timer period accordingly
    double newTimerPeriod = Example_hfsm_root->getActiveLeaf()->getTimerPeriod();

    // change the period of the timer if we're changing states
    xTimerChangePeriod( xTimer,
			MS_TO_TICKS( newTimerPeriod ),
			100);  // block for up to 100 ticks
  }
}

void HFSM_Timer_init( void ) {

  // initialize the HFSM
  Example_hfsm_root->initialize();
  
  double newTimerPeriod = Example_hfsm_root->getActiveLeaf()->getTimerPeriod();

  // set up timer / task to run the HFSM according to freeRTOS
  xTimers[0] = xTimerCreate
    ("Example_hfsm_timerFunction", // name of the timer (should be short)
     MS_TO_TICKS( newTimerPeriod ), // period of the timer in ticks
     pdTRUE, // auto-reload the timer function
     ( void * ) 0, // id of the timer (passed to the callback function)
     TimerFunction // function the timer runs
     );

  // now start the timer
  if (xTimerStart( xTimers[0], 0) != pdPASS) {
#ifdef DEBUG_OUTPUT
    std::cout << "Timer Start Fail!" << std::endl;
#endif
  }
}

And then finally in your main.cpp you would want to simply include the HFSM_Timer.hpp and call HFSM_Timer_init(); in your app_main.

Debugging

By default, debugging is turned off on the HFSM, but if you want the HFSM to output the events that it handles, the transitions that it takes, the guard conditions that evaluate to true, the states it transitions through, and the actions it takes you can add this to your code (or to the test bench code)

#define DEBUG_OUTPUT

which will use std::cout to print to stdout logs such as:

GUARD [ someTest ] for EXTERNAL TRANSITION:/3/c/I evaluated to TRUE
NO GUARD on EXTERNAL TRANSITION:/3/c/o
EXIT::Complex::State_1::/3/c/Y
Exiting State 1
TRANSITION::ACTION for /3/c/I
TRANSITION::ACTION for /3/c/o
ENTRY::Complex::State3::/3/c/T
TRANSITION::ACTION for /3/c/T/I
ENTRY::Complex::State3::ChildState::/3/c/T/W
STATE TRANSITION: Complex::State_1->Complex::State3::ChildState

How the generated code is structured

Clone this wiki locally