文章目录
前言
在ROS2之第1讲–环境的配置以及HelloWorld程序的建立中我们主要介绍了ros2环境的配置,工作空间的创建,以及helloworld程序的编写;通过第一节你会了解到通过ros2如何编写一个简单的程序;接下来我们将介绍ros2中的话题通信机制,如何编写一个简单的发布话题的程序和订阅话题的程序。
节点的介绍
节点是ROS2通信的基本单位,每个节点都对应某一单一的功能模块。例如,雷达驱动节点可能负责发布雷达消息,摄像头驱动节点可能负责发布图像消息。一个完整的机器人系统可能由许多协同工作的节点组成,而ROS2中的每个可执行文件(C++程序或Python程序)就是一个节点。
话题通信
以下通过一张图来介绍话题通信,节点A实现的功能是从摄像头获取图像,节点B的功能是向用户展示节点A获取的图像,我们通过话题进行节点A和节点B之间数据的传递;
话题通信是ROS 2中的一种主要通信方式,它基于发布/订阅模型。在这种模型中,节点之间以松耦合的方式交换消息,非常适用于高频率的数据流,如传感器数据、状态更新等。
特点: 话题通信是异步的,发布者和订阅者之间是独立的。发布者可以根据自己的节奏发布数据,而订阅者也可以根据自己的需求接收并处理这些消息。此外,一个话题可以有多个发布者和多个订阅者,支持复杂的多对多通信模式。
优势: 当其中一个节点出现问题时,并不会影响其他的节点,这种设计可以使得系统在各个部分可以更加容易的独立开发和替换,提高系统的可维护性。
下面这张图是官网上发布者和订阅者之间的关系;
创建python节点
发布者和订阅者的节点可以在以前建立的功能包里面直接添加节点,也可以创建新的功能包,在新的功能包里面创建节点;创建功能包的方法详细可以看 ROS2之第一讲。我直接就在创建好的功能包里添加两个节点;
- 添加发布者节点和订阅者节点的代码如下:
cd ws_helloworld/src/learning_pkg_py/learning_pkg_py # cd到元文件夹下
touch topic_helloworld_pub.py topic_helloworld_sub.py # 创建发布者和订阅者文件
- 在learning_pkg_py文件夹下找到 setup.py 文件并打开,并在以下图中的位置添加两个节点
发布者(publisher)python源码
发布节点源码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
# 创建一个发布者节点
class PublisherNode(Node):
def __init__(self, name):
super().__init__(name) #Ros2节点父类初始化
self.pub = self.create_publisher(String, "chatter", 10) #创建发布者对象
self.timer = self.create_timer(0.5, self.timer_callback) #创建一个定时器
def timer_callback(self): #创建定时器周期执行的回调函数
msg = String() #创建一个string类型的消息对象
msg.data = 'hello World' #填充消息对象中的消息数据
self.pub.publish(msg) #发布话题消息
self.get_logger().info('Publishing: "%s"' % msg.data) #输出日至信息,提示已经完成话题发布
def main(args=None): #ROS2节点主入口main函数
rclpy.init(args=args) #ROS2 python接口初始化
node = PublisherNode("topic_helloworld_pub") #创建ROS2节点对象并进行初始化
rclpy.spin(node) #循环等待ROS2推出
node.destroy_node() #销毁节点对象
rclpy.shutdown() #关闭ROS2 python接口
if __name__ == '__main__':
main()
订阅者(subscriber)python源码
订阅节点源码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
# 创建一个订阅者节点
class SubscriberNode(Node):
def __init__(self, name):
super().__init__(name) #Ros2节点父类初始化
self.sub = self.create_subscription(String, "chatter", self.listener_callback, 10) #创建订阅者对象
def listener_callback(self, msg): #创建回调函数
self.get_logger().info('I hear: "%s"' % msg.data) #输出日至信息,提示已经完成话题的接收
def main(args=None): #ROS2节点主入口main函数
rclpy.init(args=args) #ROS2 python接口初始化
node = SubscriberNode("topic_helloworld_sub") #创建ROS2节点对象并进行初始化
rclpy.spin(node) #循环等待ROS2推出
node.destroy_node() #销毁节点对象
rclpy.shutdown() #关闭ROS2 python接口
if __name__ == '__main__':
main()
运行结果展示
打开两个终端分别运行发布节点和接收节点。
- 运行发布节点 topic_helloworld_pub.py 的脚本如下:
cd ws_helloworld/ # cd到元文件夹下
colcon build # 编译文件
ros2 run learning_pkg_py topic_helloworld_pub # 运行发布节点
运行结果如下:
ros2 run learning_pkg_py topic_helloworld_sub # 运行接收节点
运行结果如下:
创建C++节点
- 直接在创建好的功能包 learning_pkg_cpp 里面添加发布者节点和订阅者节点的代码如下:
cd ws_helloworld/src/learning_pkg_cpp/src // cd到元文件夹下
touch topic_helloworld_pub_cpp.cpp topic_helloworld_sub_cpp.cpp // 创建发布者和订阅者文件
创建完成后如下图所示:
- CMakeLists.txt 对应的修改
从上图可以看见,在 CMakeLists.txt文件中田间对应节点;之后在进行编译
发布者(publisher)C++源码
发布者节点源码如下:
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class PublisherNode : public rclcpp::Node
{
public:
PublisherNode(): Node("topic_helloworld_pub_cpp") //ROS2节点父类初始化
{
//创建发布者对象(消息类型、话题名、队列长度)
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
//创建一个定时器, 定时执行回调函数
timer_ = this->create_wall_timer(500ms, std::bind(&PublisherNode::timer_callback, this ));
}
private:
//创建定时器周期执行的回调函数
void timer_callback()
{
// 创建一个string类型的消息对象
auto msg = std_msgs::msg::String();
// 填充消息对象中的消息数据
msg.data = "hello world!";
// 发布话题消息
publisher_->publish(msg);
// 输出日至信息,提示已经完成话题发布
RCLCPP_INFO(this->get_logger(), "Publishering: '%s", msg.data.c_str());
}
rclcpp::TimerBase::SharedPtr timer_; //定时器指针
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; //发布者指针
};
//ROS2节点主函数入口
int main(int argc, char * argv[])
{
//ROS2 C++ 接口初始化
rclcpp::init(argc, argv);
//创建ROS2节点对象并进行初始化
rclcpp::spin(std::make_shared<PublisherNode>());
//关闭ROS2 C++接口
rclcpp::shutdown();
return 0;
}
订阅者(subscriber)C++源码
订阅者节点源码如下:
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class SubscriberNode : public rclcpp::Node
{
public:
SubscriberNode(): Node("topic_helloworld_sub_cpp")
{
// 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)
subscription_ = this->create_subscription<std_msgs::msg::String>(
"chatter",10, std::bind(&SubscriberNode::topic_callback,this, _1));
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I hear: %s", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_; //订阅者指针
};
//ROS2节点主入口main函数
int main(int argc, char * argv[])
{
//ROS2 C++接口初始化
rclcpp::init(argc, argv);
//创建ROS2节点对象并进行初始化
rclcpp::spin(std::make_shared<SubscriberNode>());
//关闭ROS2 C++ 接口
rclcpp::shutdown();
return 0;
}
运行结果展示
打开两个终端分别运行发布节点和接收节点。
- 运行发布节点 topic_helloworld_pub_cpp.cpp的脚本如下:
cd ws_helloworld/ # cd到元文件夹下
colcon build # 编译文件
ros2 run learning_pkg_cpp topic_helloworld_pub_cpp # 运行发布节点
运行结果如下:
- 运行接收节点 topic_helloworld_sub_cpp.cpp 的脚本如下:
ros2 run learning_pkg_cpp topic_helloworld_sub_cpp # 运行发布节点
运行结果如下:
总结
以上为ros2话题通信的源码,以上程序传输的是字符串消息,应用的ros2中自带的消息结构,在现实的项目中难免会碰到自定义消息的时候,所以接下来我们将会讲一下,如何应用自定义消息进行话题的传递。
以上讲解如果帮到您请点赞加收藏哦,如有错误欢迎指正!