掌握ROS机器人通信系统架构
掌握ROS不同通信方式特点
掌握主题与消息的原理
本实验主要学习以下几方面的内容:
ROS通信架构
ROS主题与消息的原理
使用C++和Python实现ROS的主题
实现自定义ROS消息
1.所需仪器设备
(1)“卓越之星”设备-用于功能调试;
(2)个人PC-用于应用程序的开发与展示;
2.注意事项
(1)个人PC需要安装NoMachine或者VSCode支持远程编程开发;
(2)运动实验尽可能将设备平放在地面上;
(3)实验开始前确保设备电量为满电,实验完成后关闭设备电源;
ROS 中的主题(Topics)和消息(Messages)是 ROS 中非常重要的两个概念,它们是 ROS 用于实现节点之间通信的基本组件。
主题(Topics)是用于在 ROS 节点之间传递消息的通道。一个主题包含了一个特定类型的消息,ROS
中定义了很多不同类型的消息,比如传感器数据、控制命令、图像数据等等。节点可以发布(Publish)和订阅(Subscribe)主题,这样就可以在节点之间传递消息。
消息(Messages)是 ROS 中用于定义数据类型的基本单元。每个消息都是由若干个数据字段(Fields)组成的,每个字段有一个名称和一个数据类型。ROS
中定义了很多不同类型的消息,包括基本类型如整型、浮点型、字符串等,还有一些复杂类型如点云、图像数据等。节点之间通过主题交换消息,每个消息都是一个特定类型的消息。
总的来说,ROS 中的主题和消息是用于实现节点之间通信的基本组件。节点可以发布和订阅主题,通过主题交换消息来实现数据的传输。消息定义了数据类型,节点之间通过消息交换数据。这种通信机制使得
ROS 非常适合机器人系统的开发。
ROS 中主题是一种通讯异步通信方式,在 ROS 中,主题的实现基于 ROS 节点(Node)的概念。一个节点可以是一个发布者、一个订阅者或同时兼具两种功能。当一个节点作为发布者时,它会向
ROS 主节点(Master)注册它所发布的主题,以便其他节点可以找到并订阅它。当一个节点作为订阅者时,它会向 ROS
主节点注册它所订阅的主题,并等待来自发布者的消息。
如上图所示,是三个 ROS 节点互相通过主题通信的示意,节点 1 发布主题 1,订阅主题 2;节点 2 发布主题 2,订阅主题 1 和 3;节点 3
发布主题 3,订阅主题 2,接下来我们介绍如何通过 ROS 发布和接收主题;
ROS 中有许多标准消息,可以用于传递各种类型的数据。以下是一些常用的标准消息类型:
std_msgs/String:用于传递字符串数据。
std_msgs/Int32:用于传递 32 位整数数据。
std_msgs/Float32:用于传递 32 位浮点数数据。
geometry_msgs/Twist:用于传递机器人运动控制命令。
sensor_msgs/Image:用于传递图像数据。
sensor_msgs/LaserScan:用于传递激光雷达数据。
nav_msgs/Odometry:用于传递机器人的位姿和速度信息。
tf2_msgs/TFMessage:用于传递坐标系变换信息。
以上是一些常用的标准消息类型,还有许多其他的标准消息类型可以在 ROS 中使用。另外,也可以自定义消息类型来满足特定的应用需求。
接下来我们使用 C++实现两个 ROS 节点,一个发布者,一个接收者来传递标准字符消息;
使用 C++实现一个发布者发布消息:
下面是使用 C++在 ROS 中实现节点传递消息的基本步骤:
创建一个 ROS 节点。在 C++中,可以使用ros::init()函数初始化 ROS 节点,并使用ros::NodeHandle对象来访问 ROS 系统中的各种资源。
定义一个发布者对象。在 C++中,可以使用ros::Publisher类来定义一个发布者对象,并指定要发布的主题名称和消息类型。
创建一个消息对象并填充数据。在 C++中,可以使用消息类型对应的结构体来创建一个消息对象,并设置消息的各个字段。
使用发布者对象发布消息。在 C++中,可以使用发布者对象的publish()函数来发布消息。
定义一个 while 循环,循环发布消息:
#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "my_publisher");
// 创建节点句柄
ros::NodeHandle nh;
// 定义一个发布者对象
ros::Publisher pub = nh.advertise<std_msgs::String>("my_topic", 10);
//定义一个ros频率,间歇1.0秒
ros::Rate rate(1.0);
while (ros::ok())
{
// 创建一个消息对象并填充数据
std_msgs::String msg;
msg.data = "Hello, world!";
// 发布消息
pub.publish(msg);
//间歇休息1秒
rate.sleep();
}
return 0;
}
使用 C++实现一个订阅者订阅上述消息:
创建一个 ROS 节点。在 C++中,可以使用ros::init()函数初始化 ROS 节点,并使用ros::NodeHandle对象来访问 ROS 系统中的各种资源。
定义一个回调函数,用于处理接收到的消息。在 C++中,可以定义一个回调函数,用于处理接收到的消息。回调函数的参数类型是const
boost::shared_ptr,其中T是消息类型,表示接收到的消息对象的指针。
定义一个订阅者对象。在 C++中,可以使用ros::Subscriber类来定义一个订阅者对象,并指定要订阅的主题名称和消息类型。
#include <ros/ros.h>
#include <std_msgs/String.h>
void callback(const std_msgs::String::ConstPtr& msg){
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char** argv){
// 初始化ROS节点
ros::init(argc, argv, "my_subscriber");
// 创建节点句柄
ros::NodeHandle nh;
// 定义一个订阅者对象
ros::Subscriber sub = nh.subscribe("my_topic", 10, callback);
// 运行节点
ros::spin();
return 0;
}
接下来使用 Python 实现 ROS 的主题与消息,因为和 C++实现原理一致,这里只介绍代码步骤:
使用 Python 实现一个发布者:
创建一个 ROS 节点。在 Python 中,可以使用rospy.init_node()函数初始化 ROS 节点,并使用rospy模块提供的各种函数来访问 ROS
系统中的各种资源。
定义一个发布者对象。在 Python 中,可以使用rospy.Publisher类来定义一个发布者对象,并指定要发布的主题名称和消息类型。
创建一个消息对象并填充数据。在 Python 中,可以使用消息类型对应的类来创建一个消息对象,并设置消息的各个字段。
使用发布者对象发布消息。在 Python 中,可以使用发布者对象的publish()函数来发布消息。
#!/usr/bin/env python3
import rospy
from std_msgs.msg import String
rospy.init_node('my_publisher')
# 定义一个发布者对象
pub = rospy.Publisher('my_topic', String, queue_size=10)
while not rospy.is_shutdown():
# 创建一个消息对象并填充数据
msg = String()
msg.data = 'Hello, world!'
# 发布消息
pub.publish(msg)
#沉睡1秒
rospy.Rate.sleep(1.0)
使用 Python 实现一个 ROS 接收者:
以下是一个简单的 ROS Python 节点,它可以订阅名为my_topic的话题,并打印出接收到的消息:
#!/usr/bin/env python3
import rospy
from std_msgs.msg import String
#定义一个接收函数,打印接收的消息
def callback(data):
rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
def my_subscriber():
rospy.init_node('my_subscriber', anonymous=True)
#声明一个接收者
rospy.Subscriber("my_topic", String, callback)
#不断回调
rospy.spin()
if __name__ == '__main__':
try:
my_subscriber()
except rospy.ROSInterruptException:
pass
python 节点无需编译,可以直接运行,需要注意的是,需要使用 chmod a+x (文件名).py 这个命令,来给予 python 文件可执行权限。
前面介绍了 ROS 中如何使用主题发布标准消息,但是很多时候 ROS 标准的消息格式不能满足我们的实际需求,因此需要自定义 ROS
消息,自定义 ROS 消息流程如下所示:
在 ROS 工作空间中的src目录下创建一个新的包,例如class_2_pkg。
在class_2_pkg包中创建一个msg目录,并在该目录中创建一个.msg文件,例如msg文件定义了一个消息类型的名称和其包含的字段,每个字段都具有名称和类型。自定义消息的过程如下:
创建一个包(package):可以使用catkin_create_pkg命令创建一个新的ROS包,例如:
catkin_create_pkg class_2_pkg std_msgs rospy roscpp
这个命令创建一个名为 class_2_pkg 的 ROS 包,并依赖于std_msgs和rospy,roscpp包。
在包中创建一个.msg文件:使用文本编辑器创建一个新的.msg文件,例如:
cd class_2_pkg
mkdir msg
touch msg/MyMessage.msg
这个命令创建一个名为CreMessage的msg的文件,它将定义一个自定义的消息类型。
定义.msg文件中的消息类型:打开MyMessage.msg文件,在其中定义该消息类型的名称和字段。例如:
int32 key
string value
这个文件定义了一个名为MyMessage的消息类型,它包含两个字段:key 和 value。
在这个功能包的 package.xml 文件中添加以下内容:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
这将会告诉 catkin 构建系统,您的包需要使用 ROS 消息生成库。
在 CMakeLists.txt 文件中添加以下内容:
find_package(catkin REQUIRED COMPONENTS
message_generation
)
add_message_files(
DIRECTORY msg
FILES MyMessage.msg
)
generate_messages(
DEPENDENCIES
)
catkin_package(
CATKIN_DEPENDS message_runtime
)
这将会告诉 catkin 构建系统,您的包依赖于消息生成库,并且您的消息类型定义在MyMessage.msg 文件中。
编译自定义消息:切换到工作空间根目录运行以下命令:
cd ~/ros_ws
catkin_make
这个命令会编译class_2_pkg包中的所有消息,包括新定义的MyMessage消息类型。
在您的 C++代码中,您可以使用 class_2_pkg/MyMessage.h 头文件来引用您自定义的消息类型,class_2_pkg代表包的名称,MyMessage代表
msg 文件的文件名称(头文件可以在devel/include/class_2_pkg文件夹中找到)。
在您的 Python 代码中,您可以使用from class_2_pkg.msg import
MyMessage来引用您自定义的消息类型,class_2_pkg代表包的名称,msg代表存放消息文件的文件夹名称,MyMessage代表msg文件的文件名称(py引用文件可以在devel/lib/python3/dist-packages/class_2_plg/msg文件夹中找到)
这时,如果这个包的路径已经添加在了环境变量中(写入了 bashrc 文件),那么我们就可以像使用标准消息一样使用这个自定义的消息了,代码如下所示。
使用 C++实现一个 ros 自定义消息的发布者:
#include <ros/ros.h>
#include <class_2_pkg/MyMessage.h>
int main(int argc, char **argv) {
// 初始化ROS节点
ros::init(argc, argv, "my_publisher");
ROS_INFO("start a publisher node ..");
// 创建节点句柄
ros::NodeHandle nh;
// 定义一个发布者对象
ros::Publisher pub = nh.advertise<class_2_pkg::MyMessage>("my_topic", 10);
//定义一个ros频率,间歇1.0秒
ros::Rate rate(1.0);
while (ros::ok()) {
// 创建一个消息对象并填充数据
class_2_pkg::MyMessage msg;
msg.key = 1;
msg.value = "Hello, ROS!";
// 发布消息
pub.publish(msg);
//间歇休息1秒
rate.sleep();
}
return 0;
使用 C++实现一个 ros 自定义消息的接收者:
#include <ros/ros.h>
#include "class_2_pkg/MyMessage.h"
void callback(const class_2_pkg::MyMessage::ConstPtr &msg) {
std::string str = "callback key : " + std::to_string(msg->key) + " , value : " + msg->value;
std::cout << str << std::endl;
}
int main(int argc, char **argv) {
// 初始化ROS节点
ros::init(argc, argv, "my_subscriber");
ROS_INFO("start a subscriber node");
// 创建节点句柄
ros::NodeHandle nh;
// 定义一个订阅者对象
ros::Subscriber sub = nh.subscribe("my_topic", 10, callback);
// 运行节点
ros::spin();
return 0;
}
编译并使用rosrun运行这两个节点,体会发布者和订阅者的运行效果;
使用 Python 实现一个 ros 自定义消息的发布者:
#!/usr/bin/env python3
import rospy
from class_2_pkg.msg import MyMessage
rospy.init_node('my_publisher')
# 定义一个发布者对象
pub = rospy.Publisher('my_topic', MyMessage, queue_size=10)
while not rospy.is_shutdown():
# 创建一个消息对象并填充数据
msg = MyMessage()
msg.key = 1
msg.value = 'Hello, ROS!'
# 发布消息
pub.publish(msg)
#沉睡1秒
rospy.sleep(1.0)
使用 Python 实现一个 ros 自定义消息的接收者:
#!/usr/bin/env python3
import rospy
from class_2_pkg.msg import MyMessage
#定义一个接收函数,打印接收的消息
def callback(data):
rospy.loginfo(rospy.get_caller_id() + "I heard key: %d, data: %s", data.key, data.value)
def my_subscriber():
rospy.init_node('my_subscriber', anonymous=True)
#声明一个接收者
rospy.Subscriber("my_topic", MyMessage, callback)
#不断回调
rospy.spin()
if __name__ == '__main__':
try:
my_subscriber()
except rospy.ROSInterruptException:
pass
运行这两个节点,体会发布者和接受者的运行效果。
使用 rqt_graph 指令观察信息流:
需要注意的是,只要 Topic 和 Message 能够对应,那分别使用 C++和 Python 语言编写的节点也可以互相通信,这就是 ROS 多语言支持的魅力所在。
首先打开卓越之星的电源,将AI处理器与PC连接到同一个局域网,使用Nomachine或Remote-SSH进入开发模式;
然后打开一个终端,输入roscore,启动ros的master;
在ROS中创建一个工作空间;
在这个工作空间中创建一个功能包;
在功能包内,基于C++实现一个ROS标准的String类型的发布者/接收者;
在功能包内,基于Python实现一个ROS标准的String类型的发布者/接收者;
运行上述节点,观察实验结果;
在功能包内,自定义一个消息,包含一个int型变量和一个String型变量;
在这个功能包内,基于Python/C++实现自定义消息的发布者/接收者;
思考发布/订阅模式的通讯优势是什么?
如何合理命名以保证可维护性和合理性?
什么是ROS中的主题?它在机器人系统中的作用是什么?
主题与消息之间的关系是什么?如何理解它们之间的通信机制?
如何创建和发布一个自定义的主题?需要考虑哪些因素?
如何订阅一个主题并接收消息?请举例说明。
在ROS中,消息的序列化和反序列化是如何进行的?这对主题通信有什么影响?
如何确保主题通信的实时性和可靠性?有哪些优化策略?
如何在ROS中实现主题消息的持久化存储?这对于机器人系统的数据记录和分析有何意义?
ROS中的主题通信与传统的消息队列技术有何异同?它们的优缺点分别是什么?
请描述一下在ROS中使用主题进行数据传输时可能遇到的挑战和解决方案。
如何使用ROS中的服务来实现与主题的交互?服务相比主题有何优势?