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 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程序
浙公网安备 33010602011771号