ros2话题通信
💡 本文精心编纂的源码,灵感深植于赵虚左老师卓越的教学精髓之中,源自其精心打造的视频课程与详尽文档。赵虚左老师,以其深厚的专业功底与独到的教学方法,在业界享有盛誉,其课程不仅内容全面、深入浅出,更以实战为导向,让复杂的技术知识变得易于掌握。
本文所呈现的源码不仅完美契合了赵老师课程中讲解的核心概念与技巧,还通过实际案例,让读者能够亲手操作,加深理解,实现从理论到实践的跨越。
因此,对于渴望在编程领域深耕细作、提升自我技能的读者而言,本文源码无疑是一份不可多得的宝贵资源。它不仅能够让你紧跟赵虚左老师的步伐,领略编程的魅力,更能在实践中不断成长,迈向成功。让我们一起,借由这份精心准备的源码,开启编程之旅的新篇章!
👉 可以去b站搜索<猛狮训练营>
话题通信
话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,也即:一个节点发布消息,另一个节点订阅该消息。话题通信的应用场景也极其广泛,比如如下场景:
机器人在执行导航功能,使用的传感器是激光雷达,机器人会采集激光雷达感知到的信息并计算,然后生成运动控制信息驱动机器人底盘运动。在该场景中,就不止一次使用到了话题通信。
话题通信的发布方与订阅方是一种多对多的关系,也即,同一话题下可以存在多个发布方,也可以存在多个订阅方,这意味着数据会出现交叉传输的情况,当然如果没有订阅方,数据传输也会出现丢失的情况。
话题通信一般应用于不断更新的、少逻辑处理的数据传输场景。
在话题通信中,需要关注的要素有三个:
1. 发布方;
2. 订阅方;
3. 消息载体。
流程简介
1. 发布方实现;
2. 订阅方实现;
3. 编辑配置文件;
4. 编译;
5. 执行。
ROS2中涉及的编程语言以C++和Python为主,ROS2中的大多数功能两者都可以实现,在多数情况下建议使用和练习c++语言。
程序运行时都依赖于工作空间,工作空间是ROS2项目的顶级目录,包含多个功能包。用于组织管理ROS2代码和资源,可以看作总项目的文件夹。
1. 工作空间的创建与编译
mkdir -p ws01_plumbing/src //创建工作空间以及子级目录src,工作空间名称可以自定义
cd ws01_plumbing //进入工作空间
colcon build //编译构建
src英文名为源代码,程序代码存放在src目录内
使用colcon build 编译构建完之后会自动为工作空间ws01_plumbing 创建三个文件夹 uild、install、log。创建工作空间必须在终端下完成。
2. 实际应用中一般建议创建专门的接口功能包 定义接口文件,预先创建所使用的接口功能包,每个项目或者话题需要单独创建一个功能包文件夹来存放接口文件
(需要注意的是,目前为止无法在Python功能包中定义接口文件),终端下进入工作空间的src目录,执行如下命令
ros2 pkg create --build-type ament_cmake base_interfaces_demo
这段代码的意思是:使用 ros2 命令创建一个包,包的名称为 base_interfaces_demo。构建类型为 ament_cmakec++文件,不填写这个命令也是默认c++文件
该功能包将用于保存本教程中自定义的接口文件。
3. 创建功能包
终端下,进入ws01_plumbing/src目录,使用如下指令创建一个C++的功能包:执行完毕后在src目录下生成名为cpp01_topic的目录,且目录中默认生成了一些子级文件与文件夹。
ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfaces_demo
这段代码执行的是在 ROS 2 环境中创建一个名为 cpp01_topic 的软件包的操作。
其中,--build-type ament_cmake 表示指定该软件包的构建类型为 ament_cmake 。
--dependencies rclcpp std_msgs base_interfaces_demo 则指明了这个软件包所依赖的其他软件包,分别是 rclcpp 、 std_msgs 和 base_interfaces_demo 。
综上所述,这条命令的目的是在ROS2环境中创建一个名为cpp01_topic的新软件包,该软件包使用ament_cmake构建系统,并依赖于rclcpp、std_msgs和base_interfaces_demo这三个软件包。
1.发布方实现
功能包cpp01_topic的src目录下,新建C++文件demo01_talker_str.cpp,并编辑文件,输入如下内容:
/*
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发送一条消息,编号递增1。
步骤:
1.包含头文件;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建发布方;
3-2.创建定时器;
3-3.组织消息并发布。
4.调用spin函数,并传入节点对象指针;
5.释放资源。
*/
// 包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp" // 包含消息类型
using namespace std::chrono_literals; // 通过 chrono_literals 命令设置持续时间,字面量加时间单位,比如:10s
// 定义节点类
class Talker : public rclcpp::Node // 自定义节点类名为 Talker ,继承自 rclcpp 库中的 Node 节点
{
public:
Talker() : Node("talker_node_cpp"),count(0) // 构造函数,基类为 Node
{
// 创建消息发布方
// 模板:被发布的消息类型,返回值:发布对象指针
// 参数 1:chatter 是话题名称
//参数 2:10 表示在发布消息时设置一个队列
// 队列深度为10,作用:网络堵塞时消息发不出去,把消息放在队列里,最多可以放10条等网络恢复后从队列取数据发出去
// publisher_ 接收函数的返回值
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
// 创建定时器,以固定频率发布文本
// 3 秒为时间单位,std::bind(&Talker::on_time, this 为当前对象调用 Talker 类中的 on_time 回调函数
timer_ = this->create_wall_timer(1s, std::bind(&Talker::on_time, this));
RCLCPP_INFO(this->get_logger(), "发布节点创建!"); // RCLCPP_INFO 用于打印
}
private:
void on_time()
{
// 组织消息并发布
auto message = std_msgs::msg::String(); // 创建字符串对象
message.data = "你好,世界!" + std::to_string(count++); // string 下的字段,将计数器转换为字符串类型
RCLCPP_INFO(this->get_logger(), "发布方发布的消息:%s", message.data.c_str()); // 打印日志,转换为 c 字符串
publisher_->publish(message); // 发布 message 的字符串
}
// publisher 是一个模板类,需要传入要发布的消息类型,变量名称为 publisher_ ,用于接收函数的返回值
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count; // 计数器
};
int main(int argc, char *argv[])
{
// 初始化 ROS2 客户端
rclcpp::init(argc, argv);
// 调用 spin 函数,并传入节点对象指针
rclcpp::spin(std::make_shared<Talker>());
// 释放资源
rclcpp::shutdown();
return 0;
}
2.订阅方实现
功能包cpp01_topic的src目录下,新建C++文件demo02_listener_str.cpp,并编辑文件,输入如下内容:
/*
需求:订阅发布方发布的消息,并输出到终端。
步骤:
1.包含头文件;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建订阅方;
3-2.处理订阅到的消息。
4.调用spin函数,并传入节点对象指针;
5.释放资源。
*/
// 包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp" // 包含用于处理字符串消息的头文件
// 自定义节点类
class Listener : public rclcpp::Node {
public:
// 构造函数,初始化节点名为 "listener_node_cpp"
Listener(): Node("listener_node_cpp") {
// 使用节点的日志记录器打印"订阅方创建!"
RCLCPP_INFO(this->get_logger(), "订阅方创建!"); // 输出创建订阅方的信息
// 创建一个订阅方,订阅主题 "chatter",队列大小为 10
// 并使用 std::bind 绑定处理函数 do_cd 来处理接收到的消息
Subscription_ = this->create_subscription<std_msgs::msg::String>("chatter", 10, std::bind(&Listener::do_cd, this, std::placeholders::_1));
}
private:
// 处理订阅到的字符串消息的私有成员函数
void do_cd(const std_msgs::msg::String &msg) {
// 使用节点的日志记录器输出接收到的消息内容
RCLCPP_INFO(this->get_logger(), "订阅的消息 %s", msg.data.c_str());
}
// 存储订阅对象的共享指针
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr Subscription_;
};
int main(int argc, char * argv[])
{
// 初始化 ROS2 客户端,处理命令行参数
rclcpp::init(argc, argv);
// 调用 spin 函数,开始处理消息,并传入创建的 Listener 节点对象指针
rclcpp::spin(std::make_shared<Listener>());
// 释放 ROS2 相关资源
rclcpp::shutdown();
return 0;
}
在创建c++功能包时所使用的指令已经默认生成了配置文件,不过实际应用中经常需要自己编辑配置文件,所以在此对相关内容做简单介绍,所使用的配置文件主要有两个,分别是功能包下的package.xml与CMakeLists.txt。
package.xml文件内容如下:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp01_topic</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="qxt@todo.todo">qxt</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
#以下为要添加的依赖
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend>
#
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
注释部分以后需要根据实际的包依赖进行添加或修改。
CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.8)
project(pkg01_helloworld_cpp)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find dependencies
find_package(ament_cmake REQUIRED)
#引入外部依赖包
find_package(rclcpp REQUIRED)
#映射源文件与可执行文件
add_executable(helloworld src/helloworld.cpp)
设置目标依赖库
ament_target_dependencies(
helloworld
"rclcpp"
)
#定义安装规则
install(TARGETS helloworld
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
the following line skips the linter which checks for copyrights
comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
the following line skips cpplint (only works in a git repo)
comment the line when this package is in a git repo and when
a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
中文注释部分以后可能需要根据实际情况修改。
在话题通信中需要自己编辑配置文件
package.xml
在创建功能包时,所依赖的功能包已经自动配置了,把这些配置加在package.xml文件中,配置内容如下
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend>
需要说明的是<depend>base_interfaces_demo</depend>在本案例中不是必须的。
CMakeLists.txt
CMakeLists.txt中发布和订阅程序核心配置如下,需要加在CMakeLists.txt文件中
https://xqq2430lqmg.feishu.cn/sync/FZVjddx3OsL627bGCxpcd0E9nEe
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(base_interfaces_demo REQUIRED)
add_executable(demo01_talker_str src/demo01_talker_str.cpp)
ament_target_dependencies(
demo01_talker_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
add_executable(demo02_listener_str src/demo02_listener_str.cpp)
ament_target_dependencies(
demo02_listener_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
install(TARGETS
demo01_talker_str
demo02_listener_str
DESTINATION lib/${PROJECT_NAME})
终端中进入当前工作空间,编译功能包:
colcon build --packages-select cpp01_topic
- colcon build:表示使用 colcon 工具来构建(编译)功能包。
- --packages-select cpp01_topic:这个选项指定了只编译名为 cpp01_topic 的软件包,而不是工作空间中的所有软件包。
翻译成中文,这段代码的意思是:“使用colcon构建工具,选择性地构建名为cpp01_topic的软件包。”
当前工作空间下,启动两个终端,终端1执行发布程序,终端2执行订阅程序。
终端1输入如下指令:
. install/setup.bash
ros2 run cpp01_topic demo01_talker_str
终端2输入如下指令:
. install/setup.bash
ros2 run cpp01_topic demo02_listener_str
install/setup.bash文件通常包含了设置环境变量的命令,这些环境变量是ROS节点和工具正常运行所必需的。
- 通过执行这个脚本,您的终端会话会配置好所有必要的ROS环境变量,从而允许您顺利运行ROS命令和节点。
ros2 run cpp01_topic demo01_talker_str
- 这条命令是使用ros2命令行工具来运行一个名为demo01_talker_str的ROS2节点。
- ros2 run是ROS2中用于执行特定软件包内节点的命令。
- cpp01_topic是包含要运行节点的软件包名称。
- demo01_talker_str则是要运行的节点名称。