提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
ROS2赵虚左老师课程第二章课程学习记录
一、ROS2通信机制简介
四部分组成:
1、节点(对应单一功能模块,例如雷达驱动节点、摄像头)
2、话题(具有相同话题的节点可以通信到一起)
3、通信模型(话题通信、服务通信、动作通信、服务通信)
4、接口(数据载体,是数据传输的特定格式 msg文件用于话题通信 srv文件用于服务通信 action文件用于动作通信)
二、话题通信
创建工作空间ws02_plumbing
编译
cd src
创建三个功能包base_interfaces_demo(自己设定接口格式,只能依赖c++) cpp01_topic(写c++例子) py01_topic(写python例子)
ros2 pkg create --build-type ament_cmake base_interfaces_demo
ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfeces_demo --node-name demo01_talker_str
ros2 pkg create py01_topic --build-type ament_python --dependencies rclpy std_msgs base_interfaces_demo --node-name demo01_talker_str_py
2.1 发布方实现(C++)
/*
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发布一条,编号+1
流程:
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建消息发布方
3.2.创建定时器
3.3.组织并发布消息
4.调用spin函数,传入自定义类对象指针
5.释放资源
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;//可以在自变量后面跟上时间单位 用于后面定时器的定时设置1s
//3.自定义节点类
class Talker: public rclcpp::Node{
public:
Talker():Node("talker_node_cpp"){
RCLCPP_INFO(this->get_logger(),"发布节点创建!");
// 3.1.创建消息发布方
/*
模板:被发布的消息类型;
参数:
1.话题名称;
2.QOS(消息队列长度)
返回值:发布对象指针
*/
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter",10);//10代表消息队列长度
// 3.2.创建定时器
/*
模板:可以不使用
参数:
1.时间间隔;
2.回调函数;
返回值:定时器对象指针
*/
timer_ = this->create_wall_timer(1s,std::bind(&Talker::on_timer,this));
}
private:
void on_timer(){
// 3.3.组织并发布消息
auto message = std_msgs::msg::String();
message.data = "hello world!" + std::to_string(count++);
RCLCPP_INFO(this->get_logger(),"发布方发布的消息:%s",message.data.c_str());
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count;
};
int main(int argc, char ** argv)
{
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
//4.调用spin函数,传入自定义类对象指针
rclcpp::spin(std::make_shared<Talker>());
//5.释放资源
rclcpp::shutdown();
return 0;
}
AI解释
头文件引入:
rclcpp/rclcpp.hpp: ROS2 C++库的主要头文件。
std_msgs/msg/string.hpp: 包含用于发布字符串消息的头文件。
命名空间:
使用using namespace std::chrono_literals;引入chrono命名空间,用于后面设置定时器的时间间隔。
自定义节点类 Talker:
继承自rclcpp::Node类,表示这是一个ROS2节点。
构造函数中初始化节点,并创建了消息发布者和定时器。
消息发布者和定时器的创建:
create_publisher创建了一个发布者,用于发布字符串消息到名为 "chatter" 的话题,消息队列长度为 10。
create_wall_timer创建了一个定时器,每隔1秒触发一次,触发时调用on_timer成员函数。
on_timer 成员函数:
组织并发布带有编号的消息,编号每次递增。
使用RCLCPP_INFO输出日志,显示发布的消息内容。
main 函数:
初始化ROS2客户端。
创建Talker类的实例并传入spin函数,使节点进入主循环。
最后释放ROS2资源并返回0。
运行结果
ros2 run cpp01_topic demo01_talker_str
ros2 topic echo /chatter
2.2 订阅方实现(C++)
c++如何修改配置文件
1.直接新建文件夹命名为 demo02_listener_str.cpp
2.package.xml主要添加依赖 CMakeList.txt中主要修改 add_executable(映射关系) /ament_target_dependencies/install三部分
以下是修改前的CMakeist.txt
add_executable(demo01_talker_str src/demo01_talker_str.cpp) #创建一个名为 "demo01_talker_str" 的可执行文件,并将源文件 "src/demo01_talker_str.cpp" 添加到这个可执行文件中
target_include_directories(demo01_talker_str PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_compile_features(demo01_talker_str PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17
ament_target_dependencies(
demo01_talker_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
install(TARGETS demo01_talker_str
DESTINATION lib/${PROJECT_NAME})
以下是修改后的CMakeList.txt
add_executable(demo01_talker_str src/demo01_talker_str.cpp)
add_executable(demo02_listener_str src/demo02_listener_str.cpp) ###新增
target_include_directories(demo01_talker_str PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_compile_features(demo01_talker_str PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17
ament_target_dependencies(
demo01_talker_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
ament_target_dependencies( ###新增
demo02_listener_str
"rclcpp"
"std_msgs"
"base_interfaces_demo"
)
install(TARGETS
demo01_talker_str
demo02_listener_str ###新增
DESTINATION lib/${PROJECT_NAME})
代码实现
/*
需求:订阅发布方发布的消息,并在终端输出
流程:
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建订阅方
3.2.解析并输出数据
4.调用spin函数,并传入节点对象指针
5.资源释放
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
//3.自定义节点类
class Listener: public rclcpp::Node{
public:
Listener():Node("listener_node_cpp"){
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
//3.1.创建订阅方
/*
模板:消息类型;
参数:
1.话题名称
2.QOS,队列长度
3.回调函数
返回值:订阅对象指针
*/
subscription_ = this->create_subscription<std_msgs::msg::String>("chatter",10,std::bind(&Listener::do_cb,this,std::placeholders::_1));
}
private:
void do_cb(const std_msgs::msg::String &msg){
//3.2.解析并输出数据
RCLCPP_INFO(this->get_logger(),"订阅到的消息是:%s",msg.data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char const *argv[])
{
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
//4.调用spin函数,并传入节点对象指针
rclcpp::spin(std::make_shared<Listener>());
//5.资源释放
rclcpp::shutdown();
return 0;
}
编译
打开两个终端
分别更新环境变量
运行demo01_talker_str 和 demo02_listener_str两个文件
最终效果如下
2.3 发布方实现(python)
文件名:demo01_talker_str_py.py(已创建)
代码如下
"""
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发布一条,编号+1
流程:
1.导包
2.初始化ROS2客户端
3.自定义节点类
3.1.创建消息发布方
3.2.创建定时器
3.3.组织并发布消息
4.调用spin函数,传入自定义类对象(python中无所谓指针?)
5.释放资源
"""
# 1.导包
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
# 3.自定义节点类
class Talker(Node):
def __init__(self):
super().__init__("talker_node_py")
#初始化计数变量
self.count = 0
self.get_logger().info("发布方创建(python)!")
# 3.1.创建消息发布方
"""
参数:
1.消息类型;
2.话题名称;
3.QOS,队列长度。
返回值:
发布对象
"""
self.publisher = self.create_publisher(String,"chatter",10)
# 3.2.创建定时器
"""
参数:
1.时间间隔
2.回调函数
返回值:
定时器对象
"""
self.timer = self.create_timer(1.0,self.on_timer)
def on_timer(self):
# 3.3.组织并发布消息
message = String()
message.data = "hello eorld(python)!" + str(self.count)
self.publisher.publish(message)
self.count += 1
self.get_logger().info("发布的数据:%s" % message.data)
def main():
# 2.初始化ROS2客户端
rclpy.init()
# 4.调用spin函数,传入自定义类对象
rclpy.spin(Talker())
# 5.释放资源
rclpy.shutdown()
if __name__ == '__main__':
main()
AI解释
导包:使用import语句导入所需的ROS 2和消息相关的包和模块。
自定义节点类:创建了一个名为 Talker 的自定义节点类,继承自 Node 类。
__init__ 方法:初始化节点。在这里,它设置了节点名称、创建了一个发布者(用于发布消息)、创建了一个定时器(定时执行指定的回调函数)等。
on_timer 方法:定时器回调函数。每当定时器触发,就会调用这个方法,里面组织了一个 String 类型的消息并发布到名为 "chatter" 的话题上。
main 函数:初始化ROS 2客户端、调用 spin 函数,传入 Talker 类的实例,然后在程序结束时关闭ROS 2。
__name__ == '__main__' 的检查:确保代码在作为独立程序运行时才执行 main 函数。
整个代码的流程是初始化ROS 2,创建一个 Talker 节点,然后通过 spin 函数进入ROS 2的事件循环,等待消息的发布和接收。在 spin 函数中,Talker 节点的 on_timer 方法会根据定时器的设置定期发布带编号的字符串消息。最后,在程序结束时关闭ROS 2。
2.4 订阅方实现
与 demo01_talker_str_py.py 同目录下新创建一个python文件 demo02_listener_str_py.py
python如何修改配置文件
修改package.xml(添加依赖)以及setup.py,由于没有新的依赖需要添加只修改setup.py文件
修改前
entry_points={
'console_scripts': [
'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main'
],
},
加上1行 ‘demo02_listener_str_py = py01_topic.demo02_listener_str_py:main’
修改后
entry_points={
'console_scripts': [
'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main', #记得加上逗号隔开
'demo02_listener_str_py = py01_topic.demo02_listener_str_py:main'
],
},
代码实现
"""
需求:订阅发布方发布的消息,并在终端输出
流程:
1.导包
2.初始化ROS2客户端
3.自定义节点类
3.1.创建订阅方
3.2.解析并输出数据
4.调用spin函数,并传入节点对象指针
5.资源释放
"""
# 1.导包
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
# 3.自定义节点类
class Listener(Node):
def __init__(self):
super().__init__("listener_node_py")
self.get_logger().info("订阅方创建(python)!")
# 3.1.创建订阅方
"""
参数:
1.消息类型
2.话题名称
3.回调函数
4.QOS 队列长度
"""
self.subscription = self.create_subscription(String,"chatter",self.do_cb,10)
def do_cb(self,msg):
# 3.2.解析并输出数据
self.get_logger().info("订阅到的数据:%s" %msg.data)
def main():
# 2.初始化ROS2客户端
rclpy.init()
# 4.调用spin函数,并传入节点对象指针
rclpy.spin(Listener())
# 5.资源释放
rclpy.shutdown()
if __name__ == '__main__':
main()
编译
更新环境变量
运行
运行效果
2.5 自定义接口信息–接口文件
1.创建并编辑msg文件
创建功能包目录下创建msg文件夹
文件夹中创建msg文件,名称首字母大写(Student.msg)
string name
int32 age
float64 height
2.编辑配置文件
终端输入筛选rosidl开头的功能包
ros2 pkg list | grep -i rosidl
在package.xml里加入以下代码,并且删除原本已经存在的编译依赖
<!-- 编译依赖 -->
<build_depend>rosidl_default_generators</build_depend>
<!-- 执行依赖 -->
<exec_depend>rosidl_default_runtime</exec_depend>
<!-- 声明当前包所属的功能包组 -->
<member_of_group>rosidl_interface_packages</member_of_group>
在CMakeList.txt中加入find_package以及rosidl_generate_interfaces
# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rosidl_default_generators REQUIRED) ###新增
# 为接口文件生成源码
rosidl_generate_interfaces( ${PROJECT_NAME} ###新增
"msg/Student.msg"
)
3.编译
4.测试
输入指令查看是否有对应的消息格式返回
ros2 interface show base_interfaces_demo/msg/Student
2.6 自定义话题通信(C++)
1.创建新的文件:demo03_talker_stu.cpp demo04_listener_stu.cpp
2.修改配置文件:如上
3.写代码、编译
4.运行
demo03_talker_stu.cpp代码如下
/*
需求:以某个固定频率发送学生信息“姓名,年龄,身高”,每发布一条,;年龄+1
流程:
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建消息发布方
3.2.创建定时器
3.3.组织并发布消息
4.调用spin函数,传入自定义类对象指针
5.释放资源
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using namespace std::chrono_literals;//可以在自变量后面跟上时间单位 用于后面定时器的定时设置1s
using base_interfaces_demo::msg::Student;
//3.自定义节点类
class TalkerStu: public rclcpp::Node{
public:
TalkerStu():Node("talkerstu_node_cpp"),count(0){
RCLCPP_INFO(this->get_logger(),"发布节点创建!");
// 3.1.创建消息发布方
/*
模板:被发布的消息类型;
参数:
1.话题名称;
2.QOS(消息队列长度)
返回值:发布对象指针
*/
publisher_ = this->create_publisher<Student>("chatterstu",10);//10代表消息队列长度
// 3.2.创建定时器
/*
模板:可以不使用
参数:
1.时间间隔;
2.回调函数;
返回值:定时器对象指针
*/
timer_ = this->create_wall_timer(1s,std::bind(&TalkerStu::on_timer,this));
}
private:
void on_timer(){
// 3.3.组织并发布消息
auto message = Student();
message.name = "huluwa";
message.age = count;
count += 1;
message.height = 150.00;
RCLCPP_INFO(this->get_logger(),"学生信息:%s,%d,%.2f",message.name.c_str(),message.age,message.height);
publisher_->publish(message);
}
rclcpp::Publisher<Student>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count;
};
int main(int argc, char ** argv)
{
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
//4.调用spin函数,传入自定义类对象指针
rclcpp::spin(std::make_shared<TalkerStu>());
//5.释放资源
rclcpp::shutdown();
return 0;
}
demo04_listener_stu.cpp代码如下
/*
需求:订阅发布方发布的消息,并在终端输出
流程:
1.包含头文件
2.初始化ROS2客户端
3.自定义节点类
3.1.创建订阅方
3.2.解析并输出数据
4.调用spin函数,并传入节点对象指针
5.资源释放
*/
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using base_interfaces_demo::msg::Student;
//3.自定义节点类
class ListenerStu: public rclcpp::Node{
public:
ListenerStu():Node("listenerstu_node_cpp"){
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
//3.1.创建订阅方
/*
模板:消息类型;
参数:
1.话题名称
2.QOS,队列长度
3.回调函数
返回值:订阅对象指针
*/
subscription_ = this->create_subscription<Student>("chatterstu",10,std::bind(&ListenerStu::do_cb,this,std::placeholders::_1));
}
private:
void do_cb(const Student &msg){
//3.2.解析并输出数据
RCLCPP_INFO(this->get_logger(),"学生信息:%s,%d,%.2f",msg.name.c_str(),msg.age,msg.height);
}
rclcpp::Subscription<Student>::SharedPtr subscription_;
};
int main(int argc, char const *argv[])
{
//2.初始化ROS2客户端
rclcpp::init(argc,argv);
//4.调用spin函数,并传入节点对象指针
rclcpp::spin(std::make_shared<ListenerStu>());
//5.资源释放
rclcpp::shutdown();
return 0;
}
运行结果如下
2.7 自定义话题通信
注意vscode中导入自定义文件会报错,需要ctrl+shift+p输入settings.json,打开编辑json文件加入编译后生成的文件,具体参考课件 https://www.bilibili.com/video/BV1ad4y1g71d/?spm_id_from=333.788&vd_source=5eb835f89e9c79ac93387bfdb1ad83cb
接下来步骤同2.6
demo03_talker_stu_py.py代码如下
"""
需求:以某个固定频率发送学生信息“姓名,年龄,身高”,每发布一条,年龄+1
流程:
1.导包
2.初始化ROS2客户端
3.自定义节点类
3.1.创建消息发布方
3.2.创建定时器
3.3.组织并发布消息
4.调用spin函数,传入自定义类对象
5.释放资源
"""
# 1.导包
import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student
# 3.自定义节点类
class TalkerStu(Node):
def __init__(self):
super().__init__("talkerstu_node_py")
self.count = 0
self.get_logger().info("发布方创建(python)!")
# 3.1.创建消息发布方
"""
参数:
1.消息类型;
2.话题名称;
3.QOS,队列长度。
返回值:
发布对象
"""
self.publisher = self.create_publisher(Student,"chatterstu",10)
# 3.2.创建定时器
"""
参数:
1.时间间隔
2.回调函数
返回值:
定时器对象
"""
self.timer = self.create_timer(1.0,self.on_timer)
def on_timer(self):
# 3.3.组织并发布消息
message = Student()
message.name = "automan"
message.age = self.count
message.height = 1.85
self.publisher.publish(message)
self.count += 1
self.get_logger().info("学生信息:%s, %d, %.2f" % (message.name, message.age, message.height))
def main():
# 2.初始化ROS2客户端
rclpy.init()
# 4.调用spin函数,传入自定义类对象
rclpy.spin(TalkerStu())
# 5.释放资源
rclpy.shutdown()
if __name__ == '__main__':
main()
demo04_listener_stu_py.py 代码如下
"""
需求:订阅发布方发布的消息,并在终端输出
流程:
1.导包
2.初始化ROS2客户端
3.自定义节点类
3.1.创建订阅方
3.2.解析并输出数据
4.调用spin函数,并传入节点对象指针
5.资源释放
"""
# 1.导包
import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student
# 3.自定义节点类
class ListenerStu(Node):
def __init__(self):
super().__init__("listenerstu_node_py")
self.get_logger().info("订阅方创建(python)!")
# 3.1.创建订阅方
"""
参数:
1.消息类型
2.话题名称
3.回调函数
4.QOS 队列长度
"""
self.subscription = self.create_subscription(Student,"chatterstu",self.do_cb,10)
def do_cb(self,msg):
# 3.2.解析并输出数据
self.get_logger().info("订阅到的数据:%s,%d,%.2f" % (msg.name, msg.age, msg.height))
def main():
# 2.初始化ROS2客户端
rclpy.init()
# 4.调用spin函数,并传入节点对象指针
rclpy.spin(ListenerStu())
# 5.资源释放
rclpy.shutdown()
if __name__ == '__main__':
main()
运行效果如下
总运行效果如下
总结
话题下的发布方与订阅方是多对多的关系