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(yasmin::Blackboard::SharedPtr 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(yasmin::Blackboard::SharedPtr 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.

int main(int argc, char *argv[]) {
  // Initialize ROS 2
  rclcpp::init(argc, argv);

  // Set ROS 2 logs
  yasmin_ros::set_ros_loggers();
  YASMIN_LOG_INFO("yasmin_remapping_demo");

  // Create blackboard
  auto blackboard = yasmin::Blackboard::make_shared();
  blackboard->set<std::string>("msg1", "test1");
  blackboard->set<std::string>("msg2", "test2");

  // Create a state machine
  auto sm = yasmin::StateMachine::make_shared(
      std::initializer_list<std::string>{yasmin_ros::basic_outcomes::SUCCEED},
      true);

  // Add states to the state machine
  // 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.

  // Publish state machine updates
  yasmin_viewer::YasminViewerPub yasmin_pub(sm, "YASMIN_REMAPPING_DEMO");

  // Execute the state machine
  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();

  return 0;
}

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