机器人操作系统ROS:从入门到放弃(一) 发布接收消息 - 简书
一、发布消息
主要是 ROS的publisher和subcribe
ROS 常用的概念
1. message: 消息,机器人需要传感器,传感器采集到的信息,也就是这里所说的message。
2. topic: 话题,假设我们有两个传感器,GPS和温度计.在ROS中我们需要给采集到的消息取个名字用来区分不同的message,这就是topic了。
3. node: 节点,
4. package: 一个ROS package包含了你要完成的一个项目的所有文件.
5. workSpace: Ros 工作空间,用来存放很多不同的package, 完成很多不同的项目。
6. publish, subscribe: ROS很大的一个作用就是传递message.为什么要传递消息呢? 打个比方你写了一个程序,用来获得GPS的讯息,写了另外一个程序,用来处理GPS的信息.这时你就是需要把采集到的信息传输到处理用的程序中.信息的传输在ROS中称为publish. 信息的接收(貌似中文ros wiki把这个叫做"订阅",我更喜欢翻译成"接收"了)在ROS中称为subscribe。【接收消息subscribe,传输消息 publish】
第一个ROS程序
1. 首先创建一个工作空间 workSpace
参考之前的博客:https://blog.csdn.net/chrishuimin/article/details/122731469
2. 创建一个package
cd ~/catkin_ws/src //进入workspace的src文件夹,所有package都要在这里面创建. 编译的时候ROS才能找得到.
catkin_create_pkg pub_sub_test std_msgs rospy roscpp //catkin_create_pkg是ROS自带的命令,表示要创建一个package了. pub_sub_test是这个package的名称,当然你也可以取任何名字. ROS的package命名习惯是不出现大写字母.后面的内容表示你这个ROS package的依赖项.
cd .. //回退到workspace文件夹
catkin_make //编译
完成上述命令后进入到catkin_ws/src
文件夹中你会发现多了一个pub_sub_test
文件夹,这就是你的新package了.
3. 写一个ROS 程序
(1) 写发布消息的程序(发布器)
打开一个terminal键入下面命令
cd ~/catkin_ws/src/pub_sub_test/src
touch pub_string.cpp //创建一个叫pub_string的c++文件
第一个例子我们还是会采用官网文档的例子: cn/ROS/Tutorials/WritingPublisherSubscriber(c++) - ROS Wiki
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/**
* This tutorial demonstrates simple sending of messages over the ROS system.
*/
int main(int argc, char **argv)
{
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line.
* For programmatic remappings you can use a different version of init() which takes
* remappings directly, but for most command-line programs, passing argc and argv is
* the easiest way to do it. The third argument to init() is the name of the node.
*
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
// init() 需要用到argc 和 argv ,第三个参数是 节点的名称
ros::init(argc, argv, "talker");
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
// NodeHandle是与ROS系统通信的主要接入点。构造的第一个NodeHandle将完全初始化该节点,而销毁的最后一个NodeHandle将关闭该节点。
ros::NodeHandle n;
/**
* The advertise() function is how you tell ROS that you want to
* publish on a given topic name. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. After this advertise() call is made, the master
* node will notify anyone who is trying to subscribe to this topic name,
* and they will in turn negotiate a peer-to-peer connection with this
* node. advertise() returns a Publisher object which allows you to
* publish messages on that topic through a call to publish(). Once
* all copies of the returned Publisher object are destroyed, the topic
* will be automatically unadvertised.
*
* The second parameter to advertise() is the size of the message queue
* used for publishing messages. If messages are published more quickly
* than we can send them, the number here specifies how many messages to
* buffer up before throwing some away.
*/
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
/**
* A count of how many messages we have sent. This is used to create
* a unique string for each message.
*/
int count = 0;
while (ros::ok())
{
/**
* This is a message object. You stuff it with data, and then publish it.
*/
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
/**
* The publish() function is how you send messages. The parameter
* is the message object. The type of this object must agree with the type
* given as a template parameter to the advertise<>() call, as was done
* in the constructor above.
*/
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
代码解读:
1: #include "ros/ros.h"
,你的节点(包含main函数的那个程序)如果要使用ROS都得包含这个头文件
2: #include "std_msgs/String.h"
,我们这个程序是用来发布一个String消息的
3:#include<sstream>
,sstream是c++自带的头文件,可以实现利用输入输出流的方式往string里写东西,并且还可以拼接string和其他类型的变量.代码中的
std::stringstream ss;
ss<<"hello world"<<count;
实现了string hello world
和int变量 count
的拼接,形成一个新的string.即如果count是1,那么helloworld1
会作为string被存放在ss当中。
4: ros::init(argc, argv, "talker")
,在程序初始化ros,这行代码也是你几乎所有节点都需要的.talker
就是node(节点)的名字.即你这段程序在ROS当中的名字叫talker
.
5:ros::NodeHandle n
,官方的解释是nodeHandle是和ROS系统通讯的重要工具。NodeHandle
的对象名按照习惯一般取为n,nh(推荐)
什么的
【4、5是初始化ROS必须的】
6:ros::Publisher
这一行定义你要publish的信息和信息的名字了。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
(1)
n.advertise
通过NodeHandle的对象n告诉ROS系统我要创建一个可以发布信息的对象了 。
(2)发布的信息的类型 <std_msgs::String>
告诉ROS我要发布的是标准信息中的String类型。
(3)信息的名字:chatter,即之前说的topic,
(4) 数字1000表示 要缓冲的信息数量。
7: ros::Rate loop_rate(10)
表示发布信息的速度为10Hz
,用于
控制发布的信息的快慢。
这个函数要和loop_rate.sleep()
配合使用才能达到控制速度目的。
8: while(ros::ok())
要是ros不OK,程序就退出了。
9: std_msgs::String
msg
定义了std_msgs::String的对象msg
. 这是我们要发布的信息。
10:msg.data = ss.str()
这一行的前面两行我们在3
中已经讲了。这一行把ss.str()
赋值给msg.data
。
ss
是stringstream
的对象,它现在储存了hello world 0
这个string类型的变量(假设此时count = 0),提取stringstream
中string
的方法就是ss.str()
。
11: ROS_INFO()
这一行就可以理解为ROS里的printf()
就可以了。
12: chatter_pub.publish(msg)
,前面我们定义了一个chatter_pub用来发布信息。在我们完成了把std::string
放到std_msgs::String
的msg
之后,我们就可以发布这个信息了。方法就是这个直观的名字pusblish()
.
13: ros::spinOnce(),
这个函数是用于接收器的,必须要有spinOnce或者spin,ROS才会检测是不是接收到信息。
(2) 写接收消息的程序(接收器)
既然发布器写完,我们就要写接收器了。
打开一个terminal,输入下面命令,新建一个c++文件。
cd ~/catkin_ws/src/pub_sub_test/src
touch sub_string.cpp
代码:带注释的参考上面的链接
#include "ros/ros.h"
#include "std_msgs/String.h"
/**
* This tutorial demonstrates simple receipt of messages over the ROS system.
*/
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
ros::spin();
return 0;
}
1: 先讲main函数,ros节点的名字为 listener。
ros::init(argc, argv, "listener");
2: ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); 这一行是定义接收器的方法。Publisher使用'n.advertise',这儿使用n.subscriber
表示定义接收器, chatter
即前面publisher的topic的名字。【注意node的名字得独一无二但是topic的名字得和你想接收的信息的topic一样!】
ROS怎么知道你想接收什么信息呢?如果你有两个发布器,都发布std_msgs::String
类型的消息,接收器通过找谁的topic和自己一样就接收谁的信息。
我们发现这儿没有publisher中类似于<std_msgs::String>
的东西来定义要接收的数据类型。其实是有的,它藏在了第三个参数chatterCallback
里。我们注意到chatterCallback
和main函数之前定义那个返回值为空的函数名字一样。chatterCallback
称为回调函数,接收器每一次接收到消息,就会调用名字为它的函数。chatterCallback
这个名字也是可以随意定义的,只要和前面那个返回值为空的函数有一样的名字就可以了,但一般命名为...Callback
这样一看就知道这是ROS使用得回调函数。ROS的回调函数返回值只能为空。如果你想在接收到消息处理后返回什么有用的值怎么办呢?自然是写到类中了(当然也可以用全局变量)。
3:我们看回调函数。(const std_msgs::String::ConstPtr& msg) 这个模式是比较固定的。如果你要接受的是Int8
类型的消息,那么参数会变成(const std_msgs::Int8::ConstPtr& msg)
。
ConstPtr
代表一个指针msg
这个参数是你接收到的消息的指针
ROS_INFO("I heard: [%s]", msg->data.c_str()); msg->data
就是一个std::string
类型的量
【对象和指针的调用区别:在Publisher中我们定义了std_msgs::String
对象msg
,类包含数据成员data
,调用方式为msg.data
。如果类的指针叫msg,那么调用该成员的方式是msg->data】
4:ros::spin()
会使程序在循环中,一直检测有没有接收到新的消息。其终止方式和使ros::ok()
返回false
方式一样。
(3) 编译ROS程序
ROS中的程序要先写在CMakeLists中,再使用catkin_make编译。
打开pub_sub_test里面的CMakeLists.txt文件添加内容
add_executable(pub_string src/pub_string.cpp)
target_link_libraries(pub_string ${catkin_LIBRARIES})
add_executable(sub_string src/sub_string.cpp)
target_link_libraries(sub_string ${catkin_LIBRARIES})
第一行表示我们要编译add_executable
表示我们要添加一个可执行文件,pub_string
是这个可执行文件的名字(并不是非得和pub_string.cpp中的pub_string一样,不过建议用一样的),src/pub_string.cpp
指定要编译的源文件的位置.
第二行target_link_libraries
表示我们要将可执行文件链接到一个库,我们要使用ROS当然是要链接到ROS的库了,括号里pub_string
指定要链接可执行文件的名字,后面是指定要链接的库的名字.三四行类似,作用是编译sub_string.cpp
文件.
接下来编译:
cd ~/catkin_ws/
catkin_make
编译通过:
(4) 执行ROS程序
打开三个终端,
第一个输入: roscore;
第二个输入:
cd ~/catkin_ws/
source devel/setup.bash
rosrun pub_sub_test sub_string
第三个输入:
cd ~/catkin_ws/
source devel/setup.bash
rosrun pub_sub_test pub_string
显示的结果:
打印出 hello world 0等
图片左边是接收信息,右边是发布信息。
两个程序都是无限循环的,需要在terminal中按ctrl+c
退出.但在这之前你可以在打开一个terminal,输入下面的东西
source ~/catkin_ws/devel/setup.bash
rqt_graph
可以看到下图显示的内容,
talker
就是你发布信息的程序中取的节点的名字,listener
就是你接收信息的程序中取的节点的名字,chatter
就是你在程序中取的topic的名字.这副图传递的信息是
节点 talker
通过 topic chatter
向节点 listener
发布消息
rqt_graph
命令简洁地表现除了node和node之间的关系,当你以后有很多节点很多topic时,可以看看这个图像理清思维.