-
-
Notifications
You must be signed in to change notification settings - Fork 17
How to write HFSM Code
-
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:
int main( int argc, char** argv ) {
// initialize the HFSM
{{{sanitizedName}}}_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 = {{{sanitizedName}}}_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 into a ros::Timer
with a dynamic frequency):
StateMachine::Event* e = nullptr;
// process all pending events
while ( (e = eventFactory->getNextEvent()) != nullptr ) {
<%- hfsm.sanitizedName %>_root->handleEvent( e );
eventFactory->consumeEvent( e );
}
// run the HFSM tick event
<%- hfsm.sanitizedName %>_root->tick();
// process all events that may have been generated by the tick event
while ( (e = eventFactory->getNextEvent()) != nullptr ) {
<%- hfsm.sanitizedName %>_root->handleEvent( e );
eventFactory->consumeEvent( e );
}
// update the timer period according to new active state
ros::Duration newPeriod = ros::Duration( <%- hfsm.sanitizedName %>_root->getActiveLeaf()->getTimerPeriod() );
<%- hfsm.sanitizedName %>_HFSM_timer.stop();
if (!<%- hfsm.sanitizedName %>_root->hasStopped()) {
<%- hfsm.sanitizedName %>_HFSM_timer.setPeriod( newPeriod );
<%- hfsm.sanitizedName %>_HFSM_timer.start();
}
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