ROS2机械臂开发

1:创建工作空间环境

source /opt/ros/humble/setup.bash

作用:让当前终端找到 ROS 2 的命令、功能包和开发库。

mkdir -p ~/ros2_cpp_ws/src

创建新的工作空间

cd ~/ros2_cpp_ws/src
ros2 pkg create hello_cpp --build-type ament_cmake --license Apache-2.0 --node-name hello_node --dependencies rclcpp
命令部分作用
ros2 pkg create hello_cpp创建名为hello_cpp的功能包
--build-type ament_cmake使用 ROS 2 的 CMake 构建方式
--license Apache-2.0设置许可证信息
--node-name hello_node生成hello_node.cpp和对应的可执行程序构建配置
--dependencies rclcpp声明依赖 ROS 2 的 C++ 库rclcpp
code ~/ros2_cpp_ws

使用 VS Code 打开工作空间 code .

cd ~/ros2_cpp_ws

回到刚才的 WSL 终端,进入工作空间根目录:编译在工作空间根目录执行。

colcon build --packages-select hello_cpp
  • colcon build:构建工作空间中的功能包。
  • --packages-select hello_cpp:本次只构建hello_cpp。
source ~/ros2_cpp_ws/install/setup.bash

作用:让当前终端找到刚刚编译好的hello_cpp功能包及其程序。通常也没有输出。

ros2 run hello_cpp hello_node
部分含义
ros2 run运行功能包中的可执行程序
hello_cpp功能包名称
hello_node可执行程序名称

2:话题

概念含义在你的程序中
节点 Node参与 ROS 通信、完成某项任务的功能单元PublisherNode类创建出来的节点对象
话题 Topic具有名称和消息类型的通信通道/chatter
消息 Message一次通信所携带的数据一个包含"Hello World"的String对象
发布者 Publisher负责向某个话题发送消息的对象publisher_指向的对象
订阅者 Subscriber接收某个话题消息的对象当前代码中还没有

一个话题可以有多个发布者和多个订阅者。通信双方需要使用匹配的话题名称、相同的消息类型以及兼容的通信配置。

发布一条消息,需要完成三个动作:创建消息、填写内容、调用发布接口。

auto msg = std_msgs::msg::String(); // 创建消息 msg.data = "Hello World"; // 填写内容 publisher_->publish(msg); // 发布消息

至于“什么时候发布”,是另一件事。你的程序选择使用定时器,每隔约 500 毫秒执行一次这些动作。

