TF States

The TF States are part of the yasmin_ros package and provide YASMIN states that integrate with the tf2 transform library. They enable state machines to set up and share transform listeners through the blackboard so that subsequent states can look up coordinate frame transforms without duplicating listener infrastructure.

TfBufferState

Creates a tf2_ros::Buffer (C++) or tf2_ros.Buffer (Python) and an associated TransformListener, then stores both objects in the YASMIN blackboard under the keys tf_buffer and tf_listener. Subsequent states retrieve these objects from the blackboard and use them to perform transform lookups without creating redundant listeners.

Outcomes

Outcome Description
succeeded The tf2 buffer and transform listener were created successfully and written to the blackboard.
aborted Failed to create the tf2 buffer or transform listener (e.g. an exception was thrown during construction).

Blackboard Keys

Direction Key C++ type Python type Description
Output tf_buffer std::shared_ptr<tf2_ros::Buffer> tf2_ros.Buffer The created tf2 buffer. Pass it to tf2_ros::TransformBroadcaster or use lookupTransform directly.
Output tf_listener std::shared_ptr<tf2_ros::TransformListener> tf2_ros.TransformListener The transform listener that keeps tf_buffer up to date. Must be kept alive for as long as transforms are needed.

Parameters

Parameter Type Default Description
cache_time_sec float / double 10.0 How many seconds of transform history the tf2 buffer should keep. Increase this value if you need to look up transforms that are older than 10 seconds.

Plugin

TfBufferState is registered as a pluginlib plugin under the name yasmin_ros/TfBufferState. It can be loaded from XML state machine files via the YASMIN Factory.

Usage Examples

Python

Import TfBufferState from yasmin_ros and add it as the first state in your state machine. Subsequent states read tf_buffer from the blackboard to perform lookups.


import rclpy
from geometry_msgs.msg import TransformStamped

import yasmin
from yasmin import Blackboard, State, StateMachine
from yasmin_ros import TfBufferState, set_ros_loggers
from yasmin_ros.basic_outcomes import SUCCEED, ABORT


class LookupTransformState(State):
    """Looks up the transform from 'base_link' to 'map' using the shared buffer."""

    def __init__(self):
        super().__init__([SUCCEED, ABORT])

    def execute(self, blackboard: Blackboard) -> str:
        tf_buffer = blackboard["tf_buffer"]
        try:
            transform: TransformStamped = tf_buffer.lookup_transform(
                "map", "base_link", rclpy.time.Time()
            )
            yasmin.YASMIN_LOG_INFO(
                f"Transform: x={transform.transform.translation.x:.3f}"
                f" y={transform.transform.translation.y:.3f}"
            )
            return SUCCEED
        except Exception as exc:
            yasmin.YASMIN_LOG_WARN(f"Transform lookup failed: {exc}")
            return ABORT


def main():
    rclpy.init()
    set_ros_loggers()

    tf_state = TfBufferState()
    tf_state.set_parameter("cache_time_sec", 10.0)
    tf_state.configure()

    sm = StateMachine(outcomes=["done", "failed"])
    sm.add_state("INIT_TF", tf_state,
                 {SUCCEED: "LOOKUP", ABORT: "failed"})
    sm.add_state("LOOKUP", LookupTransformState(),
                 {SUCCEED: "done", ABORT: "failed"})

    outcome = sm()
    yasmin.YASMIN_LOG_INFO(outcome)
    rclpy.shutdown()


if __name__ == "__main__":
    main()

C++

Include yasmin_ros/tf_buffer_state.hpp and add TfBufferState to your state machine. The blackboard stores std::shared_ptr objects so they remain alive for the lifetime of the state machine execution.


#include <memory>
#include <string>

#include <rclcpp/rclcpp.hpp>
#include <tf2_ros/buffer.hpp>
#include <tf2_ros/transform_listener.hpp>

#include "yasmin/blackboard.hpp"
#include "yasmin/logs.hpp"
#include "yasmin/state.hpp"
#include "yasmin/state_machine.hpp"
#include "yasmin_ros/basic_outcomes.hpp"
#include "yasmin_ros/tf_buffer_state.hpp"
#include "yasmin_ros/yasmin_node.hpp"

// A state that uses the shared tf buffer to look up a transform.
class LookupTransformState : public yasmin::State {
public:
  LookupTransformState()
      : yasmin::State({yasmin_ros::basic_outcomes::SUCCEED,
                       yasmin_ros::basic_outcomes::ABORT}) {}

  std::string execute(yasmin::Blackboard::SharedPtr blackboard) override {
    auto tf_buffer =
        blackboard->get<std::shared_ptr<tf2_ros::Buffer>>("tf_buffer");
    try {
      auto transform = tf_buffer->lookupTransform(
          "map", "base_link", tf2::TimePointZero);
      YASMIN_LOG_INFO("x=%.3f y=%.3f",
                      transform.transform.translation.x,
                      transform.transform.translation.y);
      return yasmin_ros::basic_outcomes::SUCCEED;
    } catch (const tf2::TransformException &ex) {
      YASMIN_LOG_WARN("Transform lookup failed: %s", ex.what());
      return yasmin_ros::basic_outcomes::ABORT;
    }
  }
};

int main(int argc, char *argv[]) {
  rclcpp::init(argc, argv);
  yasmin_ros::set_ros_loggers();

  auto sm = std::make_shared<yasmin::StateMachine>(
      std::vector<std::string>{"done", "failed"});

  sm->add_state("INIT_TF",
                std::make_shared<yasmin_ros::TfBufferState>(),
                {{yasmin_ros::basic_outcomes::SUCCEED, "LOOKUP"},
                 {yasmin_ros::basic_outcomes::ABORT, "failed"}});

  sm->add_state("LOOKUP",
                std::make_shared<LookupTransformState>(),
                {{yasmin_ros::basic_outcomes::SUCCEED, "done"},
                 {yasmin_ros::basic_outcomes::ABORT, "failed"}});

  try {
    std::string outcome = (*sm)();
    YASMIN_LOG_INFO("%s", outcome.c_str());
  } catch (const std::exception &e) {
    YASMIN_LOG_WARN("%s", e.what());
  }

  rclcpp::shutdown();
  return 0;
}

XML (Factory)

TfBufferState is available as a plugin so it can be referenced directly in XML state machine definitions loaded by the YASMIN Factory.


<StateMachine outcomes="done failed">
  <State name="InitTF"
         type="cpp"
         class="yasmin_ros/TfBufferState">
    <Param name="cache_time_sec" value="10.0"/>
    <Transition from="succeeded" to="Lookup"/>
    <Transition from="aborted" to="failed"/>
  </State>

  <!-- Add your transform-using states here, reading tf_buffer
       from the blackboard. -->
</StateMachine>

Design Notes

  • Single listener per state machine: TfBufferState is intended to run once at the start of a state machine. All subsequent states share the same tf_buffer and tf_listener objects stored on the blackboard, avoiding the overhead of starting multiple listeners.
  • Lifetime management: The tf_listener must remain alive for transforms to continue being populated. Because YASMIN keeps the blackboard alive for the entire execution, the listener is automatically kept alive as long as its entry is not removed from the blackboard.
  • Cache time: The default cache time is 10 seconds. For applications that need to look up older transforms (e.g. when processing historical sensor data), increase cache_time_sec accordingly.
  • Thread safety: tf2_ros::Buffer is thread-safe. It can be accessed from states running in parallel inside a Concurrence container.