ROS核心教程学习笔记
1.安装和配置ROS环境
本节重点:
- 检查环境变量ROS_ROOT和ROS_PACKAGE_PATH的正确设置:
$ printenv | grep ROS
结果如下:
ROS_ETC_DIR=/opt/ros/melodic/etc/ros
ROS_ROOT=/opt/ros/melodic/share/ros
ROS_MASTER_URI=http://localhost:11311
ROS_VERSION=1
ROS_PYTHON_VERSION=2
ROS_PACKAGE_PATH=/opt/ros/melodic/share
ROSLISP_PACKAGE_DIRECTORIES=
ROS_DISTRO=melodic
- source命令将.bash文件加入环境:
$ source /opt/ros/<distro>/setup.bash
<distro>是发行版本名字,我的是Ubuntu18.04所以对应的ROS版本为melodic。
将.bash文件加入环境才能用ros+xx指令,可以将这条指令加入到.bashrc文件中,这样的话就不用次次都自己source一遍,开始roscore的时候就能加入source环境。
- catkin工作空间:
创建工作空间:
$ mkdir -p ~/catkin_ws/src
$ cd ~/catkin_ws/
$ catkin_make
catkin_make命令第一次运行时,会在catkin工作空间创建CMakeLists.txt文件。之后运行catkin_make会有不同的作用,如构建软件包等等。
- 查看ROS_PACKAGE_PATH环境变量是否包含当前工作空间目录:
首先对刚生成的setup.*sh文件进行source处理:
$ source devel/setup.bash
查看是否包含在ROS_PACKAGE_PATH中:
$ echo $ROS_PACKAGE_PATH
/home/<username>/catkin_ws/src:/opt/ros/<distro>/share
echo就是显示的意思,显示一下这个路径都有哪些,看看自己需要的工作空间有没有在ROS环境路径中。
至此环境搭建完成。
2.ROS文件系统导览
本节重点:
本节出现ROS专用命令,比较重要。
- rospack使用
找ROS软件包中的roscpp包的路径:
$ rospack find roscpp
- roscd使用
传送到roscpp包的所在位置:
$ roscd roscpp
输出当前工作目录:
$ pwd
- roscd log使用:
进入储存ROS日志文件的目录:
$ roscd log
- rosls使用:
直接传去包的位置,然后看看里面的有什么:
$ rosls roscpp_tutorials
- TAB补全使用:
单击TAB补全,双击显示所有可接补全。
3.创建ROS软件包
本节重点:
- catkin软件包组成:
-
这个包必须有一个符合catkin规范的package.xml文件,这个package.xml文件提供有关该软件包的元信息 。
-
这个包必须有一个catkin版本的CMakeLists.txt文件,如果它是个Catkin元包的话,则需要有一个CMakeList.txt文件的相关样板 。
-
每个包必须有自己的目录,这意味着在同一个目录下不能有嵌套的或者多个软件包存在。
总的来说就是package.xml文件,CMakeLists.txt文件和包独立。
- catkin软件包创建:
# You should have created this in the Creating a Workspace Tutorial
$ cd ~/catkin_ws/src
$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
# This is an example, do not try to run this
# catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
命令+包名字+依赖----->创建catkin软件包。
- 构建软件包:
$ cd ~/catkin_ws
$ catkin_make
$ . ~/catkin_ws/devel/setup.bash
到catkin_ws目录下catkin_make,也就相当于编译一遍,实际上还按照模板生成了文件。
- 查看一级依赖关系:
依赖就是要生成这个东西,我需要用到的东西。比如剪纸需要纸和剪刀。
查看生成这个beginner_tutorials 的时候用到的依赖包:
$ rospack depends1 beginner_tutorials
实际上依赖包信息会放在生成的package.xml文件之中,就在beginner_tutorials文件夹里面:
$ roscd beginner_tutorials
$ cat package.xml
- 查看间接依赖关系:
依赖包也有自己的依赖,查看rospy的依赖:
$ rospack depends1 rospy
还能查看一个软件包的所有依赖:
$ rospack depends beginner_tutorials
- 查看package.xml并自定义:
里面有描述标签(专门解释),维护者标签(告诉联系方式,有问题找这个人),许可证标签(许可协议相关信息),依赖项标签(依赖关系描述)。
4. 构建软件包
本章重点:
- 源代码不在catkin_ws/src的构建命令:
当源代码存放在my_src,catkin_make编译可能找不到它,可用:
# 在catkin工作空间下
$ catkin_make --source my_src
$ catkin_make install --source my_src # (可选)
-软件包构建:
$ cd ~/catkin_ws/
$ catkin_make
5.理解ROS节点
本节重点:
- 概念理解:
-
节点(Nodes):节点是一个可执行文件,它可以通过ROS来与其他节点进行通信。
-
消息(Messages):订阅或发布话题时所使用的ROS数据类型。
-
话题(Topics):节点可以将消息发布到话题,或通过订阅话题来接收消息。
-
主节点(Master):ROS的命名服务,例如帮助节点发现彼此。
-
rosout:在ROS中相当于stdout/stderr(标准输出/标准错误)。
-
roscore:主节点 + rosout + 参数服务器(会在以后介绍)。
- roscore核心启动:
给权限使其能访问/.ros:
$ sudo chown -R <your_username> ~/.ros
sudo就是一个高一级权限的命令。
-rosnode节点查看:
查看当前节点清单:
$ rosnode list
查看节点的信息:
$ rosnode info /rosout
- rosrun使用:
运行小海龟包中的turtlesim_node节点:
$ rosrun turtlesim turtlesim_node
龟每一次都会不一样,我最喜欢三头龟。
在新终端打开,用查看节点可以看到不同,多了/turtlesim节点。
- 重映射参数改变节点名称:
将/turtlesim节点更改名字为my_turtle:
$ rosrun turtlesim turtlesim_node __name:=my_turtle
-节点正常检测:
用ping检测响应:
$ rosnode ping my_turtle
6.理解ROS话题
本节重点:
- 键盘操控turtle:
$ rosrun turtlesim turtle_teleop_key
- rqt_graph使用:
话题和节点的关系图:
$ rosrun rqt_graph rqt_graph
- rostopic使用:
-h是查看help的命令:
$ rostopic -h
将/turtle1/cmd_vel话题进行显示:
$ rostopic echo /turtle1/cmd_vel
- rostopic list使用:
帮助和话题的详细信息:
$ rostopic list -h
$ rostopic list -v
- rostopic type使用:
话题类型:
$ rostopic type /turtle1/cmd_vel
- rostopic pub发布话题:
发布运动指令,-1是运行一次的意思:
$ rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'
- rostopic hz数据发布速率:
$ rostopic hz /turtle1/pose
- 话题深层信息获取:
获取类型的同时,把消息的内容显示出来:
$ rostopic type /turtle1/cmd_vel | rosmsg show
- rqt_plot使用:
对变量实时绘图:
$ rosrun rqt_plot rqt_plot
7.理解ROS服务和参数
本节重点:
-
服务概念:
服务(Services)是节点之间通讯的另一种方式。服务允许节点发送一个请求(request)并获得一个响应(response)。 -
rosservice使用:
rosservice list 输出活跃服务的信息
rosservice call 用给定的参数调用服务
rosservice type 输出服务的类型
rosservice find 按服务的类型查找服务
rosservice uri 输出服务的ROSRPC uri
rosservice list可以看到该节点所有的服务功能。
龟的节点turtlesim的服务有:
/clear
/kill
/reset
/rosout/get_loggers
/rosout/set_logger_level
/spawn
/teleop_turtle/get_loggers
/teleop_turtle/set_logger_level
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/get_loggers
/turtlesim/set_logger_level
/clear是清除轨迹
/kill是去掉海龟
/spawn是创建海龟
- rosparam使用:
rosparam能让我们在ROS参数服务器(Parameter Server)上存储和操作数据。参数服务器能够存储整型(integer)、浮点(float)、布尔(boolean)、字典(dictionaries)和列表(list)等数据类型。rosparam使用YAML标记语言的语法。一般而言,YAML的表述很自然:1是整型,1.0是浮点型,one是字符串,true是布尔型,[1, 2, 3]是整型组成的列表,{a: b, c: d}是字典。
用法:
rosparam set 设置参数
rosparam get 获取参数
rosparam load 从文件中加载参数
rosparam dump 向文件中转储参数
rosparam delete 删除参数
rosparam list 列出参数名
海龟后面版改颜色:
$ rosparam list
先看有什么参数能改,然后set修改:
$ rosparam set /turtlesim/background_r 150
也能获取绿色通道的值:
$ rosparam get /turtlesim/background_g
获取所有参数:
$ rosparam get /
- params.yaml参数文件写入和加载:
$ rosparam dump params.yaml
$ rosparam load params.yaml copy_turtle
8.使用rqt_console和roslaunch
本节重点:
- rqt_console和rqt_logger_level:
rqt_console连接到了ROS的日志框架,以显示节点的输出信息。rqt_logger_level允许我们在节点运行时改变输出信息的详细级别,包括Debug、Info、Warn、Error和Fatal。
$ rosrun rqt_console rqt_console
$ rosrun rqt_logger_level rqt_logger_level
- roslaunch使用:
在beginner_tutorials下创建launch:
$ mkdir launch
$ cd launch
注意:存放launch文件的目录不一定非要命名为launch,事实上都不用非得放在目录中,roslaunch命令会自动查找经过的包并检测可用的启动文件。然而,这种推荐的标准做法被认为是“最佳实践”。
创建turtlemimic.launch文件,然后打开编辑:
$ touch turtlemimic.launch
$ gedit turtlemimic.launch
粘贴教程的XML文件,其中包含了launch标签,命名空间标签,节点所在包名称,节点名称(名称相同为sim,这样可以让我们同时启动两个turtlesim模拟器,而不会产生命名冲突。 ),类型,模拟节点所在包名称,模拟节点名称,模拟节点类型,remap分布发布者和接收者,最后launch标签闭环。
<launch>
<group ns="turtlesim1">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<group ns="turtlesim2">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<node pkg="turtlesim" name="mimic" type="mimic">
<remap from="input" to="turtlesim1/turtle1"/>
<remap from="output" to="turtlesim2/turtle1"/>
</node>
</launch>
运行launch程序:
$ roslaunch beginner_tutorials turtlemimic.launch
对第一只乌龟操作,第二只乌龟会进行相同动作:
$ rostopic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'
9.使用rosed在ROS中编辑文件
本节重点:
- rosed使用:
首先要把编辑器加入环境,下面二者选其一:
export EDITOR='nano -w'
export EDITOR='emacs -nw'
ed就是edit编辑的意思,示例:
$ rosed roscpp Logger.msg
10.创建ROS消息和服务
本节重点:
-
msg和srv概念:
-
msg(消息):msg文件就是文本文件,用于描述ROS消息的字段。它们用于为不同编程语言编写的消息生成源代码。
-
srv(服务):一个srv文件描述一个服务。它由两部分组成:请求(request)和响应(response)。
-
msg文件存放在软件包的msg目录下,srv文件则存放在srv目录下。
-
ROS中还有一个特殊的数据类型:Header,它含有时间戳和ROS中广泛使用的坐标帧信息。在msg文件的第一行经常可以看到Header header。
-
srv文件和msg文件一样,只是它们包含两个部分:请求和响应。这两部分用一条—线隔开。下面是一个srv文件的示例:
-
int64 A
int64 B
---
int64 Sum
- msg使用:
创建一个msg文件夹,然后把一个类型传进去Num.msg文件里面:
$ roscd beginner_tutorials
$ mkdir msg
$ echo "int64 num" > msg/Num.msg
然后对package.xml文件进行修改,将下列两行复制到文件最低部:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
目的是增加依赖,确保msg文件能被转换为C++、Python和其他语言的源代码。
然后对应修改CMakeLists.txt文件:
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...) //记得去掉...
add_message_files(
FILES
Num.msg
)
generate_messages(
DEPENDENCIES
std_msgs
)
find_package中加入 message_generation依赖项,是为了能够生成消息。
有时即使没有使用全部依赖项调用find_package,项目也可以构建。这是因为catkin把你所有的项目整合在了一起,因此如果之前的项目调用了find_package,你的依赖关系也被配置成了一样的值。但是,忘记调用意味着你的项目在单独构建时很容易崩溃。
catkin_package中加入message_runtime是为了确保导出消息的运行时依赖关系。
add_message_files加入Num.msg是为了让CMake能知道何时需要重新配置项目。
generate_messages中加入任意你的消息用到的包含.msg文件的软件包(本例中为std_msgs),它配置了编译.msg文件时需要依赖的库,这里的std_msgs库定义了.msg文件中的uint和string等数据类型。
看看结果:
$ rosmsg show beginner_tutorials/Num
- srv使用:
创建一个srv文件夹存放服务:
$ roscd beginner_tutorials
$ mkdir srv
从rospy_tutorials包复制一个服务:
$ roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv
为了确保msg文件能被转换为C++、Python和其他语言的源代码,需要对package.xml进行更改,添加两行依赖关系:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
在构建时,其实只需要message_generation(就是生成过程的信息传送),而在运行时,我们只需要message_runtime(运行时的信息传送)。
在CMakeLists.txt文件中,为已经存在里面的find_package调用添加message_generation依赖项,这样就能生成消息了。直接将message_generation添加到COMPONENTS列表中即可,如下所示:
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
add_service_files(
FILES
AddTwoInts.srv
)
- rossrv使用:
查看刚刚的服务,下列指令可以看看ros是否能识别它:
$ rossrv show beginner_tutorials/AddTwoInts
也可以在不指定包名字的情况下找到这个服务在哪里,有什么:
$ rossrv show AddTwoInts
-msg和srv生成源文件:
进行编译:
# In your catkin workspace
$ roscd beginner_tutorials
$ cd ../..
$ catkin_make
$ cd -
msg目录中的任何.msg文件都将生成所有支持语言的代码。C++消息的头文件将生成在/catkin_ws/devel/include/beginner_tutorials/。Python脚本将创建在/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/msg。而Lisp文件则出现在~/catkin_ws/devel/share/common-lisp/ros/beginner_tutorials/msg/
11.编写简单的发布者和订阅者
本节重点:
- 发布者编写:
为源代码创建src文件夹:
$ roscd beginner_tutorials
$ mkdir src
$ cd src
touch创建talker.cpp文件,以下是源文件解释:
#include "ros/ros.h"
ros/ros.h是一个很便利的include,它包括了使用ROS系统中最常见的公共部分所需的全部头文件。
#include "std_msgs/String.h"
它引用了位于std_msgs包里的std_msgs/String消息。这是从std_msgs包里的String.msg文件中自动生成的头文件。有关消息定义的更多信息,请参见msg页面。
ros::init(argc, argv, "talker");
初始化ROS。这使得ROS可以通过命令行进行名称重映射——不过目前不重要。这也是我们给节点指定名称的地方。节点名在运行的系统中必须是唯一的。注意:名称必须是基本名称,例如不能包含任何斜杠/。
argc是参数数量argument count,argv是参数序列argument vector。
ros::NodeHandle n;
为这个进程的节点创建句柄。创建的第一个NodeHandle实际上将执行节点的初始化,而最后一个被销毁的NodeHandle将清除节点所使用的任何资源。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
告诉主节点我们将要在chatter话题上发布一个类型为std_msgs/String的消息。这会让主节点告诉任何正在监听chatter的节点,我们将在这一话题上发布数据。第二个参数是发布队列的大小。在本例中,如果我们发布得太快,它将最多缓存1000条消息,不然就会丢弃旧消息。
ros::Rate loop_rate(10);
ros::Rate对象能让你指定循环的频率。它会记录从上次调用Rate::sleep()到现在已经有多长时间,并休眠正确的时间。在本例中,我们告诉它希望以10Hz运行。
int count = 0;
while (ros::ok())
{
默认情况下,roscpp将安装一个SIGINT处理程序,它能够处理Ctrl+C操作,让ros::ok()返回false。
ros::ok()在以下情况会返回false:
-
收到SIGINT信号(Ctrl+C)
-
被另一个同名的节点踢出了网络
-
ros::shutdown()被程序的另一部分调用
-
所有的ros::NodeHandles都已被销毁
一旦ros::ok()返回false, 所有的ROS调用都会失败。
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
把std_msgs::String的东西赋给msg,std::stringstream的东西赋给ss,那么把hello world输出给ss,也就是字符串流,它可以转换别的类型为string,所以hello world变成字符串输出到ss,再把string发到msg.data中,防止ss内存增长过快。
引用相关解释:关于stringstream缓冲区的有关问题
chatter_pub.publish(msg);
把这个信息广播给了任何已连接的节点。
ROS_INFO("%s", msg.data.c_str());
发布的东西输出到我们的终端,相当于printf/cout。
ros::spinOnce();
这是用于接收信息,然后调用回调函数用的。因为这是个纯发布者,所以用不上。
loop_rate.sleep();
保证发布速率,其他时间睡眠,以此来控制发布次数。
总源码:
#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.
*/
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.
*/
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;
}
总结:
-
初始化ROS系统
-
向主节点宣告我们将要在chatter话题上发布std_msgs/String类型的消息
-
以每秒10次的速率向chatter循环发布消息
- 订阅者编写:
同样在src目录下创建listener.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)
{
/**
* 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.
*/
ros::init(argc, argv, "listener");
/**
* 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.
*/
ros::NodeHandle n;
/**
* The subscribe() call is how you tell ROS that you want to receive messages
* on a given topic. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. Messages are passed to a callback function, here
* called chatterCallback. subscribe() returns a Subscriber object that you
* must hold on to until you want to unsubscribe. When all copies of the Subscriber
* object go out of scope, this callback will automatically be unsubscribed from
* this topic.
*
* The second parameter to the subscribe() function is the size of the message
* queue. If messages are arriving faster than they are being processed, this
* is the number of messages that will be buffered up before beginning to throw
* away the oldest ones.
*/
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
/**
* ros::spin() will enter a loop, pumping callbacks. With this version, all
* callbacks will be called from within this thread (the main one). ros::spin()
* will exit when Ctrl-C is pressed, or the node is shutdown by the master.
*/
ros::spin();
return 0;
}
ros::spin()启动了一个自循环,它会尽可能快地调用消息回调函数。不过不要担心,如果没有什么事情,它就不会占用太多CPU。另外,一旦ros::ok()返回false,ros::spin()就会退出,这意味着ros::shutdown()被调用了,主节点让我们关闭(或是因为按下Ctrl+C,它被手动调用)。
总结:
-
初始化ROS系统
-
订阅chatter话题
-
开始spin自循环,等待消息的到达
-
当消息到达后,调用chatterCallback()函数
- 节点构建:
不要担心修改注释(#)示例,只需将这几行添加到CMakeLists.txt文件的底部:
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)
最终的CMakeLists.txt是这样的:
cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)
## Find catkin and any catkin packages
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)
## Declare ROS messages and services
add_message_files(FILES Num.msg)
add_service_files(FILES AddTwoInts.srv)
## Generate added messages and services
generate_messages(DEPENDENCIES std_msgs)
## Declare a catkin package
catkin_package()
## Build talker and listener
include_directories(include ${catkin_INCLUDE_DIRS})
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)
这将创建两个可执行文件talker和listener,默认情况下,它们将被放到软件包目录下的devel空间中,即~/catkin_ws/devel/lib/。
12.检验简单的发布者和订阅者
本节重点:
- 运行发布者和订阅者:
$ rosrun beginner_tutorials talker # (C++)
$ rosrun beginner_tutorials listener # (C++)
13.编写简单的服务和客户端
本节重点:
- 服务节点编写:
在beginner_tutorials包中创建src/add_two_ints_server.cpp文件并粘贴以下内容进去:
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
//这个函数提供了AddTwoInts服务,它接受srv文件中定义的请求(request)和响应(response)类型
//并返回一个布尔值。
{
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
//此处,两个整数被相加,和已经存储在了响应中。然后记录一些有关请求和响应的信息到日志中。
//完成后,服务返回true。
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
//在这里,服务被创建,并在ROS中宣告。
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
- 客户端节点编写:
在beginner_tutorials包中创建src/add_two_ints_client.cpp文件并粘贴以下内容进去:
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
//这将为add_two_ints服务创建一个客户端。ros::ServiceClient对象的作用是在稍后调用服务。
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
//这里我们实例化一个自动生成的服务类,并为它的request成员赋值。
//一个服务类包括2个成员变量:request和response,以及2个类定义:Request和Response。
if (client.call(srv))
//此处实际上调用了服务。由于服务调用被阻塞,它将在调用完成后返回。
//如果服务调用成功,call()将返回true,并且srv.response中的值将是有效的。
//如果调用不成功,则call()将返回false且srv.response的值将不可用。
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
atoll的用处是过滤参数,只留下数字。
- 节点构建:
再来编辑一下beginner_tutorials里面的CMakeLists.txt文件,文件位于~/catkin_ws/src/beginner_tutorials/CMakeLists.txt,并将下面的代码添加在文件末尾:
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server beginner_tutorials_gencpp)
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client beginner_tutorials_gencpp)
生成两个可执行文件add_two_ints_server和add_two_ints_client,在devel文件夹中可以找到。
回到catkin_ws运行catkin_make即可。
14.检验简单的服务和客户端
本节重点:
- 运行服务和客户端:
$ rosrun beginner_tutorials add_two_ints_server # (C++)
$ rosrun beginner_tutorials add_two_ints_client 1 3 # (C++)
15.录制和回放数据
本节重点:
-录制与回放:
已发布主题的列表是唯一可能被记录在数据日志文件中的消息类型,因为只有发布的消息才能被录制。由teleop_turtle发布的/turtle1/cmd_vel话题是指令消息,作为turtlesim进程的输入。消息/turtle1/color_sensor和/turtle1/pose是turtlesim发布的输出消息。
创建了一个临时目录来记录数据,然后运行rosbag record带选项-a,表明所有发布的话题都应该积累在一个bag文件中:
mkdir ~/bagfiles
cd ~/bagfiles
rosbag record -a
用键盘控制乌龟,然后在运行rosbag record的窗口ctrl+c停止,记录数据就已经在bagfiles中了。
查看bagfile的详细信息:
rosbag info <your bagfile>
运行bagfile的内容:
rosbag play <your bagfile>
rosbag play -r 2 <your bagfile>//两倍运行速率
- 录制数据子集:
当运行一个复杂的系统时,比如PR2软件套装,会有几百个话题被发布,有些话题亦会发布大量数据(比如包含摄像头图像流的话题)。在这种系统中,将包含所有话题的日志写入一个bag文件到磁盘通常是不切实际的。
rosbag record命令支持只录制特定的话题到bag文件中,这样就可以只录制用户感兴趣的话题。在bag文件所在目录下执行以下命令:
rosbag record -O subset /turtle1/cmd_vel /turtle1/pose
-O参数告诉rosbag record将数据记录到名为subset.bag的文件中,而后面的topic参数告诉rosbag record只能订阅这两个指定的话题。
- 局限:
乌龟的路径可能并没有完全地映射到原先通过键盘控制时产生的路径上——整体形状应该是差不多的,但没有完全一样。这是因为turtlesim的移动路径对系统定时精度的变化非常敏感。rosbag受制于其本身的性能无法完全复制录制时的系统运行行为,rosplay也一样。对于像turtlesim这样的节点,当处理消息的过程中系统定时发生极小变化时也会使其行为发生微妙变化,用户不应该期望能够完美地模仿系统行为。
16.从bag文件中读取消息
本节重点:
- 立即回放消息并在多个终端查看输出:
在任何终端中用这个命令,来手动检查所有已发布的话题,以及向每个话题发布了多少消息:
time rosbag info demo.bag
# 或者你已经知道话题名称的话:
time rosbag info mybag.bag | grep -E "(topic1|topic2|topic3)"
可以看到详细的包的内容,然后可以订阅其中的一个话题,复读该话题发布的所有内容,同时用tee命令转存到一个yaml格式的文件中:
rostopic echo /turtle1/cmd_vel | tee topic1.yaml
还能开启另外的终端,去开启订阅另一个话题,tee到topic2.yaml中。然后用新的终端来回放bag文件,–immediate是尽可能快地回放,只会回放我们感兴趣的话题,也就是我们之前tee的topic1.yaml~topicN.yaml
time rosbag play --immediate demo.bag --topics /topic1 /topic2 /topic3 /topicN
- ros_readbagfile脚本提取感兴趣话题:
先用rosbag info读取话题名称,然后用ros_readbagfile去回放感兴趣的topic:
ros_readbagfile <mybagfile.bag> [topic1] [topic2] [topic3] [...]
- ros_readbagfile和rostopic echo -b:
rostopic很慢,ros_readbagfile很快,所以不用rostopic。
17.roswtf入门
本节重点:
- roswtf检查使用:
用下列命令判断roscore是否运行:
$ ps -ef | grep -i rosmaster
//出现下列情况可能还在运行
00:00:00 /usr/bin/python /opt/ros/kinetic/bin/rosmaster
roswtf可以检查系统并发现问题:
$ roscd rosmaster
$ roswtf
得到的结果解释为:
-
ackage: rosmaster:roswtf使用当前目录中的任何内容来确定其执行的检查。这个输出告诉我们是在包rosmaster的目录中启动了roswtf。
-
Static checks summary:它会报告任何关于文件系统或非运行时(比如无需roscore的场景)的问题。本例显示我们没有错误。
-
ROS Master does not appear to be running.:roscore未在运行。roswtf不会做任何ROS在线检查。
-
在线检查:
在启动roscore后,可以在线检查,重复上面步骤就行。
roswtf会对一些系统中看起来异常但可能是正常的运行情况发出警告。也会对确实有问题的情况报告错误。