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:
- Rapid prototyping: Test different behaviors quickly
- Non-programmer friendly: Modify robot behaviors without coding
- Version control: Track state machine structures easily
- Runtime flexibility: Load different behaviors dynamically
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 element: Root container with
idanddefault_outcomeattributes -
State elements: Define individual states with their
type (
pyorcpp), module/class information - Transition elements: Specify how states connect - which outcome leads to which target state
-
Self-loops: Notice
state1can transition back to itself onfailoutcome, useful for retry logic
<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.
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>
YASMIN