ROS service 和client的测试以及roslaunch的使用

由于任务需要,安排测试单机可以模拟多少个client来并发访问一个service。

我的思路:在本机启动一个ros service,然后编写一个client来访问这个service,然后测试client能否成功访问。然后再编写launch文件,来批量启动client。

service的功能:名字“add_two_ints_server”,该节点将接收到两个整形数字,并返回它们的和。

client的功能:输入两个int,发送给service,然后接收返回值。

具体过程:

0. 创建package

创建一个package,用于整个任务测试,package名字为beginner_tutorials

cd ~/catkin_ws/src
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp 

1. 根据roswiki tutorial的Creating_a_srv编写一个service

1.1 创建一个 msg----Num.msg

在beginner_tutorials包里创建新的消息:

$ cd ~/catkin_ws/src/beginner_tutorials
$ mkdir msg
$ echo "int64 num" > msg/Num.msg

上面是最简单的例子——在.msg文件中只有一行数据。当然,你可以仿造上面的形式多增加几行以得到更为复杂的消息:

string first_name
string last_name
uint8 age
uint32 score

接下来,还有关键的一步:我们要确保msg文件被转换成为C++,Python和其他语言的源代码:

查看package.xml, 确保它包含一下两条语句:

<build_depend>message_generation</build_depend>

<exec_depend>message_runtime</exec_depend>

如果没有,添加进去。 注意,在构建的时候,我们只需要"message_generation"。然而,在运行的时候,我们只需要"message_runtime"。

打开CMakeLists.txt文件,利用find_packag函数,增加对message_generation的依赖,这样就可以生成消息了。 (你可以直接在COMPONENTS的列表里增加message_generation(也就是说在创建beginner_tutorials包的时候,添加这个依赖项)),就像这样: 

find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)

同样,你需要确保你设置了运行依赖:

catkin_package(
  ...
  CATKIN_DEPENDS message_runtime ...
  ...)

找到如下代码块:

# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

去掉注释符号#,用你的.msg文件替代Message*.msg,就像下边这样:

add_message_files(
  FILES
  Num.msg
)

手动添加.msg文件后,我们要确保CMake知道在什么时候重新配置我们的project。 确保添加了如下代码:

generate_messages()

现在,你可以生成自己的消息源代码了。

附:-----------------------------------------------------------------------------------------------------

以上就是你创建消息的所有步骤。下面通过rosmsg show命令,检查ROS是否能够识消息。

使用方法: $ rosmsg show [message type]

样例:

$ rosmsg show beginner_tutorials/Num

你将会看到:

  • int64 num

在上边的样例中,消息类型包含两部分:

  • beginner_tutorials -- 消息所在的package

  • Num -- 消息名Num.

如果你忘记了消息所在的package,你也可以省略掉package名。输入: 

$ rosmsg show Num

你将会看到:

[beginner_tutorials/Num]:
int64 num

---------------------------------------------------------------------------------------------------------------

1.2 创建一个srv----AddTwoInts.srv

在刚刚那个beginner_tutorials包中创建一个服务:

$ roscd beginner_tutorials

$ mkdir srv

这次我们不再手动创建服务,而是从其他的package中复制一个服务。 roscp是一个很实用的命令行工具,它实现了将文件从一个package复制到另外一个package的功能。

使用方法: $ roscp [package_name] [file_to_copy_path] [copy_path]

现在我们可以从rospy_tutorials package中复制一个服务文件了:

$ roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

这里,因为不是我们自己写的,所以很好奇这个.srv文件到底是啥,我们打开它,发现很简单,内容只有4行,如下:

int64 a
int64 b
---
int64 sum
还有很关键的一步:我们要确保srv文件被转换成C++,Python和其他语言的源代码。 就是前边所介绍的,在 CMakeLists.txt文件中增加了对 message_generation的依赖。
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation)

同样,跟msg文件类似,你也需要在CMakeLists.txt文件中做一些修改。查看上边的说明,增加额外的依赖项。

删掉#,去除对下边语句的注释:

# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

