ROS机器人编程实战
上QQ阅读APP看书,第一时间看更新

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服务的字段来调用它。它需要xy的位置、方位(θ)和新乌龟的名字:

$ 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中高级工具的应用。