本篇博客是慕课《ROS机器人操作系统入门》的第二篇笔记,慕课网址是这里。本篇笔记主要记录Topic在C++和Python下的两种实现方式以及需要注意的问题。
1.任务描述与分析
有一个GPS传感器(模拟)以一定间隔发送位置信息,我们需要获得它发送的位置信息并进行处理,解算当前位置到坐标原点的距离。
在这个场景下,使用自定义消息格式的Topic-Message(publish-subscribe)通信方式比较合适。可以新建一个叫gps_processer
的包(package
),这个包中有publisher
和subscriber
两个节点(Node
),publisher
用于获取、发送GPS传感器信息,subscriber
用于信息处理。定义Topic
的名称是gps_info
。Message
的格式是float
类型的x
、y
以及string
类型的当前工作状态state
。模拟数据的生成规则是x
、y
分别从1.0、2.0开始,x=x+1.3
,y=y+1.5
。
2.Topic的C++实现
以下步骤是在创建好了工作空间(本机工作空间路径/root/catkin_ws
)的情况下继续的,如果还没有创建好工作空间,可以参考这篇笔记创建工作空间,然后再回来。
(1)创建Package
创建Package
有多种方式,这里介绍最原始的手动和使用RoboWare这个IDE的两种不同方式。
a.终端中手动创建
在终端中输入如下内容即可创建一个Package
。
cd catkin_ws/src/
catkin_create_pkg gps_processer roscpp std_msgs message_generation message_runtime
第一行是切换到src
目录下,第二行是在当前目录下创建一个叫gps_processer
的包,并且包含roscpp
、std_msgs
、message_generation
、message_runtime
依赖,后两个是用到Message
的包都要包含的依赖。
b.RoboWare自动创建
打开RoboWare,在”资源管理器”中找到src
文件夹,右键单击选择”新建ROS包”,输入名称即可创建Package
。创建完成后右键Package
选择”编辑依赖的ROS包列表”,输入roscpp
、std_msgs
、message_generation
、message_runtime
(空格隔开)即可添加依赖。
(2)创建自定义消息文件
a.手动创建文件
在gps_processer
包的根目录下新建一个msg
文件夹,在其中新建一个叫做gps.msg
的文件即可。
b.RoboWare创建文件
在”资源管理器”中找到gps_processer
文件夹,右键单击选择”新建Msg文件夹”,在新建的msg
文件夹上右键选择”新建MSG文件”,输入名称即可创建。
文件创建好后在文件中写入如下内容并保存。
float32 x
float32 y
string state
需要注意的是Message
的基本数据类型和常规的C++有些区别。例如在Message
中int
进一步细分成了int6
、int32
、int64
,float
细分成了float32
和float64
。所以在Message
文件中如果写int
是会报错的。
(3)编译自定义消息文件
a.手动方式
在编译自定义消息文件为头文件之前,需要修改gps_processer
包的CMakeLists.txt
文件,有以下需要修改的地方。
首先在自动生成的CMakeLists.txt
文件中找到被注释掉的add_message_files(FILES message1.msg)
,将其取消注释,并将自定义的msg
文件名称填入,这行命令用于向项目中添加刚刚自定义的消息文件。
然后找到被注释掉的generate_messages(DEPENDENCIES std_msgs)
将其取消注释,这行命令用于生成自定义的消息文件。
修改后的文件如下。
b.RoboWare方式
如果是在RoboWare里通过菜单新建的msg
文件则无需修改,RoboWare已经自动修改好了CMakeLists.txt
中的对应内容。
完成了CMake文件的修改之后即可以调用catkin_make
进行编译了。在工作空间的根目录下catkin_make
即可。之后我们可以在/root/catkin_ws/devel/include/gps_processer
找到生成的gps.h
头文件了,后续调用它即可。
(4)创建publisher节点
a.手动方法
在包的根目录下新建一个src
文件夹,然后在其中新建一个publisher.cpp
文件,并在刚刚修改的CMakeLists.txt
文件末尾添加如下内容,用于添加可执行文件:
add_executable(publisher
src/publisher.cpp
)
add_dependencies(publisher ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(publisher
${catkin_LIBRARIES}
)
b.RoboWare方法
除了手动添加,也可以在RoboWare中自动添加。具体做法是在RoboWare的资源管理器中找到gps_processer
,在src
文件夹上右键,然后选择”新建CPP源文件”,输入名称为publisher.cpp
并回车,然后会弹出一个选择的对话框如下。
这是让你指定新建的这个cpp
文件是作为一个库文件还是作为一个可执行文件,选择不同选项后,程序会自动帮你在CMakeLists.txt
文件中填好相关内容。当然,如果不选也可以,自己手动填写就可以了。这里由于我们是希望生成一个可执行文件的,所以选择”加入到新的可执行文件中”即可。
(5)编写publisher节点代码
对于一个Topic的发布者,需要以下几个步骤:初始化ROS(ros::init()
)、新建NodeHandle(ros::NodeHandle
)、构造Publisher(nh.advertise()
)、循环发布消息(pub.publish()
)。具体代码如下:
#include<ros/ros.h> // ROS基本头文件
#include<gps_processer/gps.h> // 引用自定义生成的消息类型
int main(int argc, char *argv[])
{
// 设置消息初值
gps_processer::gps msg;
msg.x=1.0;
msg.y=2.0;
msg.state="working";
// 初始化一个叫做publisher的节点
ros::init(argc, argv, "publisher");
ros::NodeHandle nh; // 新建一个NodeHandle用于管理节点
// 利用NodeHandle构造一个publisher
ros::Publisher pub = nh.advertise<gps_processer::gps>("gps_info",1);
// 设置循环迭代间隔并发布消息
ros::Rate loop_rate(1.0);
while (ros::ok())
{
msg.x += 1.3;
msg.y += 1.5;
ROS_INFO("Publisher:GPS:x = %f,y = %f",msg.x,msg.y);
pub.publish(msg);
loop_rate.sleep();
}
return 0;
}
(6)创建subscriber节点
a.手动方法
在src
文件夹中新建一个subscriber.cpp
文件,并在CMakeLists.txt
文件末尾添加如下内容,用于添加可执行文件:
add_executable(subscriber
src/subscriber.cpp
)
add_dependencies(subscriber ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(subscriber
${catkin_LIBRARIES}
)
b.RoboWare方法
除了手动添加,也可以在RoboWare中自动添加。具体做法是在RoboWare的资源管理器中找到gps_processer
,在src
文件夹上右键,然后选择”新建CPP源文件”,输入名称为subscriber.cpp
并回车。选择”加入到新的可执行文件中”即可。
(7)编写subscriber节点代码
对于一个Topic的接收者,需要以下几个步骤:初始化ROS(ros::init()
)、新建NodeHandle(ros::NodeHandle
)、定义回调函数、构造Subscriber(nh.subscribe()
)、循环接收消息(ros::spin()
)。具体代码如下:
#include<ros/ros.h>
#include<gps_processer/gps.h>
#include<std_msgs/Float32.h> //ROS中的Float32类型
void gpsCallback(const gps_processer::gps::ConstPtr &msg)
{
std_msgs::Float32 distance;
distance.data = sqrt(pow(msg->x,2)+pow(msg->y,2));
ROS_INFO("Subscriber:Distance to origin = %f,state = %s",distance.data,msg->state.c_str());
}
int main(int argc, char *argv[])
{
ros::init(argc, argv, "subscriber");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("gps_info",1,gpsCallback);
ros::spin();
return 0;
}
(8)编译运行测试
最后在Catkin工作空间根目录下打开终端,使用catkin_make
进行编译,编译成功后如下:
然后先输入roscore
打开Master
,然后在新终端中输入rosrun gps_processer publisher
启动publisher
节点,新终端中输入rosrun gps_processer subscriber
启动subscriber
节点。如下:
可以看到成功运行了刚刚写的两个节点,并且实现了消息的收发与处理。最后简单说一下,生成的两个节点的可执行文件放在了/root/catkin_ws/devel/lib/gps_processer
里,直接在终端中运行这个可执行文件也是可以的(Master
已经运行的情况下)。
3.Topic的Python实现
(1)新建Package
a.手动方法
在终端中输入如下内容即可创建一个Package。
cd catkin_ws/src/
catkin_create_pkg gps_processer rospy std_msgs message_generation message_runtime
第一行是切换到src
目录下,第二行是在当前目录下创建一个叫gps_processer
的包,并且包含rospy
、std_msgs
、message_generation
、message_runtime
依赖,后两个是用到Message
的包都要包含的依赖。
b.RoboWare方法
打开RoboWare,在”资源管理器”中找到src
文件夹,右键单击选择”新建ROS包”,输入名称即可创建Package
。创建完成后右键Package
选择”编辑依赖的ROS包列表”,输入rospy
、std_msgs
、message_generation
、message_runtime
(空格隔开)即可添加依赖。
(2)新建消息文件
与前面自定义消息文件内容一模一样。
(3)编译消息文件
与前面编译自定义消息文件内容一模一样。
(4)新建publisher节点文件
根据之前的习惯,src
文件夹里一般放C/C++文件,Python代码一般放在scripts
文件夹里。
a.手动方法
在package
的根目录下创建一个scripts
文件夹,然后新建一个publisher.py
文件。由于Python是脚本,并不需要CMake编译,因此创建好文件就可以写代码了。
b.RoboWare方法
在包名上右键点击,选择”新建文件夹”,输入名称为scripts
,在scripts
文件夹上右键选择”新建文件”,输入publisher.py
即可。
(5)编写publisher代码
参考之前笔记说过的rospy
的API,代码如下。
# coding=utf-8
import rospy
from gps_processer.msg import gps
def gps_pub():
# 数据的初始化
x = 1.0
y = 2.0
state = 'working'
# 初始化节点
rospy.init_node('publisher')
# 创建发布对象
pub = rospy.Publisher('gps_info', gps, queue_size=1)
# 设置循环间隔
rate = rospy.Rate(1.0)
while not rospy.is_shutdown():
x += 1.3
y += 1.5
rospy.loginfo('Publisher:GPS:x = %f,y = %f', x, y)
# 发布消息
pub.publish(gps(x, y, state))
rate.sleep()
if __name__ == '__main__':
gps_pub()
(6)新建subscriber节点文件
a.手动方法
在scripts
文件夹下创建subscriber.py
即可。
b.RoboWare方法
在scripts
文件夹上右键选择”新建文件”,输入subscriber.py
即可。
(7)编写subscriber代码
Python代码如下:
# coding=utf-8
import math
import rospy
from gps_processer.msg import gps
def callback(gps):
distance = math.sqrt(math.pow(gps.x, 2)+math.pow(gps.y, 2))
rospy.loginfo('Subscriber:Distance to origin = %f,state = %s',
distance, gps.state)
def gps_sub():
# 初始化节点
rospy.init_node("subscriber")
# 开始订阅
rospy.Subscriber('gps_info', gps, callback)
# 循环
rospy.spin()
if __name__ == '__main__':
gps_sub()
(8)运行测试
最后在终端中输入roscore
启动Master
,在scripts
目录下打开新终端然后分别输入python publisher.py
启动发布节点,python subscriber.py
启动接收节点。运行结果如下:
4.Topic的C++与Python实现比较
通过上面的介绍可以看到,ROS中Topic的使用总体而言分为四步:新建包、自定义Topic消息、编写代码以及编译运行。
对于C++和Python在实现起来整体步骤基本是相同的,只是由于API差异导致在代码编写这一步差别较大。其它步骤相同或有细微差别,如由于Python不需要编译因此没有catkin_make
环节。具体对比如下表:
步骤 | C++ | Python |
---|---|---|
创建Package | 依赖roscpp |
依赖rospy |
创建消息文件 | 相同 | 相同 |
编译消息文件 | 步骤相同,结果不同。生成.h 文件,放在catkin_ws/devel/include/pkg_name |
步骤相同,结果不同。生成.py 文件,放在catkin_ws/devel/lib/python2.7/dist-packages/pkg_name |
创建publisher节点 | 在src 下 |
在scripts 下 |
编写publisher节点代码 | 使用C++ API | 使用Python API |
创建subscriber节点 | 在src 下 |
在scripts 下 |
编写subscriber节点代码 | 使用C++ API | 使用Python API |
运行测试 | 运行前需要调用catkin_make 编译 |
无需编译直接运行脚本 |
如果一个项目中既会用到C++也会用到Python,那么在一开始新建Package
的时候就同时依赖roscpp
和rospy
,这样方便一些。
最后将所有代码上传到了Github,点击查看。
本文作者原创,未经许可不得转载,谢谢配合