用你自己的srv文件名替换掉那些Service*.srv文件: 

add_service_files(
  FILES
  AddTwoInts.srv
)
现在,你可以生成自己的服务源代码了。

附:---------------------------------------------------------------------------------------------------------------

以上就是创建一个服务所需的所有步骤。下面通过rosmsg show命令,检查ROS是否能够识该服务。

使用方法: $ rossrv show <service type>

例子:

$ rossrv show beginner_tutorials/AddTwoInts

你将会看到(其实就是.srv的内容):

  • int64 a
    int64 b
    ---
    int64 sum

rosmsg类似, 你也可以不指定具体的package名来查找服务文件:

$ rossrv show AddTwoInts
[beginner_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum

[rospy_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum

1.3 重新编译beginner_tutorials包

接下来,在CMakeLists.txt中找到如下部分:

# generate_messages(
#   DEPENDENCIES
# #  std_msgs  # Or other packages containing msgs
# )

去掉注释并附加上所有你消息文件所依赖的那些含有.msg文件的package(这个例子是依赖std_msgs,不要添加roscpp,rospy),结果如下:

generate_messages(
  DEPENDENCIES
  std_msgs
)

由于增加了新的消息,所以我们需要重新编译我们的package:

# In your catkin workspace
$ cd ../..
$ catkin_make
$ cd 

所有在msg路径下的.msg文件都将转换为ROS所支持语言的源代码。生成的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/ 路径下.

我们同样很好奇生成.h文件的内容是啥,我们打开它,内容如下(看不懂):

// Generated by gencpp from file beginner_tutorials/AddTwoInts.msg
// DO NOT EDIT!

#ifndef BEGINNER_TUTORIALS_MESSAGE_ADDTWOINTS_H
#define BEGINNER_TUTORIALS_MESSAGE_ADDTWOINTS_H
#include <ros/service_traits.h>
#include <beginner_tutorials/AddTwoIntsRequest.h>
#include <beginner_tutorials/AddTwoIntsResponse.h>
namespace beginner_tutorials
{
struct AddTwoInts
{
typedef AddTwoIntsRequest Request;
typedef AddTwoIntsResponse Response;
Request request;
Response response;

typedef Request RequestType;
typedef Response ResponseType;

}; // struct AddTwoInts
} // namespace beginner_tutorials

namespace ros
{
namespace service_traits
{
template<>
struct MD5Sum< ::beginner_tutorials::AddTwoInts > {
  static const char* value()
  {
    return "6a2e34150c00229791cc89ff309fff21";
  }
  static const char* value(const ::beginner_tutorials::AddTwoInts&) { return value(); }
};

template<>
struct DataType< ::beginner_tutorials::AddTwoInts > {
  static const char* value()
  {
    return "beginner_tutorials/AddTwoInts";
  }

  static const char* value(const ::beginner_tutorials::AddTwoInts&) { return value(); }
};
// service_traits::MD5Sum< ::beginner_tutorials::AddTwoIntsRequest> should match
// service_traits::MD5Sum< ::beginner_tutorials::AddTwoInts >
template<>
struct MD5Sum< ::beginner_tutorials::AddTwoIntsRequest>
{
  static const char* value()
  {
    return MD5Sum< ::beginner_tutorials::AddTwoInts >::value();
  }
  static const char* value(const ::beginner_tutorials::AddTwoIntsRequest&)
  {
    return value();
  }
};

// service_traits::DataType< ::beginner_tutorials::AddTwoIntsRequest> should match
// service_traits::DataType< ::beginner_tutorials::AddTwoInts >
template<>
struct DataType< ::beginner_tutorials::AddTwoIntsRequest>
{
  static const char* value()
  {
    return DataType< ::beginner_tutorials::AddTwoInts >::value();
  }
  static const char* value(const ::beginner_tutorials::AddTwoIntsRequest&)
  {
    return value();
  }
};

// service_traits::MD5Sum< ::beginner_tutorials::AddTwoIntsResponse> should match
// service_traits::MD5Sum< ::beginner_tutorials::AddTwoInts >
template<>
struct MD5Sum< ::beginner_tutorials::AddTwoIntsResponse>
{
  static const char* value()
  {
    return MD5Sum< ::beginner_tutorials::AddTwoInts >::value();
  }
  static const char* value(const ::beginner_tutorials::AddTwoIntsResponse&)
  {
    return value();
  }
};

// service_traits::DataType< ::beginner_tutorials::AddTwoIntsResponse> should match
// service_traits::DataType< ::beginner_tutorials::AddTwoInts >
template<>
struct DataType< ::beginner_tutorials::AddTwoIntsResponse>
{
  static const char* value()
  {
    return DataType< ::beginner_tutorials::AddTwoInts >::value();
  }
  static const char* value(const ::beginner_tutorials::AddTwoIntsResponse&)
  {
    return value();
  }
};

} // namespace service_traits
} // namespace ros

#endif // BEGINNER_TUTORIALS_MESSAGE_ADDTWOINTS_H

自动生成.h文件总共有4个,



---------------------------------------------------------------------------------------------------------------

2. 编写Service节点

在beginner_tutorials包中创建src/add_two_ints_server.cpp文件,并复制粘贴下面的代码:

   #include "ros/ros.h"
   #include "beginner_tutorials/AddTwoInts.h"//AddTwoInts.h是重新编译beginner_tutorials包自动生成的

   bool add(beginner_tutorials::AddTwoInts::Request  &req,
            beginner_tutorials::AddTwoInts::Response &res)
   {
     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;
  }

  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);//广告服务名add_two_ints
    ROS_INFO("Ready to add two ints.");
    ros::spin();

    return 0;
  }

