【ROS2】中级:tf2-编写监听器 (C++)

目标:学习如何使用 tf2 获取帧变换。

教程级别:中级

 时间:10 分钟

 目录

  •  背景

  •  先决条件

  •  任务

    • 1.编写监听节点

    • 2.更新启动文件

    •  3.构建

    •  4.运行

  •  摘要

 背景

在之前的教程中,我们创建了一个 tf2 广播器来发布乌龟的姿态到 tf2。

在本教程中,我们将创建一个 tf2 监听器以开始使用 tf2。

 先决条件

本教程假设您已经完成了 tf2 静态广播器教程(C++)https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Writing-A-Tf2-Static-Broadcaster-Cpp.html 和 tf2 广播器教程(C++)https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Writing-A-Tf2-Broadcaster-Cpp.html。在上一个教程中,我们创建了一个 learning_tf2_cpp 包,我们将继续在此基础上进行工作。

 任务

1. 编写监听节点

让我们首先创建源文件。转到我们在上一个教程中创建的 learning_tf2_cpp 包。在 src 目录中,通过输入以下命令下载示例监听器代码:

cxy@ubuntu2404-cxy:~/ros2_ws/src/learning_tf2_cpp/src$ wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_cpp/src/turtle_tf2_listener.cpp

使用您喜欢的文本编辑器打开文件。

#include <chrono>  // 包含C++标准库的时间库
#include <functional>  // 包含C++标准库的函数库
#include <memory>  // 包含C++标准库的内存库
#include <string>  // 包含C++标准库的字符串库


#include "geometry_msgs/msg/transform_stamped.hpp"  // 包含geometry_msgs包的TransformStamped消息
#include "geometry_msgs/msg/twist.hpp"  // 包含geometry_msgs包的Twist消息
#include "rclcpp/rclcpp.hpp"  // 包含ROS2的rclcpp库
#include "tf2/exceptions.h"  // 包含tf2的异常处理库
#include "tf2_ros/transform_listener.h"  // 包含tf2_ros的TransformListener库
#include "tf2_ros/buffer.h"  // 包含tf2_ros的Buffer库
#include "turtlesim/srv/spawn.hpp"  // 包含turtlesim的Spawn服务


using namespace std::chrono_literals;  // 使用std::chrono_literals命名空间


class FrameListener : public rclcpp::Node  // 定义一个名为FrameListener的类,继承自rclcpp::Node
{
public:
  FrameListener()  // FrameListener类的构造函数
  : Node("turtle_tf2_frame_listener"),  // 初始化节点名为"turtle_tf2_frame_listener"
    turtle_spawning_service_ready_(false),  // 初始化乌龟生成服务的状态为未准备好
    turtle_spawned_(false)  // 初始化乌龟的生成状态为未生成
  {
    // 声明并获取`target_frame`参数
    target_frame_ = this->declare_parameter<std::string>("target_frame", "turtle1");


    tf_buffer_ =
      std::make_unique<tf2_ros::Buffer>(this->get_clock());  // 创建一个新的tf2_ros::Buffer对象
    tf_listener_ =
      std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);  // 创建一个新的tf2_ros::TransformListener对象


    // 创建一个生成乌龟的客户端
    spawner_ =
      this->create_client<turtlesim::srv::Spawn>("spawn");


    // 创建turtle2速度发布器
    publisher_ =
      this->create_publisher<geometry_msgs::msg::Twist>("turtle2/cmd_vel", 1);


    // 每秒调用一次on_timer函数
    timer_ = this->create_wall_timer(
      1s, [this]() {return this->on_timer();});
  }


