ROS (Robot Operating System),即机器人操作系统,是为了加快机器人的开发效率,提高机器人软件代码的复用率,用于机器人算法快速仿真验证的一个软件平台。
ROS已经成为机器人领域的一个普遍标准。换句话说,未来的任何一个机器人工程师都必须会ROS,越来越多的机器人也将基于ROS来进行开发。
ROS主要可以由以下四个部分组成: ROS = 通信机制 + 开发工具 + 应用功能 + 生态系统。
ROS通信机制中有两大通信模型:
发布/订阅者模型是基于话题(topic)和消息(message)来实现的。发布者负责发布某一话题topic,而话题的内容就是消息message,其有属于自己的消息类型。当某个话题被发布后,该话题的订阅者便会接收到该消息。
举个例子。
我们打算编写这样一个节点:该节点名为“velocity_publisher”,负责发布turtlesim仿真器的海龟速度消息,而海龟仿真器负责订阅该话题消息。
再编写程序之前,我们先来看看ROS的海龟仿真器turtlesim。
终端输入:roscore
,启动ros_master。
终端输入:rosrun turtlesim turtlesim_node
,启动海龟仿真器,界面如下。
终端输入:rostopic list
,查看当前环境的话题消息。
可见,当前环境中已经有了话题/turtle1/cmd_vel
,turtle1
表示仿真器中产生的第一只海龟,cmd_vel
表示速度指令。
终端输入:rostopic info /turtle1/cmd_vel
,获取该话题的具体信息。
通过终端输出,我们可以知道话题的三个重要信息:消息类型、发布者和订阅者。
turtle1/cmd_vel
话题消息的类型是geometry_msgs/Twist
,发布者无,订阅者是/turtlesim
。即,/turtlesim
已经自动订阅了话题消息/turtle/cmd_vel
,我们需要做的仅仅是编写一个发布/turtle1/cmd_vel
的节点。
终端输入:rosmsg show geometry_msgs/Twist
,获取Twist消息的具体消息格式。
Twist
消息格式主要分为两部分:linear
和angular
。其中,linear
指海龟在空间三个维度上的线速度,angular
指海龟在空间三个维度上的角速度。
首先,我们选择C++来编写程序,直接上代码,,详细可参考代码中的注释。
#include //导入ros功能包
#include //导入geometry_msgs::Twist消息类型
/*
功能:创建一个名为“velocity_publisher”的节点,负责发布海龟的速度指令
*/
int main(int argc,char **argv)
{
//创建一个名为“velocity_publisher”的节点
ros::init(argc,argv,"velocity_publisher");
//创建节点句柄,用于管理节点对象
ros::NodeHandle n;
/*
创建一个发布者对象: turtle_vel_pub,负责发布海龟速度消息
消息类型:geometry_msgs::Twist(ros定义的关于海龟的速度消息类型)
消息名字:/turtle1/cmd_vel
消息队列长度:10
*/
ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);
//创建Rate对象,设置速度消息发布频率 单位Hz 10次/s
ros::Rate pub_rate(10);
while (ros::ok())
{
//创建消息对象,并初始化数据
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = 0.3;
vel_msg.angular.z = 0.3;
//发布速度消息
turtle_vel_pub.publish(vel_msg);
//打印日志信息
ROS_INFO("Publishing turtle velocity command:[%0.2f m/s,%0.2f rad/s]",vel_msg.linear.x,vel_msg.angular.z);
//配合发布频率进行自适应延时
pub_rate.sleep();
}
return 0;
}
编写好C++代码后,我们还需要在CMakeLists.txt中配置编译规则。
add_executable(velocity_publisher src/velocity_publisher.cpp)
target_link_libraries(velocity_publisher ${catkin_LIBRARIES})
终端输入:
roscore
rosrun turtlesim turtlesim_node
rosrun learning_topic velocity_publisher
运行结果:
发现了嘛,我们的小海龟已经在做圆周运动了。
终端输入:rqt_graph
,利用Qt工具箱得到当前环境的计算图。
通过rqt_graph
我们可以直观的了解当前环境中各个节点之间的关系。
python 版代码
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
"""
func: creating a node to publish a topic of /turtle1/cm_vel
"""
import rospy # 导入rospy库
from geometry_msgs.msg import Twist # 导入Twist消息类型
def velocity_publisher():
# 创建一个名为“velocity_publisher”的节点
rospy.init_node("velocity_publisher", anonymous=True)
# 创建一个发布者:turtle_vel_pub
# 消息名称:/turtle1/cmd_vel
# 消息类型:Twist
# 消息队列长度:10
turtle_vel_pub = rospy.Publisher("/turtle1/cmd_vel", Twist, queue_size=10)
# 创建消息发布频率对象,10次/s
pub_rate = rospy.Rate(10)
while not rospy.is_shutdown():
# 创建消息,并初始化数据
vel_msg = Twist()
vel_msg.linear.x = 0.3
vel_msg.angular.z = 0.3
# 发布消息
turtle_vel_pub.publish(vel_msg)
# 打印日志信息
rospy.loginfo("Publishing turtle velocity commanding: [% 0.2f m/s, %0.2f rad/s]", vel_msg.linear.x,
vel_msg.angular.z)
# 按照发布频率进行延时
pub_rate.sleep()
if __name__ == "__main__":
try:
velocity_publisher()
except rospy.ROSInterruptException:
pass
小提示:在运行.py文件之前,确保右击文件,在属性中修改.py可作为可执行文件执行。
这里,我们小结一下编写节点,发布话题的步骤:
前面我们已经实现了编写一个节点用来发布海龟的速度指令信息。这一节,我们将编写一个节点,负责订阅海龟的位置信息。
不知道你们有没有发现在启动海龟仿真器turtlesim
后,话题消息列表(见前面)中出现了/turtle1/pose
这个话题?那我们来看看它的具体信息。
终端输入:
roscore
rosrun turtlesim turtlesim_node
rostopic info /turtle1/pose
终端输出结果如下:
可知,话题消息/turtle1/pose
的发布者是turtlesim
,订阅者无。所以,我们需要做的仅仅是编写一个节点,负责订阅海龟的位置信息即可。
终端输入:rosmsg show geometry_msgs/Pose
终端输出:
可知,Pose
话题消息格式包含两个部分:position
和orientation
,其中position
代表三维空间中的位置,orientation
代表的是三维空间中的方位。
C++代码如下:
#include // 导入ros cpp库
#include "turtlesim/Pose.h" // 导入Pose消息类型
// 订阅者注册的回调函数
void poseCallback(const turtlesim::Pose::ConstPtr &msg)
{
//将接收到的消息打印出来,这里只显示平面二维坐标
ROS_INFO("The turtle pose: x:%0.6f, y: %0.6f",msg->x,msg->y);
}
int main(int argc,char **argv)
{
// 创建节点
ros::init(argc,argv,"pose_subscriber");
// 创建节点句柄
ros::NodeHandle n;
/*
创建一个订阅者:turtle_pose_sub
订阅消息名称:/turtle1/pose
订阅消息类型:Pose
消息队列长度:10
回调函数:poseCallback
*/
ros::Subscriber turtle_pose_sub = n.subscribe("/turtle1/pose",10,poseCallback);
//循环等待回调函数
ros::spin();
return 0;
}
同样,在CMakeLists.txt配置编译规则
add_executable(pose_subscriber src/pose_subscriber.cpp)
target_link_libraries(pose_subscriber ${catkin_LIBRARIES})
终端输入:
roscore
rosrun turtlesim turtlesim_node
rosrun learning_topic velocity_publisher
rosrun learning_topic pose_subscriber
终端输出:
我们可以看到,小海龟在做圆周运动,同时它的位置也被实时输出到终端。
终端输入:rqt_graph
,查看计算图。
现在来总结一下,编写一个节点,实现订阅者的步骤:
python版本代码
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
"""
func: subscribing the pose information of a turtle
"""
import rospy # 导入rospy
from turtlesim.msg import Pose # 导入Pose类型
def poseCallback(msg):
# 打印位置信息
rospy.loginfo("The turtle's pose: x: %0.6f, y:%0.6f", msg.x, msg.y)
def pose_subscriber():
# 创建一个名为"pose_subscriber"的节点
rospy.init_node("pose_subscriber", anonymous=True)
# 订阅位置信息
rospy.Subscriber("/turtle1/pose", Pose, poseCallback)
# 循环等待回调函数
rospy.spin()
if __name__ == "__main__":
pose_subscriber()
前面,我们分别单独介绍了如何编写一个节点实现发布者发布消息和订阅者订阅消息。但是,我们使用的都是ROS库封装好的消息结构,那我们可不可以定义自己的话题消息结构,然后实现发布和订阅呢?
答案是可以的!
ROS提供了一套机制,让用户可以定义自己想要的话题消息结构。
首先,我们在功能包中创建一个msg
文件夹,然后在该文件夹下创建一个Person.msg
文件,在该文件中我们添加以下内容:
string name
string sex
uint8 age
在Person.msg
中我们定义了三个数据:name 、sex、age,用来表征一个人的信息。
接下来我们需要在CMakeLists.txt和package.xml中添加相关内容进行配置。
在CMakeLists.txt中添加:
find_package( ...... message_generation)
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(...... message_runtime)
在package.xml中添加:
<build_depend>message_generationbuild_depend>
<exec_depend>message_runtimeexec_depend>
到此,我们的自定义消息结构便配置完成了,然后在工作空间下进行编译。
终端输入:catkin_make
编译后,我们在/devel/include/learning_topic
目录下可以发现ROS生成了一个Person.h
头文件,该文件中的内容就是一些关于我们自定义消息结构的相关配置说明。
现在我们来检查以下自己创建的消息类型是否成功。
终端输入:rosmsg show learning_topic/Person
可见,我们已经成功定义了自己的话题消息结构learning_topic/Person
接下来,便是编写发布者节点和订阅者节点了,具体操作步骤同(一)(二),这里就不详细介绍了,具体参考代码。
发布者:person_publisher.cpp
/*
func: 发布/person_info话题消息,自定义消息类型learning_topic::Person
*/
#include
#include "learning_topic/Person.h" //加入自定义消息结构头文件Person.h
int main(int argc,char **argv)
{
//创建节点
ros::init(argc,argv,"person_publisher");
//创建节点句柄
ros::NodeHandle n;
/*
创建发布者:person_info_pub
消息名称:/person_info
消息类型:learning_topic
消息队列长度:10
*/
ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info",10);
//设置消息发布频率
ros::Rate pub_rate(10);
while(ros::ok())
{
//创建消息,并初始化数据
learning_topic::Person person_msg;
person_msg.name = "Steve";
person_msg.sex = "male";
person_msg.age = 25;
//发布消息
person_info_pub.publish(person_msg);
//打印日志信息
ROS_INFO("publishing person info: name:%s sex: %s age:%d",person_msg.name.c_str(),person_msg.sex.c_str(),person_msg.age);
//按照发布频率延时
pub_rate.sleep();
}
return 0;
}
订阅者:person_subscriber.cpp
/*
func:订阅话题消息/person_info
*/
#include
#include "learning_topic/Person.h"
void personCallback(const learning_topic::Person::ConstPtr &msg)
{
//打印接收到的信息
ROS_INFO("Subscribing peron info: name %s sex:%s age:%d",msg->name.c_str(),msg->sex.c_str(),msg->age);
}
int main(int argc,char **argv)
{
//创建节点
ros::init(argc,argv,"person_subscriber");
//创建节点句柄
ros::NodeHandle n;
/*
创建订阅者:person_info_sub
消息名称:/person_info
回调函数:personCallback
*/
ros::Subscriber person_info_sub = n.subscribe("/person_info",10,personCallback);
//循环等待回调函数
ros::spin();
return 0;
}
配置编译规则
add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)
add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)
在工作空间目录下编译
catkin_make
终端输入
roscore
rosrun learning_topic person_subscriber
rosrun learning_topic person_publisher
运行效果:
终端输入:rqt_graph
,查看当前环境的计算图。
到此,我们便是简单地介绍了ROS的发布/订阅者模型。接下来,我们将会学习ROS的另外一大通信模型:服务/客户端模型。
古月居