/* * ROS 2 C++ 话题发布示例 * * 节点名称:topic_helloworld_pub * 话题名称:chatter * 默认完整话题名:/chatter * 消息类型:std_msgs::msg::String * 消息内容:Hello World * 定时周期:500 毫秒,正常情况下约每秒发布两次 */ // 时间相关类型,以及下面使用的 500ms 时间字面量 #include <chrono> // std::bind:把成员函数与具体对象绑定,生成可调用对象 #include <functional> // std::shared_ptr、std::make_shared 等智能指针工具 #include <memory> // std::string 字符串类型 #include <string> // ROS 2 C++ 接口:节点、发布者、定时器、日志等 #include "rclcpp/rclcpp.hpp" // ROS 2 标准字符串消息类型 #include "std_msgs/msg/string.hpp" // 允许直接写 500ms,表示 500 毫秒 using namespace std::chrono_literals; // 自定义节点类。 // public 继承 rclcpp::Node,使本类具有 ROS 2 节点的相关功能。 class PublisherNode : public rclcpp::Node { public: // 构造函数:创建 PublisherNode 对象时执行一次。 PublisherNode() // 初始化本对象中的 Node 基类部分,并设置 ROS 节点名。 : Node("topic_helloworld_pub") { /* * 创建一个发布者。 * * this: * 指向当前正在构造的 PublisherNode 对象。 * * <std_msgs::msg::String>: * 指定发布者发送的消息类型。 * * "chatter": * 指定话题名称。本例默认解析为 /chatter。 * * 10: * 设置 QoS 的历史深度,采用 KeepLast 策略。 * 它控制缓存深度,不控制发布频率或总发送次数。 * * 返回值: * 管理发布者对象的共享智能指针。 * 将它保存到成员 publisher_,供后续回调使用。 */ publisher_ = this->create_publisher<std_msgs::msg::String>( "chatter", 10); /* * 创建周期定时器,并将它的智能指针保存到 timer_。 * * 第一个参数 500ms: * 设置定时周期为 500 毫秒。 * * 第二个参数 std::bind(...): * 创建将来供执行器调用的可调用对象。 * * &PublisherNode::timer_callback: * 指定需要调用的成员函数。 * * this: * 指定在当前节点对象上调用该成员函数。 * 这里保存的是原始对象指针,不增加共享引用计数。 * * 此处只是创建并配置定时器。 * 进入 spin() 后,执行器才会处理到期的定时器回调。 */ timer_ = this->create_wall_timer( 500ms, std::bind(&PublisherNode::timer_callback, this)); } private: /* * 定时器回调函数。 * * 构造函数中已经把它绑定给定时器。 * 运行 spin() 时,执行器会在定时器到期后调用它。 * * 每调用一次,就准备并发布一条消息。 */ void timer_callback() { // 创建本次回调使用的局部消息对象。 // auto 推导出的类型是 std_msgs::msg::String。 auto msg = std_msgs::msg::String(); // data 是 String 消息中的 std::string 字段。 msg.data = "Hello World"; /* * 记录一条 INFO 级别日志。 * * get_logger():获取当前节点的日志器。 * %s:字符串格式占位符。 * c_str():提供与 %s 匹配的 C 风格字符串。 * * 日志出现在下面的发布调用之前。 * 它表示程序正在准备发布,不证明订阅者已经收到消息。 */ RCLCPP_INFO( this->get_logger(), "Publishing: '%s'", msg.data.c_str()); /* * 通过 publisher_ 指向的发布者发布消息。 * * 发布者创建时已指定: * 话题为 chatter; * 消息类型为 std_msgs::msg::String。 * * 所以这里直接提供消息对象 msg 即可。 */ publisher_->publish(msg); // 回调结束后,局部变量 msg 被销毁。 // 下一次回调会重新创建一个消息对象。 } /* * 定时器共享智能指针成员。 * * 声明时默认为空。 * 构造函数中保存 create_wall_timer() 返回的智能指针。 */ rclcpp::TimerBase::SharedPtr timer_; /* * 发布者共享智能指针成员。 * * Publisher<std_msgs::msg::String> 是发布者类型。 * SharedPtr 是该类型的共享智能指针别名。 * * 它管理发布者对象;每次发送的消息由回调中的 msg 表示。 */ rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; }; // C++ 程序入口 int main(int argc, char * argv[]) { // 初始化 ROS 2,并处理命令行中的 ROS 参数。 rclcpp::init(argc, argv); /* * 创建一个 PublisherNode 对象。 * * 此时执行 PublisherNode 的构造过程, * 完成发布者和定时器的创建。 * * node 的类型是 std::shared_ptr<PublisherNode>。 * 它共同管理这个节点对象,使对象在运行期间保持存活。 */ auto node = std::make_shared<PublisherNode>(); /* * 让执行器等待并处理节点的工作。 * * 本例中主要处理定时器回调。 * 它不会每隔 500 毫秒重新创建节点。 * * 正常运行期间,此调用持续等待和处理回调。 * 默认情况下,按 Ctrl + C 会触发 ROS 上下文关闭, * 从而使 spin() 结束。 */ rclcpp::spin(node); // 进行 ROS 2 关闭操作。 // 如果 Ctrl + C 已触发上下文关闭,这里可能已经处于关闭状态。 rclcpp::shutdown(); // 正常退出 main;局部智能指针 node 随作用域结束而销毁。 return 0; }

2.1:具体分析

class PublisherNode : public rclcpp::Node

结合你学过的继承:

  • rclcpp::Node是 ROS 2 提供的基类。
  • PublisherNode是你定义的派生类。
  • 派生类继承了创建发布者、创建定时器、获取日志器等功能。

创建一个PublisherNode对象时,这个对象内部包含rclcpp::Node基类部分。

PublisherNode() : Node("topic_helloworld_pub")

这是构造函数和初始化列表。

Node("topic_helloworld_pub")调用基类构造函数,为节点设置名称。

两个成员变量保存发布者和定时器的智能指针。

rclcpp::TimerBase::SharedPtr timer_; rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;

可以这样子理解:

std::shared_ptr< rclcpp::Publisher<std_msgs::msg::String> > publisher_;

只声明这两个成员时,它们默认是空智能指针。之后,构造函数中的创建函数返回智能指针,再保存到这些成员中。

create_publisher()创建发布者。

publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);

采用:

这里的this指向当前正在构造的PublisherNode对象。通过继承,它可以调用基类提供的create_publisher()。

部分含义
<std_msgs::msg::String>指定发布的消息类型
"chatter"指定话题名称
10设置 QoS 的历史深度