Blackboard Remapping (C++)
This tutorial demonstrates how to use blackboard remapping in YASMIN to share data between states using different variable names in C++. Remapping is a powerful feature that allows you to reuse states with different data without modifying the state code. This is particularly valuable when you have generic, reusable states (like data processors or validators) that you want to apply to different blackboard variables. Remapping promotes code reuse and makes state machines more modular and maintainable.
1. Create the FooState
Define the FooState that reads from foo_data and writes
to foo_out_data. This state is designed to be generic -
it doesn't know or care what actual data it's processing. It simply
reads from a blackboard key named foo_data, logs it, and
writes it to foo_out_data. The beauty of this design is
that we can use the same state class multiple times with different
actual data by using remapping, avoiding code duplication and making
the state more flexible and testable.
class FooState : public yasmin::State {
public:
FooState() : yasmin::State({yasmin_ros::basic_outcomes::SUCCEED}) {};
std::string
execute(std::shared_ptr<yasmin::blackboard::Blackboard> blackboard) override {
std::string data = blackboard->get<std::string>("foo_data");
YASMIN_LOG_INFO("%s", data.c_str());
blackboard->set<std::string>("foo_out_data", data);
return yasmin_ros::basic_outcomes::SUCCEED;
};
};
2. Create the BarState
The BarState is similar to FooState but reads from
bar_data instead. This state demonstrates how different
states can work with their own blackboard keys, which will later be
remapped to share data between them without modifying the state
classes themselves.
class BarState : public yasmin::State {
public:
BarState() : yasmin::State({yasmin_ros::basic_outcomes::SUCCEED}) {}
std::string
execute(std::shared_ptr<yasmin::blackboard::Blackboard> blackboard) override {
std::string data = blackboard->get<std::string>("bar_data");
YASMIN_LOG_INFO("%s", data.c_str());
return yasmin_ros::basic_outcomes::SUCCEED;
}
};
3. Setup with Remapping
In the main function, we create a blackboard with two messages:
msg1="test1" and msg2="test2". Then we add
three states to the state machine, each using remapping (the fourth
parameter in add_state). In STATE1, we remap
foo_data to msg1, so when FooState reads
foo_data, it actually gets msg1 from the
blackboard. In STATE2, we reuse the same FooState class but remap
foo_data to msg2, so it processes different
data. In STATE3, we use BarState and remap bar_data to
foo_out_data (the output from the previous FooState),
creating a data pipeline. This demonstrates how remapping enables:
state reuse, data flow between states with incompatible key names, and
clear separation between state logic and data sources.
yasmin_ros::set_ros_loggers();
auto blackboard = std::make_shared<yasmin::blackboard::Blackboard>();
blackboard->set<std::string>("msg1", "test1");
blackboard->set<std::string>("msg2", "test2");
auto sm = std::make_shared<yasmin::StateMachine>(
std::initializer_list<std::string>{yasmin_ros::basic_outcomes::SUCCEED});
// Cancel state machine on ROS 2 shutdown
rclcpp::on_shutdown([sm]() {
if (sm->is_running()) {
sm->cancel_state();
}
});
// STATE1: remap foo_data to msg1
sm->add_state("STATE1", std::make_shared<FooState>(),
{
{yasmin_ros::basic_outcomes::SUCCEED, "STATE2"},
},
{
{"foo_data", "msg1"},
});
// STATE2: remap foo_data to msg2
sm->add_state("STATE2", std::make_shared<FooState>(),
{
{yasmin_ros::basic_outcomes::SUCCEED, "STATE3"},
},
{
{"foo_data", "msg2"},
});
// STATE3: remap bar_data to foo_out_data
sm->add_state("STATE3", std::make_shared<BarState>(),
{
{yasmin_ros::basic_outcomes::SUCCEED,
yasmin_ros::basic_outcomes::SUCCEED},
},
{
{"bar_data", "foo_out_data"},
});
4. Execute
Execute the state machine and observe how the same state classes process different data through remapping. The viewer tool helps visualize the state transitions and data flow in real-time.
yasmin_viewer::YasminViewerPub yasmin_pub(sm, "YASMIN_REMAPPING_DEMO");
try {
std::string outcome = (*sm.get())(blackboard);
YASMIN_LOG_INFO(outcome.c_str());
} catch (const std::exception &e) {
YASMIN_LOG_WARN(e.what());
}
rclcpp::shutdown();
5. Run the Demo
Compile and execute the remapping demonstration to see how states can be reused with different data sources.
ros2 run yasmin_demos remap_demo
YASMIN