XML State Machines

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

What is a Plugin?

A plugin is a dynamically loadable component that extends YASMIN's functionality without modifying the core library. In the context of YASMIN, plugins are primarily used for C++ states that can be loaded at runtime from shared libraries. This architecture enables:

How to Create a Plugin

To create a C++ state plugin for use with YASMIN Factory XML, follow these steps:

1. Create your State Class

Define a class that inherits from yasmin::State. Implement the required execute() method and register outcomes.

// my_package/include/my_package/my_custom_state.hpp
#ifndef MY_PACKAGE__MY_CUSTOM_STATE_HPP_
#define MY_PACKAGE__MY_CUSTOM_STATE_HPP_

#include "yasmin/state.hpp"
#include "yasmin/blackboard.hpp"

namespace my_package {

class MyCustomState : public yasmin::State {
public:
  MyCustomState();
  std::string execute(yasmin::Blackboard::SharedPtr blackboard) override;
};

}  // namespace my_package

#endif

2. Implement and Export the Plugin

Implement your state and use the PLUGINLIB_EXPORT_CLASS macro to register it.

// my_package/src/my_custom_state.cpp
#include "my_package/my_custom_state.hpp"
#include <pluginlib/class_list_macros.hpp>

namespace my_package {

MyCustomState::MyCustomState() : yasmin::State({"success", "failure"}) {}

std::string MyCustomState::execute(
    yasmin::Blackboard::SharedPtr blackboard) {
  // Your state logic here
  return "success";
}

}  // namespace my_package

PLUGINLIB_EXPORT_CLASS(my_package::MyCustomState, yasmin::State)

3. Create the Plugin Description XML

Create a plugins.xml file in your package root that describes your plugin:

<!-- my_package/plugins.xml -->
<library path="my_package">
  <class type="my_package::MyCustomState" base_class_type="yasmin::State">
    <description>My custom YASMIN state plugin</description>
  </class>
</library>

4. Update CMakeLists.txt

Configure your CMakeLists.txt to build the plugin library and export the plugin description:

# Add the library
add_library(my_custom_state SHARED src/my_custom_state.cpp)
target_include_directories(my_custom_state PUBLIC include)
ament_target_dependencies(my_custom_state yasmin pluginlib)

# Export the plugin
pluginlib_export_plugin_description_file(yasmin plugins.xml)

# Install
install(TARGETS my_custom_state
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
)

5. Use in XML State Machine

Reference your plugin in XML state machine definitions using the format package_name/ClassName:

<State id="my_state" type="cpp" class="my_package/MyCustomState">
    <Transition outcome="success" target="next_state"/>
    <Transition outcome="failure" target="error_handler"/>
</State>
💡 Tip:

For Python states, no plugin system is needed. Python states are loaded dynamically using Python's import system. Simply specify the module and class in the XML definition with type="py".

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