private:
  void on_timer()  // 定义on_timer函数
{
    // 将帧名称存储在变量中,这些变量将用于
    // 计算转换
    std::string fromFrameRel = target_frame_.c_str();  // 目标帧的名称
    std::string toFrameRel = "turtle2";  // turtle2的帧名称


    if (turtle_spawning_service_ready_) {  // 如果乌龟生成服务已准备好
      if (turtle_spawned_) {  // 如果乌龟已经生成
        geometry_msgs::msg::TransformStamped t;  // 定义一个TransformStamped消息


        // 查找target_frame和turtle2帧之间的转换
        // 并发送速度命令让turtle2达到target_frame
        try {
          t = tf_buffer_->lookupTransform(
            toFrameRel, fromFrameRel,
            tf2::TimePointZero);  // 查找转换
        } catch (const tf2::TransformException & ex) {  // 捕获并处理异常
          RCLCPP_INFO(
            this->get_logger(), "Could not transform %s to %s: %s",
            toFrameRel.c_str(), fromFrameRel.c_str(), ex.what());
          return;
        }


        geometry_msgs::msg::Twist msg;  // 定义一个Twist消息


        static const double scaleRotationRate = 1.0;  // 定义旋转速度的缩放因子
        msg.angular.z = scaleRotationRate * atan2(
          t.transform.translation.y,
          t.transform.translation.x);  // 计算并设置z轴的角速度


        static const double scaleForwardSpeed = 0.5;  // 定义前进速度的缩放因子
        msg.linear.x = scaleForwardSpeed * sqrt(
          pow(t.transform.translation.x, 2) +
          pow(t.transform.translation.y, 2));  // 计算并设置x轴的线速度


        publisher_->publish(msg);  // 发布消息
      } else {
        RCLCPP_INFO(this->get_logger(), "Successfully spawned");  // 打印成功生成乌龟的信息
        turtle_spawned_ = true;  // 设置乌龟已经生成
      }
    } else {
      // 检查服务是否准备好
      if (spawner_->service_is_ready()) {  // 如果服务已经准备好
        // 使用乌龟的名字和坐标初始化请求
        // 注意在turtlesim/srv/Spawn中,x,y和theta被定义为浮点数
        auto request = std::make_shared<turtlesim::srv::Spawn::Request>();  // 定义一个Spawn服务的请求
        request->x = 4.0;  // 设置乌龟的x坐标
        request->y = 2.0;  // 设置乌龟的y坐标
        request->theta = 0.0;  // 设置乌龟的初始角度
        request->name = "turtle2";  // 设置乌龟的名字


        // 调用请求
        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) {  // 如果结果的名字是"turtle2"
              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");  // 打印服务未准备好的信息
      }
    }
  }


  // 布尔值用于存储信息
  // 如果生成乌龟的服务可用
  bool turtle_spawning_service_ready_;
  // 如果乌龟已成功生成
  bool turtle_spawned_;
  rclcpp::Client<turtlesim::srv::Spawn>::SharedPtr spawner_{nullptr};  // 定义一个Spawn服务的客户端
  rclcpp::TimerBase::SharedPtr timer_{nullptr};  // 定义一个定时器
  rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_{nullptr};  // 定义一个Twist消息的发布器
  std::shared_ptr<tf2_ros::TransformListener> tf_listener_{nullptr};  // 定义一个TransformListener
  std::unique_ptr<tf2_ros::Buffer> tf_buffer_;  // 定义一个Buffer
  std::string target_frame_;  // 定义一个字符串用于存储目标帧的名字
};


int main(int argc, char * argv[])  // 主函数
{
  rclcpp::init(argc, argv);  // 初始化ROS
  rclcpp::spin(std::make_shared<FrameListener>());  // 创建FrameListener对象并开始处理ROS事件
  rclcpp::shutdown();  // 关闭ROS
  return 0;  // 返回0表示程序正常结束
}
1.1  检查代码

要了解生成海龟服务背后的工作原理,请参考编写一个简单的服务和客户端(C++)教程 https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Service-And-Client.html 。

现在,让我们来看一下与获取帧变换相关的代码。 tf2_ros 包含一个 TransformListener 类,这使得接收变换的任务变得更容易。

#include "tf2_ros/transform_listener.h"

在这里,我们创建一个 TransformListener 对象。监听器创建后,它开始通过网络接收 tf2 变换,并将它们缓冲最多 10 秒。

tf_listener_ =
  std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);

最后,我们查询监听器以获取特定转换。我们调用 lookup_transform 方法,传入以下参数:

  1.  目标框架

  2.  来源框架

  3. 我们想要变换的时间

提供 tf2::TimePointZero 将只会得到最新可用的转换。所有这些都包裹在一个 try-catch 块中,以处理可能的异常。

t = tf_buffer_->lookupTransform(
  toFrameRel, fromFrameRel,
  tf2::TimePointZero);

所得到的变换代表了目标乌龟(fromFrameRel)相对于 turtle2 (toFrameRel)的位置和方向。然后使用乌龟之间的角度来计算速度指令以跟随目标乌龟。有关 tf2 的更多一般信息,请参阅概念部分中的 tf2 页面 https://docs.ros.org/en/jazzy/Concepts/Intermediate/About-Tf2.html 。

1.2 CMakeLists.txt

返回到 learning_tf2_cpp 目录, CMakeLists.txt 和 package.xml 文件位于那里。

