ROS学习记录(二)--消息和服务

转:http://blog.csdn.net/Siyuada/article/details/78936675

命令总结

rospack = ros+pack(age) : 提供ROS package相关的信息 
rosstack = ros+stack  : 提供stacks 相关的信息 
roscd = ros+cd  : changes directory to a ROS package or stack 
rosls = ros+ls  : lists files in a ROS package 
roscp = ros+cp  : copies files from/to a ROS package 
rosmsg = ros+msg : provides information related to ROS message definitions 
rossrv = ros+srv : provides information related to ROS service definitions 
rosmake = ros+make : makes (compiles) a ROS package 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用rosed编辑ROS中的文件

rosed : 可以直接通过package名来获取到待编辑的文件而无需指定该文件的存储路径

rosed [package_name] [filename]

rosed roscpp Logger.msg
  • 1

默认编辑器是vim,将其他编辑器设置为默认,用gedit编辑

export EDITOR='gedit'
  • 1

创建ROS消息和ROS服务

消息msg: msg文件就是一个描述ROS中所使用消息类型的简单文本。它们会被用来生成不同语言的源代码

msg文件存放在package的msg目录下,实际上就是每行声明一个数据类型和变量名

可以使用的数据类型如下:

int8, int16, int32, int64 (plus uint*) float32, float64 string time, duration other msg files variable-length array[] and fixed-length array[C]

在ROS中有一个特殊的数据类型:Header,它含有时间戳和坐标系信息
在msg文件的第一行经常可以看到Header header的声明.
下面是一个msg文件的样例,它使用了Header,string,和其他另外两个消息类型。

Header header
  string child_frame_id
  geometry_msgs/PoseWithCovariance pose
  geometry_msgs/TwistWithCovariance twist
  • 1
  • 2
  • 3
  • 4

服务srv: 一个srv文件描述一项服务。它包含两个部分:请求和响应

srv文件存放在srv目录下,srv文件分为请求和响应两部分,由’—’分隔。下面是srv的一个样例

int64 A
int64 B
---
int64 Sum
  • 1
  • 2
  • 3
  • 4

使用msg

创建一个msg

在beginner_tutorials包里创建msg文件夹,定义新的消息

echo "int64 num" > msg/Num.msg
  • 1

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

查看package.xml, 确保它包含一下两条语句:没有的话加上

  <build_depend>message_generation</build_depend>
错误  <run_depend>message_runtime</run_depend>
    <exec_depend>message_runtime</exec_depend>
  • 1
  • 2
  • 3

注意:在构建的时候,我们只需要”message_generation”。
然而,在运行的时候,我们只需要”message_runtime”。
打开CMakelist.txt
在CMakeLists.txt文件中,利用find_packag函数,增加对message_generation的依赖,这样就可以生成消息了

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

catkin_package(
  INCLUDE_DIRS include
  LIBRARIES beginner_tutorials
  CATKIN_DEPENDS roscpp rospy std_msgs **message_runtime**
  DEPENDS system_lib
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

找到add_message_files,去掉#,把msg文件名写成自己的msg

add_message_files(
   FILES
   NUM.msg
#   Message2.msg
 )
  • 1
  • 2
  • 3
  • 4
  • 5

手动添加.msg文件后,我们要确保CMake知道在什么时候重新配置我们的project,找到下面,去掉#

 generate_messages(
   DEPENDENCIES
   std_msgs
 )
 这个例子是依赖std_msgs,不要添加roscpp,rospy
 在package.xml中加入:
   <build_depend>std_msgs</build_depend>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

为下边这行代码去掉#:

# rosbuild_genmsg()
  • 1

没找到??应该是rosbuild编译系统需要的,此处用catkin,不用加这句和下面的gensrv

创建好消息后,通过rosmsg show 看是否能识别消息
rosmsg show [message type]

rosmsg show beginner_tutorials/Num
  • 1

提示找不到msg Num
原因:变量$ROS_PACKAGE_PATH里没有beginner_tutorials的路径 原来是在bashrc里面没有添加进去
解决

export ROS_PACKAGE_PATH=/home/ada/Code/rgbdslam_catkin_ws/src:/opt/ros/kinetic/share:/home/ada/kinect/catkin_ws/src:/home/ada/ada_ws/src
或export ROS_PACKAGE_PATH=/home/ada/ada_ws/src:$ROS_PACKAGE_PATH 
  • 1
  • 2
rosmsg show beginner_tutorials/Num (也可以省略包名)
显示:int64 num
rosmsg show Num 
显示:
[beginner_tutorials/Num]:
int64 num
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用srv

创建一个srv

在beginner_tutorials目录下创建srv文件夹
这次我们不再手动创建服务,而是从其他的package中复制一个服务
roscp是一个很实用的命令行工具,它实现了将文件从一个package复制到另外一个package的功能
roscp [package_name] [file_to_copy_path] [copy_path]

roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv
  • 1

message_generation 对msg和srv都起作用,在CMakeLists.txt文件中增加对message_generation的依赖,find_package(…)
然后同样cmakelist.txt中找到,去掉#,把文件名改成刚刚复制的那个

   FILES
   AddTwoInts.srv
#   Service2.srv
 )
  • 1
  • 2
  • 3
  • 4

