2.5 学习ROS的使用
在2.4节中,我们学习了ROS计算网络结构所涉及的基本概念和术语。在接下来这一部分中,我们将主要精力放到实践上来。
2.5.1 准备工作
在运行任何ROS节点之前,我们应该先启动ROS节点管理器和ROS参数服务器。只需要使用一条roscore命令就可以完成ROS节点管理器和ROS参数服务器的启动。该命令默认情况下将启动3个程序:ROS节点管理器、ROS参数服务器和rosout日志节点。
2.5.2 如何完成
现在是将我们所学到的知识用于实践的时候了。接下来,我们将通过一些示例进行一些练习,这里面包括创建功能包、使用节点、使用参数服务器,以及使用turtlesim(小乌龟)模拟机器人的移动等操作。
使用下面的命令来运行ROS节点管理器和参数服务器:
$ roscore
在图2-9中的第一部分可以看到一个日志文件,它位于目录/home/kbipin/.ros/log中,主要用于从ROS节点来收集日志。我们主要使用它进行调试。
图2-9 运行RoSCORE命令时的终端显示
下一部分显示roslaunch命令正在执行一个名为roscore.xml的ROS启动文件。当这个启动文件执行时,它就会自动启动ROS节点管理器和ROS参数服务器。roslaunch命令是一个使用Python编写的脚本,当它试图执行一个启动文件时,它可以启动ROS节点管理器和ROS参数服务器。
它还显示了ROS参数服务器的端口地址。
在图2-9的第三部分中,我们可以看到终端上显示的rosdistro和rosversion两个参数。当我们在使用roslaunch命令执行RoSCOR.XML时就会显示这些参数。
在图2-9的第四部分,我们可以看到ROS参数服务器节点是使用ROS_MASTER_URI启动的,它是一个环境变量。正如前面讨论过的一样,它的值就是ROS参数服务器的端口地址。在最后一个部分,我们可以看到节点rosout已经启动了,它将会订阅/rosout话题并且转播到/rosout_agg。
当执行roscore命令时,它首先检查命令行参数以获取ROS参数服务器的新端口号。如果成功获取了端口号,ROS会在这个端口号上进行监听,否则会在默认端口监听。这个端口号和roscore.xml启动文件将会被传输给roslaunch系统。这个roslaunch系统是在Python模块中实现的,它将会解析端口号并启动RoSCOR.XML文件。
roscore.xml文件的内容如下:
<launch> <group ns="/"> <param name="rosversion" command="rosversion roslaunch" /> <param name="rosdistro" command="rosversion -d" /> <node pkg="rosout" type="rosout" name="rosout" respawn="true"/> </group> </launch>
在roscore.xml文件中,我们可以看到使用XML中group标签封装的ROS参数和节点。group标签封装的所有节点具有相同的设置。而两个名为rosversion和rosdistro的参数则分别使用command标签来存储“rosversion roslaunch”和“rosversion -d”命令的输出,这里的command标签是ROS中param标签的一部分。command标签将执行封装其中的命令,并将命令的输出存储在这两个参数中。
节点rosout将从其他ROS节点收集日志消息并存储在日志文件中,还将所收集的日志消息重新广播到另一个话题。使用ROS客户端库(如RoSCPP和RoSpice)的ROS节点发布的/rosout话题由rosout节点所接收的,它将消息转播到另一个话题/rosout_agg中。这个话题会将日志消息聚合在一起。
我们需要对运行roscore后创建的ROS主题和ROS参数进行检查。下面的命令将列出终端上的活动话题:
$ rostopic list
活动话题列表显示如下:
/rosout /rosout_agg
下面的命令列出运行roscore时可用的参数。以下是列出活动ROS参数的命令:
$ rosparam list
这里提到的参数包括ROS发行版名称、版本、roslaunch服务的地址以及run_id,其中的run_id是与RoScript的特定运行相关联的唯一ID。
/rosdistro /roslaunch/uris/host_ubuntu__33187 /rosversion /run_id
使用以下命令可以检查roscore运行期间生成的ROS服务的列表:
$ rosservice list
运行的服务列表如下所示:
/rosout/get_loggers /rosout/set_logger_level
这些ROS服务是为每个ROS节点生成的,用于设置日志记录。在了解了ROS节点管理器、参数服务器和ROSCORE的基础知识之后,我们来更详细地回顾ROS节点、话题、消息和服务的概念。
正如前面部分所讨论的,节点是可执行程序。当这些可执行文件完成构建操作之后,它们就会被保存在devel空间中。我们将使用一个名为turtlesim的常用功能包来学习和练习节点的使用。
如果你在安装系统时采用了默认的桌面安装方式,那么就可以直接使用这个turtlesim功能包。如果你采用了其他安装方式,那么可以使用如下的命令来安装它:
$ sudo apt-get install ros-kinetic-ros-tutorials
在上一节中,我们已经在一个打开的终端中执行了roscore。现在,我们将在另一个终端中使用rosrun命令启动一个新节点,使用的命令如下所示:
$ rosrun turtlesim turtlesim_node
然后我们将看到一个新窗口出现,在它的中间有一只小乌龟,如图2-10所示。
图2-10 Turtlesim
我们使用以下命令获得关于正在运行的节点的信息:
$ rosnode list
我们将看到一个名为/turtlesim的新节点,可以使用下面的命令获取关于节点的信息:
$ rosnode info /turtlesim
上面一个命令执行完毕之后将打印如图2-11所示的信息。
图2-11 节点信息
从图2-11显示的节点信息中我们可以看到一个节点拥有Publications(发布者)、Subscriptions(订阅者)和Services(服务)等信息,每个都有唯一的名称。
在2.5.3节中,我们将学习如何使用话题和服务与节点进行交互。
我们可以使用rostopic工具与话题进行交互并获取信息。使用rostopic pub,我们可以发布任何节点都可以订阅的话题。我们只需以正确的名称发布话题。
使用以下命令启动turtlesim包中的turtle_teleop_key节点:
$ rosrun turtlesim turtle_teleop_key
使用这个节点,我们可以使用箭头键移动乌龟,如图2-12所示。
图2-12 Turtlesim teleoperation
让我们理解为什么在turtle_teleop_key运行时小乌龟会移动。如果你想查看关于rosnode提供的关于teleop_turtle和turtlesim节点的信息(见图2-13),我们可以注意到在/teleop_turtle节点的publications部分存在一个名为/turtle1/cmd_vel [geometry_msgs/Twist]的话题,在/turtlesim节点的第二段代码Subscriptions部分有一个/turtle1/cmd_vel [geometry_msgs/Twist]的话题。
图2-13 Teleop节点信息
$ rosnode info /teleop_turtle
这表明第一个节点正在发布第二个节点可以订阅的话题。我们可以使用下面的命令行来观察话题列表:
$ rostopic list
输出的内容如下所示:
/rosout /rosout_agg /turtle1/colour_sensor /turtle1/cmd_vel /turtle1/pose
我们可以使用echo参数的命令获得节点发送的信息,运行下面的命令查看这些发送的数据:
$ rostopic echo /turtle1/cmd_vel
我们将看到类似于以下输出的内容:
--- linear: x: 0.0 y: 0.0 z: 0.0 angular: x: 0.0 y: 0.0 z: 2.0 ---
类似的,我们可以使用以下命令来获得话题发送的消息类型:
$ rostopic type /turtle1/cmd_vel
输出的内容如下所示:
Geometry_msgs/Twist
为了获取消息字段,我们可以使用以下命令:
$ rosmsg show geometry_msgs/Twist
我们将得到类似如下所示的输出:
geometry_msgs/Vector3 linear float64 x float64 y float64 z geometry_msgs/Vector3 angular float64 x float64 y float64 z
这些信息是很有用的,我们可以使用命令rostopic pub [topic][msg_type] [args]来发布话题:
$ rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- "linear: x: 1.0 y: 0.0 z: 0.0 angular: x: 0.0 y: 0.0 z: 1.0"
如图2-14所示,我们可以观察乌龟做出的曲线。
图2-14 乌龟做出的曲线
服务是另一种实现节点间相互通信的方法,并且允许节点发送请求和接收响应。如前面的ROS服务部分所讨论的,我们将使用RoService工具与服务交互并获取有关服务的信息。
下面的命令将列出turtlesim节点可用的服务(如果roscore和turtlesim节点没有运行,就需要启动这两个节点。)
$ rosservice list
系统将会显示如下输出:
/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服务为例:
$ rosservice type /clear
执行完毕之后系统将显示如下结果:
std_srvs/Empty
我们可以使用rosservice call [service] [args]来调用服务,例如下面的命令将调用/clear服务:
$ rosservice call /clear
现在我们可以看到,在turtlesim窗口中由乌龟的运动产生的线条将被删除。
现在我们可以查看另一个服务,例如/spawn服务。这将在给定位置和指定方向创建另一只乌龟。我们使用下面的命令来启动:
$ rosservice type /spawn | rossrv show
执行完毕之后,系统将显示如下结果:
float32 x float32 y float32 theta string name --- string name
前面的命令与下面的命令相同(如果你希望了解更多细节,可以在谷歌中搜索“piping Linux”)。
$ rosservice type /spawn $ rossrv show turtlesim/Spawn
这将得到与前面命令显示的输出相类似的结果。我们可以通过/spawn服务的字段来调用它。它需要x和y的位置、方位(θ)和新乌龟的名字:
$ rosservice call /spawn 3 3 0.2 "new_turtle"
我们会在TurtleSim窗口中看到如图2-15所示的输出。
图2-15 TurtleSim窗口
ROS参数服务器用于存储数据,它位于ROS计算网络的中心,以便所有运行节点都可以访问。正如我们在前面ROS参数服务器部分中所学到的,可以使用ROSPARAM工具来管理参数服务器。
例如,我们可以在服务器中看到所有当前运行节点所使用的参数:
$ rosparam list
这个命令执行之后的输出结果为:
/background_b /background_g /background_r /rosdistro /roslaunch/uris/host_ubuntu__33187 /rosversion /run_id
节点turtlesim的背景参数定义了窗口的颜色,初始值为蓝色。我们可以通过get参数来读取这个值。
$ rosparam get /background_b
与此相似,我们也可以使用参数set来设置一个新的值:
$ rosparam set /background_b 120
dump参数是rosparam最重要的部分之一,用于保存或加载参数服务器的内容。
我们可以使用rosparam dump [file_name]命令来保存参数服务器的内容:
$ rosparam dump save.yaml
同样,我们可以使用rosparam load [file_name] [namespace]命令来加载参数服务器的内容:
$ rosparam load load.yaml namespace
2.5.3 工作原理
在上一节中,我们通过turtlesim包的示例学习了ROS参数服务器、话题、服务和参数服务器。这为我们进一步深入学习ROS的高级概念做好了准备。在本节中,我们将学习ROS节点(包括MSG和SRV文件)的创建和构建。
1.ROS节点的创建
在本节中,我们将创建两个节点,其中一个节点将发布数据,另一个节点将接收该数据。这是ROS系统中两个节点之间最基本的通信方式。我们在前面的章节ROS功能包和元功能包部分中创建了一个ROS功能包chapter2_tutorials。现在我们需要使用下面的命令定位到hapter2_tutorialsrc文件夹:
$ roscd chapter2_tutorials/src/
我们要分别创建两个名为example_1a.cpp和example_1b.cpp的文件。
example_1a.cpp文件将创建一个名为example1a的节点,它将会在话题/message来发布数据“Hello World!”。此外,example_1b.cpp文件将创建名为example1b的节点,它订阅/message话题并接收节点example1a发送的数据,并在shell中显示数据。
我们可以将下面的代码复制到例子中,或者从配套代码中获取:
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream> int main(int argc, char **argv) { ros::init(argc, argv, "example1a"); ros::NodeHandle n; ros::Publisher pub = n.advertise<std_msgs::String>("message", 100); ros::Rate loop_rate(10); while (ros::ok()) { std_msgs::String msg; std::stringstream ss; ss << "Hello World!"; msg.data = ss.str(); pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); } return 0; }
我们详细研究前面的代码以便理解ROS开发框架。这段代码中引入的头文件包括ros/ros.h、std_msgs/String.h和sstream。其中ros/ros.h包括与ROS节点使用所需的全部文件,而std_msgs/String.h中包括用于向ROS计算网络发布消息的类型的文件头。
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream>
在这里,我们将完成节点的初始化并设置它的名称。同时需要注意这个名字必须是独一无二的:
ros::init(argc, argv, "example1a");
这是与节点相关联进程的处理程序,通过它可以实现节点与环境的交互:
ros::NodeHandle n;
此时我们将使用话题的名称和类型来实例化发布程序,第二个参数指定了缓冲区的大小。(如果希望话题可以快速发布数据,则缓冲区的大小至少为100条消息。)
ros::Publisher pub = n.advertise<std_msgs::String>("message", 100);
下一行代码设置数据发送频率,在我们的这个实例中将其设置为10Hz:
ros::Rate loop_rate(10);
当ROS停止所有节点或者使用者按下Ctrl+C组合键,下面的ros::ok()行就会停止节点。
while (ros::ok()) {
在代码的这个部分中,我们为消息创建了一个变量,它具有发送数据的正确类型:
std_msgs::String msg; std::stringstream ss; ss << "Hello World!"; msg.data = ss.str(); pub.publish(msg);
另外,我们将继续使用先前定义的发布节点发送消息:
pub.publish(msg);
spinOnce函数负责处理所有内部ROS事件和动作,例如对订阅的话题进行阅读;然而,spinOnce在ROS的主循环中执行一次迭代,以便允许用户在迭代之间执行操作,而spin函数不中断地运行主循环。
最后,我们的程序需要休眠一段时间,以实现频率为10Hz:
loop_rate.sleep();
我们已经成功地创建了一个发布节点。同样,我们现在来创建订阅节点。将下面的代码复制到example_1b.cpp文件中。另外,你也可以从配套代码中获取本段代码。
#include "ros/ros.h" #include "std_msgs/String.h" void messageCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("Thanks: [%s]", msg->data.c_str()); } int main(int argc, char **argv) { ros::init(argc, argv, "example1b"); ros::NodeHandle n; ros::Subscriber sub = n.subscribe("message", 100, messageCallback); ros::spin(); return 0; }
下面来研究一下这部分代码。前面我们已经提到过,ros ros.h中包括了ROS中使用节点的全部文件,std_msgs/String.h定义了消息所使用的类型:
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream>
下面的源代码显示了回调函数(用来响应一个动作)的类型,在这种情况下,函数是在订阅话题上接收字符串消息。这个函数允许我们处理接收到的消息数据;在这种情况下,它在终端上显示消息数据。
void messageCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("Thanks: [%s]", msg->data.c_str()); }
此时,我们将创建一个订阅器,并开始监听名为message的话题,它的缓冲区大小为1000,处理消息的函数是messageCallback:
ros::Subscriber sub = n.subscribe("message", 1000, messageCallback);
最后,ros::spin()行是节点开始读取主题的主循环,当一个消息到达时就会调用messageCallback。当用户按下Ctrl+C组合键时,节点退出循环并结束:
ros::spin();
2.编译ROS节点
我们正在使用chapter2_tutorials包,这里需要首先编辑CMakeLists.txt文件,准备并配置好编译所需要的包:
$ rosed chapter2_tutorials CMakeLists.txt
在这个文件的末尾,我们需要添加以下几行代码:
include_directories( include ${catkin_INCLUDE_DIRS} ) add_executable(example1a src/example_1a.cpp) add_executable(example1b src/example_1b.cpp) add_dependencies(example1a chapter2_tutorials_generate_messages_cpp) add_dependencies(example1b chapter2_tutorials_generate_messages_cpp) target_link_libraries(example1a ${catkin_LIBRARIES}) target_link_libraries(example1b ${catkin_LIBRARIES})
catkin_make工具用于构建编译所有节点的包:
$ cd ~/catkin_ws/ $ catkin_make --pkg chapter2_tutorials
我们以之前创建的节点为例,首先启动这个节点:
$ roscore
接下来,我们可以在另一个终端中输入rosnode list命令检查ROS是否正在运行,如下所示:
$ rosnode list
现在,我们在不同的命令行(shell)中运行两个节点:
$ rosrun chapter2_tutorials example1a $ rosrun chapter2_tutorials example1b
我们将在正在运行example1b节点的命令行(shell)中看到类似图2-16所示的内容。
图2-16 运行截图
我们可以使用rosnode和rostopic命令对正在运行的节点进行调试和获取这个节点的信息:
$ rosnode list $ rosnode info /example1_a $ rosnode info /example1_b $ rostopic list $ rostopic info /message $ rostopic type /message $ rostopic bw /message
3.创建ROS消息
在本节中将学习如何使用.msg文件创建用户定义的自定义消息,这些消息将在节点中使用。这包含一个关于要传输的数据类型的标准。ROS构建系统将使用这个文件来创建实现ROS计算框架或网络中的消息所需要的代码。
在前面创建ROS节点中,我们用标准类型的消息创建了两个节点。现在,我们将学习如何使用ROS工具创建自定义消息。
首先,在chapter2_tutorials包中创建一个MSG文件夹。此外,在那里创建一个新的chapter2_msg.msg文件,并在其中添加以下命令行:
int32 A int32 B int32 C
此外,我们还必须在package.xml文件中查找以下命令行并取消注释:
<build_depend>message_generation</build_depend> <run_depend>message_runtime</run_depend>
这些命令行支持在ROS构建系统中配置消息和服务。此外,我们将在CMakeLists.txt中添加一行代码“message_generation”。
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs message_generation )
然后,我们还需要在CMakeLists.txt中查找add_message_files行并取消这一行的注释,同时添加新消息文件的名称,如下所示:
## Generate messages in the 'msg' folder add_message_files( FILES chapter2_msg.msg ) ## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES std_msgs )
最后,我们可以使用以下命令编译包:
$ cd ~/catkin_ws/ $ catkin_make
我们可以使用rosmsg命令检查一切是否正常运行:
$ rosmsg show chapter2_tutorials/chapter2_msg
执行rosmsg命令之后将显示chapter2_msg.msg文件的内容。
现在,我们将使用前面描述的自定义msg文件来创建节点。这和前面讨论的example_1a.cpp和example_1b.cpp非常类似,但是使用了新的消息chapter2_msg.msg。
下面给出了example_2a.cpp文件的代码片段:
#include "ros/ros.h" #include "chapter2_tutorials/chapter2_msg.h" #include <sstream> int main(int argc, char **argv) { ros::init(argc, argv, "example2a"); ros::NodeHandle n; ros::Publisher pub = n.advertise<chapter2_tutorials::chapter2_msg>("chapter2_tutorials/message", 100); ros::Rate loop_rate(10); while (ros::ok()) { chapter2_tutorials::chapter2_msg msg; msg.A = 1; msg.B = 2; msg.C = 3; pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); } return 0; }
同样,下面给出了example_2b.cpp文件的代码片段:
#include "ros/ros.h" #include "chapter2_tutorials/chapter2_msg.h" void messageCallback(const chapter2_tutorials::chapter2_msg::ConstPtr& msg) { ROS_INFO("I have received: [%d] [%d] [%d]", msg->A, msg->B, msg->C); } int main(int argc, char **argv) { ros::init(argc, argv, "example3_b"); ros::NodeHandle n; ros::Subscriber sub = n.subscribe("chapter2_tutorials/message", 100, messageCallback); ros::spin(); return 0; }
我们可以使用以下命令来运行发布者和订阅者节点:
$ rosrun chapter2_tutorials example2a $ rosrun chapter2_tutorials example2b
当在两个单独的shell中运行这两个节点时,我们将看到类似以下输出的内容:
... [ INFO] [1355280835.903686201]: I have received: [1] [2] [3] [ INFO] [1355280836.020326872]: I have received: [1] [2] [3] [ INFO] [1355280836.120367649]: I have received: [1] [2] [3] [ INFO] [1355280836.220260466]: I have received: [1] [2] [3] ...
4.创建ROS服务
在本节中学习如何创建一个将用于我们节点中的srv文件。这包含一个关于要传输的数据类型的标准。ROS构建系统将使用它来创建实现ROS计算框架或网络中的srv文件所需要的代码。
在前面创建节点的部分中,我们用标准类型的消息创建了两个节点。现在,我们将学习如何使用ROS工具创建服务。
首先,在chapter2_tutorials包中创建一个srv文件夹。此外,在那里创建一个新的chapter2_msg.msg文件,并在其中添加以下命令行:
int32 A int32 B --- int32 sum
这里,A和B是来自客户端的请求的数据类型,sum是来自服务器的响应数据类型。
我们还必须查找以下行并取消注释:
<build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
这些命令行支持在ROS构建系统中配置消息和服务。此外,我们将在CMakeLists.txt中添加一行代码“message_generation”。
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs message_generation )
然后,我们还需要CMakeLists.txt中查找add_message_files行并取消这一行的注释,同时添加新消息文件的名称,如下所示:
## Generate services in the 'srv' folder add_service_files( FILES chapter2_srv.srv ) ## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES std_msgs )
最后,我们可以使用以下命令编译包:
$ cd ~/catkin_ws/ $ catkin_make
我们可以使用rossrv命令检查一切是否正常运行:
$ rossrv show chapter2_tutorials/chapter2_srv
执行rossrv命令之后将显示chapter2_msg.msg文件的内容。
现在我们已经学会了如何在ROS中创建服务数据类型。接下来,我们将研究如何创建计算两个数字之和的服务。首先在chapter2_tutorials包的src文件夹中创建两个节点,即一个服务端和一个客户端,名称为:example_3a.cpp和example_3b.cpp。
在第一个example_3a.cpp文件中输入如下代码:
#include "ros/ros.h" #include "chapter2_tutorials/chapter2_srv.h" bool add(chapter2_tutorials::chapter2_srv::Request &req, chapter2_tutorials::chapter2_srv::Response &res) { res.sum = req.A + req.B; ROS_INFO("Request: A=%d, B=%d", (int)req.A, (int)req.B); ROS_INFO("Response: [%d]", (int)res.sum); return true; } int main(int argc, char **argv) { ros::init(argc, argv, "adder_server"); ros::NodeHandle n; ros::ServiceServer service = n.advertiseService("chapter2_tutorials/adder", add); ROS_INFO("adder_server has started"); ros::spin(); return 0; }
我们来研究这段代码。下面这些行引入必要的头文件和之前创建的srv文件:
#include "ros/ros.h" #include "chapter2_tutorials/chapter2_srv.h"
下面的函数将对两个变量求和,并将结果发送到客户端节点:
bool add(chapter2_tutorials::chapter2_srv::Request &req, chapter2_tutorials::chapter2_srv::Response &res) { res.sum = req.A + req.B; ROS_INFO("Request: A=%d, B=%d", (int)req.A, (int)req.B); ROS_INFO("Response: [%d]", (int)res.sum); return true; }
在ROS计算网络上创建和发布服务:
ros::ServiceServer service = n.advertiseService("chapter2_tutorials/adder", add);
我们要在第二个文件example_3b.cpp添加如下代码:
#include "ros/ros.h" #include "chapter2_tutorials/chapter2_srv.h" #include <cstdlib> int main(int argc, char **argv) { ros::init(argc, argv, "adder_client"); if (argc != 3) { ROS_INFO("Usage: adder_client A B "); return 1; } ros::NodeHandle n; ros::ServiceClient client = n.serviceClient<chapter2_tutorials::chapter2_srv>("chapter2_tutorials/adder "); chapter2_tutorials::chapter2_srv srv; srv.request.A = atoll(argv[1]); srv.request.B = atoll(argv[2]); if (client.call(srv)) { ROS_INFO("Sum: %ld", (long int)srv.response.sum); } else { ROS_ERROR("Failed to call service adder_server"); return 1; } return 0; }
我们需要为这个服务端创建一个名为chapter2_tutorials/adder的客户端:
ros::ServiceClient client = n.serviceClient<chapter2_tutorials::chapter2_srv>("chapter2_tutorials/adder ");
在下面的代码中,我们会创建一个srv请求类型的实例,并填充要发送的所有值,其中有两个字段:
chapter2_tutorials::chapter2_srv srv; srv.request.A = atoll(argv[1]); srv.request.B = atoll(argv[2]);
在下一行的代码中将会调用服务端并发送数据。如果调用成功的话,call()将会返回true,否则call()将会返回false:
if (client.call(srv))
我们需要在CMakeLists.txt文件中添加以下命令行来完成对服务端和客户端节点的编译:
add_executable(example3a src/example_3a.cpp) add_executable(example3b src/example_3b.cpp) add_dependencies(example3a chapter2_tutorials_generate_messages_cpp) add_dependencies(example3b chapter2_tutorials_generate_messages_cpp) target_link_libraries(example3a ${catkin_LIBRARIES}) target_link_libraries(example3b ${catkin_LIBRARIES})
我们使用catkin_make工具来编译这个功能包,这将会对所有的节点进行编译:
$ cd ~/catkin_ws $ catkin_make
我们需要在两个单独的shell中执行以下命令才能使用这些节点:
$ rosrun chapter2_tutorials example3a $ rosrun chapter2_tutorials example3b 2 3
输出结果如图2-17所示。
图2-17 服务端和客户端
我们已经研究了使用ROS所需的基本概念,并学习了发布者和订阅者、客户端和服务端以及参数服务器。在本节中,我们将学习ROS中高级工具的应用。