代码解释

现在,让我们来逐步分析代码。

   #include "ros/ros.h"
   #include "beginner_tutorials/AddTwoInts.h"

beginner_tutorials/AddTwoInts.h是由编译系统自动根据我们先前创建的srv文件生成的对应该srv文件的头文件。

   bool add(beginner_tutorials::AddTwoInts::Request  &req,
            beginner_tutorials::AddTwoInts::Response &res)

这个函数提供两个int值求和的服务,int值从request里面获取,而返回数据装入response内,这些数据类型都定义在srv文件内部,函数返回一个boolean值。

   {
     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;
   }

现在,两个int值已经相加,并存入了response。然后一些关于request和response的信息被记录下来。最后,service完成计算后返回true值。

     ros::ServiceServer service = n.advertiseService("add_two_ints", add);

这里,service已经建立起来,并在ROS内发布出来。

3. 编写client节点

在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服务
     beginner_tutorials::AddTwoInts srv;
     srv.request.a = atoll(argv[1]);
     srv.request.b = atoll(argv[2]);
    clock_t start,finish;//我自己添加的,用于计算反馈时间
    start=clock();
    if (client.call(srv)) { 
          finish=clock();
          ROS_INFO("Sum: %ld, consum: %ld ms", (long int)srv.response.sum,(long int)1000*(finish-start)/CLOCKS_PER_SEC);
    } 
     else
     {
       ROS_ERROR("Failed to call service add_two_ints");
       return 1;
     }

     return 0;
   }

代码解释

现在,让我们来逐步分析代码。

     ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");

这段代码为add_two_ints service创建一个client。ros::ServiceClient 对象待会用来调用service。

     beginner_tutorials::AddTwoInts srv;
     srv.request.a = atoll(argv[1]);
     srv.request.b = atoll(argv[2]);

这里,我们实例化一个由ROS编译系统自动生成的service类,并给其request成员赋值。一个service类包含两个成员request和response。同时也包括两个类定义Request和Response。

     if (client.call(srv))

这段代码是在调用service。由于service的调用是模态过程(调用的时候占用进程阻止其他代码的执行),一旦调用完成,将返回调用结果。如果service调用成功,call()函数将返回true,srv.response里面的值将是合法的值。如果调用失败,call()函数将返回false,srv.response里面的值将是非法的。

4. 重新编译package

