// Look up for the transformation between target_frame and turtle2 frames // and send velocity commands for turtle2 to reach target_frame try { transformStamped = tf_buffer_->lookupTransform( toFrameRel, fromFrameRel, tf2::TimePointZero);//这里得到的是from_frame_rel在to_frame_rel坐标系下的相对位置 } catch (tf2::TransformException & ex) { RCLCPP_INFO( this->get_logger(), "Could not transform %s to %s: %s", toFrameRel.c_str(), fromFrameRel.c_str(), ex.what()); return; }
intmain(int argc, char * argv[]) { auto logger = rclcpp::get_logger("logger");
// Obtain parameters from command line arguments if (argc != 8) { RCLCPP_INFO( logger, "Invalid number of parameters\nusage: " "ros2 run learning_tf2_cpp static_turtle_tf2_broadcaster " "child_frame_name x y z roll pitch yaw"); return1; }
// As the parent frame of the transform is `world`, it is // necessary to check that the frame name passed is different if (strcmp(argv[1], "world") == 0) { RCLCPP_INFO(logger, "Your static turtle name cannot be 'world'"); return1; }
# Initialize the transform broadcaster self.br = TransformBroadcaster(self)
# Subscribe to a turtle{1}{2}/pose topic and call handle_turtle_pose # callback function on each message self.subscription = self.create_subscription( Pose, f'/{self.turtlename}/pose', self.handle_turtle_pose, 1)
defhandle_turtle_pose(self, msg): t = TransformStamped()
# Read message content and assign it to # corresponding tf variables t.header.stamp = self.get_clock().now().to_msg() t.header.frame_id = 'world' t.child_frame_id = self.turtlename
# Turtle only exists in 2D, thus we get x and y translation # coordinates from the message and set the z coordinate to 0 t.transform.translation.x = msg.x t.transform.translation.y = msg.y t.transform.translation.z = 0.0
# For the same reason, turtle can only rotate around one axis # and this why we set rotation in x and y to 0 and obtain # rotation in z axis from the message q = tf_transformations.quaternion_from_euler(0, 0, msg.theta) t.transform.rotation.x = q[0] t.transform.rotation.y = q[1] t.transform.rotation.z = q[2] t.transform.rotation.w = q[3]
# Send the transformation self.br.sendTransform(t)
Publish a static coordinate transform to tf2 using an x/y/z offset in meters and roll/pitch/yaw in radians. In our case, roll/pitch/yaw refers to rotation about the x/y/z-axis, respectively.
1
ros2 run tf2_ros static_transform_publisher x y z yaw pitch roll frame_id child_frame_id
角度用四元数表示
Publish a static coordinate transform to tf2 using an x/y/z offset in meters and quaternion.
1
ros2 run tf2_ros static_transform_publisher x y z qx qy qz qw frame_id child_frame_id
在launch文件中发布TF
1 2 3 4 5 6 7 8 9 10 11
from launch import LaunchDescription from launch_ros.actions import Node
classFramePublisher : public rclcpp::Node { public: FramePublisher() : Node("turtle_tf2_frame_publisher") { // Declare and acquire `turtlename` parameter this->declare_parameter<std::string>("turtlename", "turtle"); this->get_parameter("turtlename", turtlename_);
// Initialize the transform broadcaster tf_broadcaster_ = std::make_unique<tf2_ros::TransformBroadcaster>(*this);
// Subscribe to a turtle{1}{2}/pose topic and call handle_turtle_pose // callback function on each message std::ostringstream stream; stream << "/" << turtlename_.c_str() << "/pose"; std::string topic_name = stream.str();
// Read message content and assign it to // corresponding tf variables t.header.stamp = now; t.header.frame_id = "world"; t.child_frame_id = turtlename_.c_str();
// Turtle only exists in 2D, thus we get x and y translation // coordinates from the message and set the z coordinate to 0 t.transform.translation.x = msg->x; t.transform.translation.y = msg->y; t.transform.translation.z = 0.0;
// For the same reason, turtle can only rotate around one axis // and this why we set rotation in x and y to 0 and obtain // rotation in z axis from the message tf2::Quaternion q; q.setRPY(0, 0, msg->theta); t.transform.rotation.x = q.x(); t.transform.rotation.y = q.y(); t.transform.rotation.z = q.z(); t.transform.rotation.w = q.w();
# Initialize the transform broadcaster self.br = TransformBroadcaster(self)
# Subscribe to a turtle{1}{2}/pose topic and call handle_turtle_pose # callback function on each message self.subscription = self.create_subscription( Pose, f'/{self.turtlename}/pose', self.handle_turtle_pose, 1)
defhandle_turtle_pose(self, msg): t = TransformStamped()
# Read message content and assign it to # corresponding tf variables t.header.stamp = self.get_clock().now().to_msg() t.header.frame_id = 'world' t.child_frame_id = self.turtlename
# Turtle only exists in 2D, thus we get x and y translation # coordinates from the message and set the z coordinate to 0 t.transform.translation.x = msg.x t.transform.translation.y = msg.y t.transform.translation.z = 0.0
# For the same reason, turtle can only rotate around one axis # and this why we set rotation in x and y to 0 and obtain # rotation in z axis from the message q = tf_transformations.quaternion_from_euler(0, 0, msg.theta) t.transform.rotation.x = q[0] t.transform.rotation.y = q[1] t.transform.rotation.z = q[2] t.transform.rotation.w = q[3]
# Send the transformation self.br.sendTransform(t)
// Call on_timer function every second timer_ = this->create_wall_timer( 1s, std::bind(&FrameListener::on_timer, this)); }
private: voidon_timer() { // Store frame names in variables that will be used to // compute transformations std::string fromFrameRel = target_frame_.c_str(); std::string toFrameRel = "turtle2";
if (turtle_spawning_service_ready_) { if (turtle_spawned_) { geometry_msgs::msg::TransformStamped transformStamped;
// Look up for the transformation between target_frame and turtle2 frames // and send velocity commands for turtle2 to reach target_frame try { transformStamped = tf_buffer_->lookupTransform( toFrameRel, fromFrameRel, tf2::TimePointZero);//这里得到的是from_frame_rel在to_frame_rel坐标系下的位置 } catch (tf2::TransformException & ex) { RCLCPP_INFO( this->get_logger(), "Could not transform %s to %s: %s", toFrameRel.c_str(), fromFrameRel.c_str(), ex.what()); return; }
publisher_->publish(msg); } else { RCLCPP_INFO(this->get_logger(), "Successfully spawned"); turtle_spawned_ = true; } } else { // Check if the service is ready if (spawner_->service_is_ready()) { // Initialize request with turtle name and coordinates // Note that x, y and theta are defined as floats in turtlesim/srv/Spawn auto request = std::make_shared<turtlesim::srv::Spawn::Request>(); request->x = 4.0; request->y = 2.0; request->theta = 0.0; request->name = "turtle2";
// Call request using ServiceResponseFuture = rclcpp::Client<turtlesim::srv::Spawn>::SharedFuture; auto response_received_callback = [this](ServiceResponseFuture future) { auto result = future.get(); if (strcmp(result->name.c_str(), "turtle2") == 0) { turtle_spawning_service_ready_ = true; } else { RCLCPP_ERROR(this->get_logger(), "Service callback result mismatch"); } }; auto result = spawner_->async_send_request(request, response_received_callback); } else { RCLCPP_INFO(this->get_logger(), "Service is not ready"); } } } // Boolean values to store the information // if the service for spawning turtle is available bool turtle_spawning_service_ready_; // if the turtle was successfully spawned bool turtle_spawned_; rclcpp::Client<turtlesim::srv::Spawn>::SharedPtr spawner_{nullptr}; rclcpp::TimerBase::SharedPtr timer_{nullptr}; rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_{nullptr}; std::shared_ptr<tf2_ros::TransformListener> transform_listener_{nullptr}; std::unique_ptr<tf2_ros::Buffer> tf_buffer_; std::string target_frame_; };
# Create a client to spawn a turtle self.spawner = self.create_client(Spawn, 'spawn') # Boolean values to store the information # if the service for spawning turtle is available self.turtle_spawning_service_ready = False # if the turtle was successfully spawned self.turtle_spawned = False
# Call on_timer function every second self.timer = self.create_timer(1.0, self.on_timer)
defon_timer(self): # Store frame names in variables that will be used to # compute transformations from_frame_rel = self.target_frame to_frame_rel = 'turtle2'
if self.turtle_spawning_service_ready: if self.turtle_spawned: # Look up for the transformation between target_frame and turtle2 frames # and send velocity commands for turtle2 to reach target_frame try: now = rclpy.time.Time() trans = self.tf_buffer.lookup_transform( to_frame_rel, from_frame_rel, now) #这里得到的是from_frame_rel在to_frame_rel坐标系下的位置 except TransformException as ex: self.get_logger().info( f'Could not transform {to_frame_rel} to {from_frame_rel}: {ex}') return
self.publisher.publish(msg) else: if self.result.done(): self.get_logger().info( f'Successfully spawned {self.result.result().name}') self.turtle_spawned = True else: self.get_logger().info('Spawn is not finished') else: if self.spawner.service_is_ready(): # Initialize request with turtle name and coordinates # Note that x, y and theta are defined as floats in turtlesim/srv/Spawn request = Spawn.Request() request.name = 'turtle2' request.x = float(4) request.y = float(2) request.theta = float(0) # Call request self.result = self.spawner.call_async(request) self.turtle_spawning_service_ready = True else: # Check if the service is ready self.get_logger().info('Service is not ready')
/** Return the yaw, pitch, roll of anything that can be converted to a tf2::Quaternion * The conventions are the usual ROS ones defined in tf2/LineMath/Matrix3x3.h * \param a the object to get data from (it represents a rotation/quaternion) * \param yaw yaw * \param pitch pitch * \param roll roll */ template<class A> voidgetEulerYPR(const A & a, double & yaw, double & pitch, double & roll) { tf2::Quaternion q = impl::toQuaternion(a); impl::getEulerYPR(q, yaw, pitch, roll); }
/** Return the yaw of anything that can be converted to a tf2::Quaternion * The conventions are the usual ROS ones defined in tf2/LineMath/Matrix3x3.h * This function is a specialization of getEulerYPR and is useful for its * wide-spread use in navigation * \param a the object to get data from (it represents a rotation/quaternion) * \param yaw yaw */ template<class A> doublegetYaw(const A & a) { tf2::Quaternion q = impl::toQuaternion(a); return impl::getYaw(q); }
To do this turtle1 must listen to the topic where turtle3’s pose is being published, wait until transforms into the desired frame are ready, and then do its operations. To make this easier the tf2_ros::MessageFilter is very useful. The tf2_ros::MessageFilter will take a subscription to any ROS 2 message with a header and cache it until it is possible to transform it into the target frame.
tf2_buffer_ = std::make_shared<tf2_ros::Buffer>(this->get_clock()); // Create the timer interface before call to waitForTransform, // to avoid a tf2_ros::CreateTimerInterfaceException exception auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>( this->get_node_base_interface(), this->get_node_timers_interface()); tf2_buffer_->setCreateTimerInterface(timer_interface); tf2_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf2_buffer_);
point_sub_.subscribe(this, "/turtle3/turtle_point_stamped"); tf2_filter_ = std::make_shared<tf2_ros::MessageFilter<geometry_msgs::msg::PointStamped>>( point_sub_, *tf2_buffer_, target_frame_, 100, this->get_node_logging_interface(), this->get_node_clock_interface(), buffer_timeout); // Register a callback with tf2_ros::MessageFilter to be called when transforms are available tf2_filter_->registerCallback(&PoseDrawer::msgCallback, this);//等待需要的tf关系就绪,一就绪有数据就进入到回调函数中。 }