XML-Based State Machine Definition

YASMIN Factory allows you to define state machines using XML files, enabling configuration without code changes. This is particularly useful for:

XML Structure

A YASMIN XML state machine definition includes state declarations, transitions, and optional remapping. The example below shows a simple state machine with two states:

<StateMachine id="example_sm" default_outcome="finish">
    <State id="state1" type="py" module="my_package.states" class="MyState">
        <Transition outcome="success" target="state2"/>
        <Transition outcome="fail" target="state1"/>
    </State>
    
    <State id="state2" type="py" module="my_package.states" class="AnotherState">
        <Transition outcome="done" target="finish"/>
    </State>
</StateMachine>

State Types

YASMIN Factory supports both Python and C++ states in the same state machine, enabling you to leverage existing code in either language or mix implementations based on performance needs and developer expertise. The factory automatically handles the language-specific loading and instantiation mechanisms.

⚠️ Important - Cross-Language Communication:

When mixing Python and C++ states in the same state machine, they can communicate through the blackboard, but only with primitive data types:

  • int - Integer numbers
  • float - Floating-point numbers
  • bool - Boolean values
  • string - Text strings

Complex objects, custom classes, or ROS messages cannot be directly shared between Python and C++ states through the blackboard.

Python States

Python states are loaded dynamically from the specified module and class. The type="py" attribute tells the factory to use Python's import system to locate and instantiate the state class.

<State id="python_state" type="py" module="my_package.states" class="MyPythonState">
    <Transition outcome="success" target="next_state"/>
</State>

C++ States

C++ states use ROS 2's pluginlib system for dynamic loading. The type="cpp" attribute indicates that the state should be loaded as a plugin, with the class name following the package_name/ClassName convention used by pluginlib.

<State id="cpp_state" type="cpp" class="my_package/MyCppState">
    <Transition outcome="success" target="next_state"/>
</State>

Loading XML State Machines

Once you've defined your state machine in XML, the YASMIN Factory API provides simple methods to load and execute it. The factory handles all the parsing, state instantiation, and wiring of transitions automatically.

Python

In Python, create a factory instance and use create_sm_from_file() to load the XML definition. The returned state machine can be executed directly by calling it as a function.

from yasmin_factory import YasminFactory

factory = YasminFactory()
sm = factory.create_sm_from_file("path/to/state_machine.xml")

# Execute the state machine
outcome = sm()
print(outcome)

C++

The C++ API mirrors the Python interface. Include the factory header, create a factory instance, and load the XML file. The state machine is returned as a shared pointer that can be dereferenced and executed.

#include "yasmin_factory/yasmin_factory.hpp"

yasmin_factory::YasminFactory factory;
auto sm = factory.create_sm_from_file("path/to/state_machine.xml");

// Execute the state machine
std::string outcome = (*sm.get())();
YASMIN_LOG_INFO(outcome.c_str());

Advanced Features

YASMIN Factory supports advanced state machine patterns through XML configuration, enabling complex behaviors without modifying code. These features include data remapping, hierarchical composition, and parallel execution.

Blackboard Remapping

Remapping allows you to connect state inputs and outputs to different blackboard keys, making states more reusable. This is essential when using generic states in different contexts or when multiple instances of the same state need to access different data.

<State id="my_state" type="py" module="pkg.states" class="MyState">
    <Remap from="input_data" to="sensor_reading"/>
    <Remap from="output_data" to="processed_result"/>
    <Transition outcome="success" target="next_state"/>
</State>

Nested State Machines

Hierarchical state machines allow you to organize complex behaviors into manageable sub-machines. A nested state machine appears as a single state to its parent, with its internal outcomes mapped to transitions in the outer machine. This promotes modularity and reusability of behavior patterns.

<StateMachine id="main_sm" default_outcome="done">
    <StateMachine id="sub_sm" default_outcome="sub_done">
        <State id="sub_state1" type="py" module="pkg" class="State1"/>
        <State id="sub_state2" type="py" module="pkg" class="State2"/>
    </StateMachine>
    
    <Transition from="sub_sm" outcome="sub_done" target="done"/>
</StateMachine>

Concurrence States

Concurrence states execute multiple child states simultaneously in parallel threads. The outcome mapping defines which combination of child outcomes determines the overall concurrence outcome. This is useful for implementing behaviors that monitor multiple conditions simultaneously or execute independent tasks in parallel for efficiency.

<Concurrence id="concurrent_states" default_outcome="defaulted">
    <State id="state_a" type="py" module="pkg" class="StateA"/>
    <State id="state_b" type="py" module="pkg" class="StateB"/>
    
    <OutcomeMap outcome="all_done">
        <StateOutcome state="state_a" outcome="success"/>
        <StateOutcome state="state_b" outcome="success"/>
    </OutcomeMap>
</Concurrence>

Example: Mixed Language State Machine

This example demonstrates a state machine using both Python and C++ states:

<StateMachine outcomes="outcome4">
    <State name="Foo" type="cpp" class="yasmin_demos/FooState">
        <Transition from="outcome1" to="Bar"/>
        <Transition from="outcome2" to="outcome4"/>
    </State>
    <State name="Bar" type="py" module="yasmin_demos.bar_state" class="BarState">
        <Transition from="outcome3" to="Foo"/>
    </State>
</StateMachine>

See Also