再来编辑一下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 space下的包目录下,默认为~/catkin_ws/devel/lib/share/。你可以直接调用可执行程序,或者使用rosrun命令去调用它们。

现在运行catkin_make命令:

#In your catkin workspace
cd ~/catkin_ws
catkin_make

如果你的编译过程因为某些原因而失败:

确保你已经依照先前的creating the AddTwoInts.srv教程里的步骤完成操作。

5. 运行service和client

让我们从运行Service开始 之前,确保在catkin工作空间下(~/catkin_ws$):

$ source ./devel/setup.bash
$ rosrun beginner_tutorials add_two_ints_server     (C++)
$ rosrun beginner_tutorials add_two_ints_server.py  (Python)

你将会看到如下类似的信息:

Ready to add two ints.

(在新终端)运行Client
现在,运行Client并附带一些参数:

$ source ./devel/setup.bash
$ rosrun beginner_tutorials add_two_ints_client 1 3     (C++)
$ rosrun beginner_tutorials add_two_ints_client.py 1 3  (Python)

你将会看到如下类似的信息:

request: x=1, y=3
sending back response: [4]

现在,你已经成功地运行了你的第一个Service和Client程序

6. 编写launch 文件来批量启动client

roslaunch可以用来启动定义在launch文件中的多个节点。

用法: $ roslaunch [package] [filename.launch]

先切换到beginner_tutorials程序包目录下:

$ roscd beginner_tutorials

然后创建一个launch文件夹:

$ mkdir launch
$ cd launch

现在我们来创建一个名为test1.launch的空launch文件,并复制粘贴以下内容到该文件里面:

Toggle line numbers
   1 <launch>
   2 
   3 </launch>
由于tutorial中(http://wiki.ros.org/cn/ROS/Tutorials/UsingRqtconsoleRoslaunch)介绍launch文件的例子是根据订阅/发布模式的,我们不能直接用,所以这里只好先建一个空的文件。因为之前还没有接触到这个东西,所以这里只能在网上到处找,然后尝试。

最终在如下位置找到一个可用的:http://blog.chinaunix.net/uid-29742351-id-4961536.html

它给出的例子是同时启动service和client,如下:

    <node name="add_two_ints_server" pkg="beginner_tutorials" type="add_two_ints_server" />
    <node name="add_two_ints_client" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" /> 

对于我们来说,我们也可以直接用这个文件,也可以在launch中只启动多个client,而在terminal中启动service。我们修改后的launch文件如下:

<launch>
	<node name="client1" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client2" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client3" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client4" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client5" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client6" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client7" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client8" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client9" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
	<node name="client10" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />
</launch>

7. 通过launch文件启动多个client

这里我们在launch文件中只编写了多个client,而不包括service,所以我们还要在terminal中先启动service。

~/catkin_ws$ rosrun beginner_tutorials add_two_ints_server

然后在另一个terminal上运行launch文件:

~/catkin_ws$ roslaunch beginner_tutorials test1.launch a:=123 b:=91

然后根据client的输出内容,可以到响应的目录查看结果和日志。




附 :package.xml和CMakelist.txt

1. package.xml

<?xml version="1.0"?>

<package format="2">
  <name>beginner_tutorials</name>
  <version>0.0.0</version>
  <description>The beginner_tutorials package</description>

  <maintainer email="[email protected]">oldking</maintainer>
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_depend>message_generation</build_depend>
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>rospy</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  <exec_depend>message_runtime</exec_depend>


  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <!-- Other tools can request additional information be placed here -->

  </export>
</package>

2. CMakeList.txt

cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

## Generate messages in the 'msg' folder
 add_message_files(
   FILES
   Num.msg
 )

## Generate services in the 'srv' folder
add_service_files(
  FILES
  AddTwoInts.srv
)

## Generate added messages and services with any dependencies listed here
 generate_messages(
   DEPENDENCIES
   std_msgs
 )

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES beginner_tutorials
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
 # DEPENDS system_lib
)

include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

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)


猜你喜欢

转载自blog.csdn.net/jinking01/article/details/79555087
今日推荐