一起学ROS之通信机制
首先很抱歉这段时间一直在准备毕业设计答辩,很久都没更博了,各位读者是不是想死我了,哈哈哈哈哈哈哈哈哈。
近期,接着毕设的RGBD-SLAM(离线)要做成实时的SLAM了,要做实时的SLAM离不开机器人平台ROS,尤其是在具体的机器人上运行的时候使用ROS更加方便。因此开始了ROS的学习,ROS(机器人操作平台,Robot Operating System)是面向机器人的开源的元操作系统。。。。。。。。。。。。此处省略n字(想详细了解ROS历史及其意义的同学可以自行查阅)
直入主题,其优越性在于其通信机制,那么它的通信机制是什么呢?分为两种,一是话题消息机制,二是服务调用机制。前者为主要通信机制也是常用的通信机制,后者作为了解,我们这里也做简单介绍。
一、话题消息机制
首先了解一下几个名词
节点:ROS程序的运行实例称为节点。话题:供消息传递的平台称为话题。消息:ROS中传递的信息称为消息。
消息的传递即ROS的通信需要发布和订阅消息,发布方和订阅方分别是由一个节点实现。
该机制有如下特性:
所有订阅该话题的节点都会收到该话题上的所有消息,不管你是否想要接收。
详细点讲:”生产”消息的程序只管发布消息,而不用关心该消息是如何被消费的; 消费消息的程序只管订阅该话题或者他所需要消息的所有话题,而不用关心这些消息数据是如何生产的。
这个特性十分重要,这就意味着我们在这种通信机制中是一对多或者多对一的机制,而在下一种通信机制中将是一对一的机制!
ROS项目的构建过程:
(1)建立文件夹即工作区,另在工作区中建src文件夹
(2)创建功能包,在src文件夹目录下运行命令:catkin_create_pkgpackage-name
(3)在src下生成了两个文件,package.xml和CMakeLists.txt,将你写的源代码放入到src目录下
(4)书写源代码,package.xml和CMakeLists.txt文件
(5)编译工作区在工作区目录下运行catkin_make
(6)在工作区目录下运行source devel/setup.bash,这个命令是设置若干的环境变量,使得ROS能够找到你创建的功能包和新生成的可执行文件
(7)rosrunpackage-name executable-name 在ROS下运行可执行文件
ROS项目中package.xml的书写:
<build_depend>package-name</build_depend> 编译依赖
<run_depend>package-name</run_depend> 运行依赖
该文件主要是将所用到的依赖库列举出来
ROS项目中CMakeLists.txt的书写:
一般会加入catkin包命令为:
find_package(catkin REQUIED COMPONENTSpackage-names)
#include<ros/ros.h>头文件在包roscpp中因此,只要引用这个头文件,在添加包名字时必须要添加roscpp
在最后指定链接库时需要:
add_executable(executable-name source.cpp-name)
target_link_libraries(executable-name ${catkin_LIBRARIES})
然后如果需要的话我们需要将可执行的输出路径改变一下,默认在build路径中,我们改为当前工作目录下:set(EXECUTABLE_OUTPUT_PATH${PROJECT_SOURCE_DIR})
举例:
下边代码为发布者程序,将海龟的移动作为对象,将随机产生的海龟速度发送到turtle1/cmd_vel话题中,使得订阅该话题消息的节点都做出相应动作,其代码的注释在下边已经给出,并且我将源代码上传至本博客。改代码引自A Gentle Introduction to ROS,Jason M.O'Kane一书
#include<geometry_msgs/Twist.h> //消息类型所用数据结构的包
#include<ros/ros.h> //ros系统所用基本函数的包
#include<stdlib.h>
int main(int argc,char** argv)
{
ros::init(argc,argv,"public"); //初始化ROS系统
ros::NodeHandle nh; //定义ROS节点
ros::Publisher pub=nh.advertise<geometry_msgs::Twist>("turtle1/cmd_vel",1000); //定义所要发送到的话题
srand(time(0)); //定义随机变量
ros::Rate rate(2); //定义发送频率为每秒钟2次即2HZ
while(ros::ok()) //循环发送部分
{
geometry_msgs::Twist msg; //定义所发送消息的类型对象
//给消息对象赋值
msg.linear.x=double(rand())/double(RAND_MAX);
msg.angular.z=2*double(rand())/double(RAND_MAX)-1;
pub.publish(msg); //将消息发送到话题上,使得订阅该话题的节点都能接受到该消息
ROS_INFO_STREAM("sending random velocity command:"<<"linear = "<<msg.linear.x<<"angular="<<msg.angular.z); //打印消息的内容
rate.sleep(); //延时,使得发送频率确定为rate所提供的频率2HZ
}
}
下边代码是订阅者代码的程序,订阅的是turtle1/pose话题的消息,回调函数中将其打印,这里一定要注意回调函数的引用 &函数名 的方式
ros::spin()是必不可少的,这个保证了在改话题上的消息会传送到该节点,注意它和ros::spinOnce();的区别,前者是无限次循环该回调函数,知道该节点被销毁,而后者则是执行回调函数后将控制权返还给我们。
#include<ros/ros.h>
#include<turtlesim/Pose.h> //接受消息的类型
#include<iomanip> //setprecision()等函数的头文件
void poseMessageReceived(const turtlesim::Pose& msg) //回调函数
{
ROS_INFO_STREAM(std::setprecision(2)<<std::fixed<<"position = ("<<msg.x<<","<<msg.y<<")"<<"*direction = "<<msg.theta);//打印相关信息
}
int main(int argc,char** argv)
{
ros::init(argc,argv,"subscribe_to_pose"); //ros初始化
ros::NodeHandle nh; //ros节点
//接受消息,第一个参数是消息所在的话题名称,1000消息缓存大小 第三个参数是回调函数
ros::Subscriber sub=nh.subscribe("turtle1/pose",1000,&poseMessageReceived);
ros::spin();//确保在消息来时会接受到该消息
}
二、服务调用机制
服务调用与消息的区别在于:
服务调用是双向的,一个节点给另一个节点发送信息并等待响应,因此信息流是双向的。作为对比,当消息发布后,并没有响应的概念,甚至不能保证系统内有节点订阅了这些消息。
服务调用实现的是一对一通信。每一个服务由一个节点发起,对这个服务的响应返回同一个节点。另一方面,每一个消息都和一个话题相关,这个话题可能有很多的发布者和订阅者。
如图所示:
命令行查看
主要机制是客户端发送请求到服务器节点,服务器节点发送相应到客户端节点,请求响应数据所携带的特定内容称为服务数据类型,服务数据类型分为两部分一部分为客户端发送给服务器节点的数据类型另一部分为服务器节点发送给客户端节点的数据类型,查看数据类型一般直接在终端命令行中输入:rossrv show service-data-type-name,service-data-type-name为服务类型名,服务类型名用命令rosservice info service-name 来查看Type中就是,service-name是服务名就和话题名类似的一个东西,也可用命令:rosservice list 来查看当前的服务列表
例:
客户端程序:
#include <ros/ros.h> //ros基本命令头文件
#include <turtlesim/Spawn.h> //服务数据类型名头文件
int main( int argc , char ** argv ) {
ros::i n i t ( argc , argv , "spawn_turtle") ; //ros初始化
ros::NodeHandle nh ;
ros::ServiceClient spawnClient = nh.serviceClient <turtlesim::Spawn>("spawn") ; //客户端声明,turtlesim::Spawn表示服务数据类型,spawn为我们想调用的服务名称,一般为相对名称
turtlesim::Spawn::Request req ; //定义客户端请求的数据类型
turtlesim::Spawn::Response resp ; //定义服务器端相应的数据类型
req.x = 2;
req.y = 3;
req.theta = M_PI/2;
req.name = "Leo" ;
bool success = spawnClient.call( req , resp ) ; //客户端发出请求并等待服务器端响应
i f ( success ) //如果成功就输出所建立的海龟的名称
{
ROS_INFO_STREAM("Spawned a turtle named "<< resp.name) ;
}
Else //否则输出建立失败
{
ROS_ERROR_STREAM("Failed␣ to␣ spawn.") ;
}
}
服务器端程序:
// This program toggles between rotation and translation
// commands, based on calls to a service .
#include <ros/ros.h>
#include <std_srvs/Empty.h>
#include <geometry_msgs/Twist.h>
bool forward = true ;
bool toggleForward (
std_srvs::Empty::Request &req ,
std_srvs::Empty::Response &resp
)//回调函数,每次调用将forward变量取反
{
forward = !forward ;
ROS_INFO_STREAM("Now␣ sending␣ " << ( forward ?
"forward" : " rotate ") << "␣ commands.") ;
return true ;
}
int main( int argc , char ** argv ) {
ros::i n i t ( argc , argv , "pubvel_toggle") ;
ros::NodeHandle nh ;
// Register our service with the master .
ros::ServiceServer server = nh.advertiseService (
"toggle_forward" , &toggleForward ) ;//定义服务器对象,toggle_forward为我们想要提供服务的名称,toggleForward为回调函数函数名
// Publish commands, using the latest value for forward ,
// until the node shuts down.
ros::Publisher pub = nh.advertise <geometry_msgs::Twist>(
"turtle1/cmd_vel" , 1000) ;//定义发布者对象,发布的话题为:turtle1/cmd_vel,缓存为1000
ros::Rate rate (2) ;
while ( ros::ok () )//发布的消息为根据forward的值来改变内容,forward为0,那么就直线行走,如果为1则原地旋转
{
geometry_msgs::Twist msg ;
msg.linear.x = forward ? 1.0 : 0.0 ;
msg.angular.z = forward ? 0.0 : 1.0 ;
pub.publish (msg) ;
ros::spinOnce () ;
rate.sleep () ;
}
}
到这里本文就结束了,本文主要目的是在于简要介绍一下ROS的通信机制,并给出每个通信机制的范例,如果引用到实际应用当中,当然还需要进一步的学习与深究,这里也是只做简单的介绍,等本人有了进一步的了解之后再向大家介绍。。。。