-
-
Notifications
You must be signed in to change notification settings - Fork 17
How to write HFSM Code
- Attribute descriptions
- Declaring / Defining / Using HFSM-wide members
- Integrating HFSM generated code into your own codebase
- Debugging
- How the generated code is structured
-
State Machine
-
Includes
: this attribute is included in the generated states header file outside of theStateMachine
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 theStateMachine
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 theStateMachine
namespace, specifically into its generated states source file. It is where you should define any functions or variables that were declared inDeclarations
. 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 differenthandleEvent
state class functions at different levels of the class tree structure depending on the structure of the HFSM and its transition pathways.
-
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
).
- 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 namedMotor HFSM
, the root pointer to the HFSM class object would beMotor_HFSM_root
. Note: the name of the HFSM is sanitized, so that spaces are convered to underscores. - Defining the members statically, such that they are immediately accessible to all classes within the HFSM class.
-
Declarations
: -
Definitions
: - Using the members:
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();
}
}
}
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();
}
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 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
}
}
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
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
}
return 0;
};
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
.
.
.
#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
#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
.
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