为下边这行代码去掉#:

# rosbuild_gensrv()
  • 1

没找到??

创建好服务后,通过rossrv show命令,检查ROS是否能够识该服务

rossrv show beginner_tutorials/AddTwoInts
显示:
int64 a
int64 b
---
int64 sum
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

由于重新配置了CMakeLists.txt文件,因此需要对workspace编译

catkin_make
  • 1

报错:

Error(s) in /home/ada/ada_ws/src/beginner_tutorials/package.xml:
- The manifest (with format version 2) must not contain the following tags: run_depend
  • 1
  • 2

原因:

package.xml文件中有两种格式,对应的书写模式不一样: 在模式1中的格式为:
foo 在模式2中的书写格式为:
foo
foo
改为下面的格式即可,编译通过

所有在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/路径下.

需要注意的是:
find_package(…)
add_message_files(…)
add_service_files(…)
generate_message(…)
catkin_package(…)
这几项的先后顺序不能发生变化。

编写简单的消息发布器节点和订阅器节点

“节点(Node)” 是ROS中指代连接到ROS网络的可执行文件的术语

发布器节点(“talker”) : 它将不断的在ROS网络中广播消息

cd ~/ada_ws/src/beginner_tutorials
创建src文件夹,存储源代码
在beginner_tutorials package里创建src/talker.cpp文件,并粘贴如下代码:

#include "ros/ros.h"
#include "std_msgs/String.h"

#include <sstream>

/**
#include "ros/ros.h"
//ros/ros.h是一个实用的头文件,它引用了ROS系统中大部分常用的头文件,使用它会使得编程很简便
#include "std_msgs/String.h"
//引用了std_msgs/String 消息, 它存放在std_msgs package里,是由String.msg文件自动生成的头文件

#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");
//初始化ROS,指定节点名称"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;
//为该进程的节点创建一个句柄
//第一个创建的NodeHandle会为节点进行初始化,最后一个销毁的会清理节点使用的所有资源。

  /**
   * 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);
//告诉master将要在"chatter" topic上发布一个std_msgs类型的信息
//这样master会告诉所有订阅了"chatter"的节点,将有数据要发布
//第二个参数是发布序列的大小,在这样的情况下,如果我们发布的消息太快,缓冲区中的消息在大于1000个的时候就会开始丢弃先前发布的消息
/*NodeHandle::advertise() 
返回一个 ros::Publisher对象,它有两个作用:
它有一个publish()成员函数可以让你在topic上发布消息;
如果消息类型不对,它会拒绝发布。*/
  ros::Rate loop_rate(10);