现在打开 CMakeLists.txt ,添加可执行文件并将其命名为 turtle_tf2_listener ,稍后您将与 ros2 run 一起使用。

add_executable(turtle_tf2_listener src/turtle_tf2_listener.cpp)
ament_target_dependencies(
    turtle_tf2_listener
    geometry_msgs
    rclcpp
    tf2
    tf2_ros
    turtlesim
)

最后,添加 install(TARGETS…) 部分,以便 ros2 run 可以找到你的可执行文件:

install(TARGETS
    turtle_tf2_listener
    DESTINATION lib/${PROJECT_NAME})

2. 更新启动文件

打开位于 src/learning_tf2_cpp/launch 目录中名为 turtle_tf2_demo_launch.py 的启动文件,用你的文本编辑器,向启动描述中添加两个新节点,添加一个启动参数,并添加导入。结果文件应该看起来像:

from launch import LaunchDescription  # 导入LaunchDescription模块
from launch.actions import DeclareLaunchArgument  # 导入DeclareLaunchArgument模块
from launch.substitutions import LaunchConfiguration  # 导入LaunchConfiguration模块


from launch_ros.actions import Node  # 导入Node模块




def generate_launch_description():  # 定义一个名为generate_launch_description的函数
    return LaunchDescription([  # 返回一个LaunchDescription对象
        Node(  # 创建一个Node对象
            package='turtlesim',  # 设置包名为'turtlesim'
            executable='turtlesim_node',  # 设置可执行文件名为'turtlesim_node'
            name='sim'  # 设置节点名为'sim'
        ),
        Node(  # 创建一个Node对象
            package='learning_tf2_cpp',  # 设置包名为'learning_tf2_cpp'
            executable='turtle_tf2_broadcaster',  # 设置可执行文件名为'turtle_tf2_broadcaster'
            name='broadcaster1',  # 设置节点名为'broadcaster1'
            parameters=[  # 设置参数
                {'turtlename': 'turtle1'}  # 设置'turtlename'参数为'turtle1'
            ]
        ),
        DeclareLaunchArgument(  # 声明一个启动参数
            'target_frame', default_value='turtle1',  # 设置参数名为'target_frame',默认值为'turtle1'
            description='Target frame name.'  # 设置参数描述为'Target frame name.'
        ),
        Node(  # 创建一个Node对象
            package='learning_tf2_cpp',  # 设置包名为'learning_tf2_cpp'
            executable='turtle_tf2_broadcaster',  # 设置可执行文件名为'turtle_tf2_broadcaster'
            name='broadcaster2',  # 设置节点名为'broadcaster2'
            parameters=[  # 设置参数
                {'turtlename': 'turtle2'}  # 设置'turtlename'参数为'turtle2'
            ]
        ),
        Node(  # 创建一个Node对象
            package='learning_tf2_cpp',  # 设置包名为'learning_tf2_cpp'
            executable='turtle_tf2_listener',  # 设置可执行文件名为'turtle_tf2_listener'
            name='listener',  # 设置节点名为'listener'
            parameters=[  # 设置参数
                {'target_frame': LaunchConfiguration('target_frame')}  # 设置'target_frame'参数为LaunchConfiguration对象的'target_frame'属性
            ]
        ),
    ])

这将声明一个 target_frame 启动参数,为我们将生成的第二只乌龟启动一个广播器,并启动一个将订阅这些转换的监听器。

3. 构建

在工作区的根目录运行 rosdep 以检查缺失的依赖项。

rosdep install -i --from-path src --rosdistro jazzy -y

仍在工作区的根目录下,构建您的包:

colcon build --packages-select learning_tf2_cpp

打开一个新的终端,导航到工作区的根目录,然后加载设置文件:

. install/setup.bash

e85c9a94ade2ac8e4ff329bfb04a3a37.png

4. 运行

现在您已准备好开始完整的海龟演示:

ros2 launch learning_tf2_cpp turtle_tf2_demo_launch.py

您应该看到有两只乌龟的 turtle sim。在第二个终端窗口中输入以下命令:

ros2 run turtlesim turtle_teleop_key

da7507ea82e472176a8cafee621db5d8.png

要查看事物是否正常工作,只需使用箭头键驾驶第一只乌龟(确保您的终端窗口处于活动状态,而不是您的模拟器窗口),您会看到第二只乌龟跟随第一只乌龟!

 摘要

在本教程中,您学习了如何使用 tf2 来获取帧变换的访问权限。您还完成了编写您在 tf2 教程介绍中首次尝试的 turtlesim 演示。https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Introduction-To-Tf2.html 

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值