//指定自循环的频率,此处以10hz的频率运行

  /**
   * 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())
  {
//roscpp会默认安装一个SIGINT句柄,它负责处理Ctrl-C键盘操作——使得ros::ok()返回FALSE
/*ros::ok()返回false,如果下列条件之一发生:
SIGINT接收到(Ctrl-C)
被另一同名节点踢出ROS网络
ros::shutdown()被程序的另一部分调用
所有的ros::NodeHandles都已经被销毁
一旦ros::ok()返回false, 所有的ROS调用都会失效*/
    /**
     * 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();
//我们使用一个由msg file文件产生的‘消息自适应’类在ROS网络中广播消息。
//现在我们使用标准的String消息,它只有一个数据成员"data"。

    ROS_INFO("%s", msg.data.c_str());
//ROS_INFO和类似的函数用来替代printf/cout.
    /**
     * 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);
//向所有连接到chatter topic的节点发送了消息

    ros::spinOnce();
//在这个例子中并不是一定要调用ros::spinOnce(),因为我们不接受回调。
//然而,如果你想拓展这个程序,却又没有在这调用ros::spinOnce(),你的回调函数就永远也不会被调用。所以,在这里最好还是加上这一语句
    loop_rate.sleep();
//这条语句是调用ros::Rate对象来休眠一段时间以使得发布频率为10hz
    ++count;
  }


  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

订阅器节点(“listener”) : 它将不断的在ROS网络中接收消息

在beginner_tutorials package目录下创建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());
}
//这是一个回调函数,当消息到达chatter topic的时候就会被调用。
//消息是以 boost shared_ptr指针的形式传输,这就意味着你可以存储它而又不需要复制数据

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);
//告诉master要订阅"chatter"topic上的消息
//当有消息到达topic时,会去调用chatterCallback()函数
//第二个参数是队列大小,以防我们处理消息的速度不够快,在缓存了1000个消息后,再有新的消息到来就将开始丢弃先前接收的消息
//NodeHandle::subscribe()返回ros::Subscriber对象,你必须让它处于活动状态直到你不再想订阅该消息。当这个对象销毁时,它将自动退订上的消息。

  /**
   * 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();
//ros::spin()进入自循环,可以尽可能快的调用消息回调函数。一旦ros::ok()返回FALSE,ros::spin()就会立刻跳出自循环
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

编译节点

在CMakeLists.txt文件末尾加入几条语句:

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这会生成两个可执行文件, talker 和 listener, 默认存储到devel space目录
具体是在~/catkin_ws/devel/lib/中
现在要为可执行文件添加对生成的消息文件的依赖

add_dependencies(talker beginner_tutorials_generate_messages_cpp)如上
  • 1

这样就可以确保自定义消息的头文件在被使用之前已经被生成。因为catkin把所有的package并行的编译,所以如果你要使用其他catkin工作空间中其他package的消息,你同样也需要添加对他们各自生成的消息文件的依赖
运行catkin_make
ok

测试一下发布器和订阅器

启动发布器

确保roscore可用,并运行:

roscore
  • 1

catkin specific 如果使用catkin,确保你在调用catkin_make后,在运行你自己的程序前,已经source了catkin工作空间下的setup.sh文件:
cd /adaws source ./devel/setup.bash
上个教程开发了发布节点”talker”. 现在运行它:

rosrun beginner_tutorials talker   
显示:
[ INFO] [1514619919.574168017]: hello world 0
[ INFO] [1514619919.674492139]: hello world 1
[ INFO] [1514619919.774342665]: hello world 2
[ INFO] [1514619919.874525171]: hello world 3 ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

启动订阅器

rosrun beginner_tutorials listener
显示:
[ INFO] [1514620019.714325380]: I heard: [hello world 291]
[ INFO] [1514620019.814041151]: I heard: [hello world 292]
[ INFO] [1514620019.913983319]: I heard: [hello world 293]
[ INFO] [1514620020.013977198]: I heard: [hello world 294]
[ INFO] [1514620020.113639708]: I heard: [hello world 295]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

编写简单的Service和Client (C++ catkin)

编写service节点

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

#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)
{
  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值求和的服务,int值从request里面获取,而返回数据装入response内,这些数据类型都定义在srv文件内部,然后一些关于request和response的信息被记录下来。最后,service完成计算后返回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);
//这里,service已经建立起来,并在ROS内发布出来。
  ROS_INFO("Ready to add two ints.");
  ros::spin();

  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

编写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 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))
  {
    ROS_INFO("Sum: %ld", (long int)srv.response.sum);
  }
  else
  {
    ROS_ERROR("Failed to call service add_two_ints");
    return 1;
  }
/*这段代码是在调用service。
由于service的调用是模态过程(调用的时候占用进程阻止其他代码的执行),一旦调用完成,将返回调用结果。
如果service调用成功,call()函数将返回true,srv.response里面的值将是合法的值。
如果调用失败,call()函数将返回false,srv.response里面的值将是非法的。*/

  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

编译节点

编辑一下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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这段代码将生成两个可执行程序”add_two_ints_server”和”add_two_ints_client”,这两个可执行程序默认被放在你的devel space下的包目录下,默认为~/catkin_ws/devel/lib/share/。

你可以直接调用可执行程序,或者使用rosrun命令去调用它们。

它们不会被装在/bin目录下,因为当你在你的系统里安装这个包的时候,这样做会污染PATH变量。
如果你希望在安装的时候你的可执行程序在PATH变量里面,你需要设置一下install target,请参考:catkin/CMakeLists.txt
现在运行catkin_make

测试service client

运行Service

$ rosrun beginner_tutorials add_two_ints_server 
[ INFO] [1514622050.890716648]: Ready to add two ints.
  • 1
  • 2

运行Client,并附带参数

rosrun beginner_tutorials add_two_ints_client 1 3 
  • 1

已经成功地运行了第一个Service和Client程序

posted on 2018-01-18 11:14  三炮儿  阅读(233)  评论(0)    收藏  举报

导航