ROS msg和srv

0.概念相关

Nodes:节点,一个节点其实只不过是ROS程序包中的一个可执行文件。ROS节点可以使用ROS客户库与其他节点通信。节点可以发布或接收一个话题(向话题发布消息或者订阅话题以接收消息)。节点也可以提供或使用某种服务。
Messages:消息,消息是一种ROS数据类型,用于订阅或发布到一个话题。
Topics:话题,节点可以发布消息到话题,也可以订阅话题以接收消息。
Servicce:服务,服务是节点之间通讯的另一种方式。服务允许节点发送请求(request) 并获得一个响应(response)。
Msg:描述ROS messages内容的简单text文件,它们用来生成不同语言的messages源码。
Srv:一个srv文件描述一个服务,它由两部分组成:一个请求和一个回应。 msg文件存放在一个软件包的msg目录中,而srv文件则存放在srv目录中。

0.1msg和srv究竟干什么用

  • msg:msg文件就是一个描述ROS中所使用消息类型的简单文本。它们会被用来生成不同语言的源代码。
  • srv:一个srv文件描述一项服务。它包含两个部分:请求和响应。所以srv文件具体描述的就是一个服务中请求和响应分别要求的消息格式。

1.该如何定义msg和srv?

要定义这两个东西,那就需要.msg.srv文件。那么得有个地方来放置这两个文件,总不能随便找个地儿放着就指望着它起作用吧。
那么,它们应该被放在哪里呢?
.msg:这个文件应该被放在package目录下的msg文件夹里
.srv:这个文件应该被放在package目录下的srv文件夹里

什么?你说没有这两个文件夹?
没有那就新建啊!
比如我的工作空间名叫catkin_ws,package名叫demo,那么:

cd catkin_ws/src/demo
mkdir msg
mkdir srv
  •  

在终端中这样操作不就行了。

1.0.msg文件的定义:

ROS消息由每个ROS包的msg目录中的消息定义文件说明。这些文件会被编译成与编程语言有关的的实现版本,这样你才能在写代码的时候使用这些消息结构。这意味着及时你使用的是一种解释型的编程语言,例如Python。你依然需要运行catkin_make编译一次消息定义文件。否则将无法生成可用的实现文件,Python将找不到你自定义的消息类型。
同样的道理,如果你修改完消息定义文件以后,忘记重新编译的话,那么你使用的消息依旧是老版本。
虽然这种机制听起来好像很麻烦,但是这其实是一种一劳永逸的方法,因为我们只需要定义一次消息,接着在所以ROS支持的编程语言中我们都可以使用这种消息。

ROS消息定义文件通常十分简短易懂,都是每一行一个类型名加一个变量名的格式。类型名是ROS的基础类型,或者来自其他包里定义的类型,或者是像Header这样的特殊类型。
像下面这样子的,就形成了一个简单的.msg文件。

int32 data1
string data2
  •  

1.1.srv文件的定义:

创建一个新服务的第一步是要定义服务调用的输入和输出。那么.srv文件的用武之地就来了。我们通过编辑.srv文件及服务定义文件来完成对server调用的I/O的定义。.srv有着跟消息定义文件相同的结构,但是由于server既有输入又有输出,所以要比消息定义稍微复杂一丢丢。
那么.srv文件究竟如何编写呢?
很简单:

#part1
input information
---
#part2
output information

--- 上面是request 下面是response

就这么简单?没错就是这么简单!

具体例子:

int32 a
int32 b
---
int32 sum

 

.具体例子

2、简单的主题发布者和订阅者

2.1 主题发布者节点
在package下创建src/talker.cpp,在cpp文件下输入如下程序:

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

int main(int argc, char **argv)  
{  
//初始化ROS。它允许ROS通过命令行进行名称重映射——目前,这不是重点。同样,我们也在这里指定我们节点的名称——必须唯一。这里的名称必须是一个base name,不能包含/
  ros::init(argc, argv, "talker");  
  
/为这个进程的节点创建一个句柄。第一个创建的NodeHandle会为节点进行初始化,最后一个销毁的NodeHandle会清理节点使用的所有资源。  
  ros::NodeHandle n;
 
 //告诉节点管理器master我们将要在chatter topic上发布一个ros_test/my_msg的消息。这样master就会告诉所有订阅了chatter topic的节点,将要有数据发布。第二个参数是发布序列的大小。在这样的情况下,如果我们发布的消息太快,缓冲区中的消息在大于1000个的时候就会开始丢弃先前发布的消息。NodeHandle::advertise() 返回一个 ros::Publisher对象,它有两个作用: 1) 它有一个publish()成员函数可以让你在topic上发布消息; 2) 如果消息类型不对,它会拒绝发布。  
  ros::Rate loop_rate(10);//ros::Rate对象可以允许你指定自循环的频率。它会追踪记录自上一次调用Rate::sleep()后时间的流逝,并休眠直到一个频率周期的时间。在这个例子中,我们让它以10hz的频率运行。  
  ros::Publisher chatter_pub = n.advertise<ros_test::my_msg>("chatter", 1000);

  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调用都会失效。  
   
    ros_test::my_msg msg; 
    std::stringstream ss;  
    ss << "hello world " << count;  
    msg.data1 = 1000;
    msg.data2 = ss.str();
//我们使用一个由msg file文件产生的‘消息自适应’类在ROS网络中广播消息。现在我们使用标准的String消息,它只有一个数据成员"data"。当然你也可以发布更复杂的消息类型。  
    ROS_INFO("%s", msg.data1.c_str());//ROS_INFO和类似的函数用来替代printf/cout.   
   
    chatter_pub.publish(msg);//现在我们已经向所有连接到chatter topic的节点发送了消息。  
    ros::spinOnce();//在这个例子中并不是一定要调用ros::spinOnce(),因为我们不接受回调。然而,如果你想拓展这个程序,却又没有在这调用ros::spinOnce(),你的回调函数就永远也不会被调用。所以,在这里最好还是加上这一语句。  
    loop_rate.sleep();//这条语句是调用ros::Rate对象来休眠一段时间以使得发布频率为10hz。  
    ++count;  
  }  
  return 0;  
}  

小结:编写主题发布者节点步骤:
⑴初始化ROS系统
⑵广播消息:在chatter主题上发布自定义消息ros_test/my_msg到节点管理器,告诉节点管理器master我们将要在chatter topic上发布一个ros_test/my_msg的消息,这样master就会告诉所有订阅了chatter topic的节点,将要有数据发布。
⑶以指定的频率循环发布消息到chatter主题。

2.2 主题订阅者节点
在package下创建src/listener.cpp,在cpp文件下输入如下程序:

#include "ros/ros.h"  

#include<ros_test/my_msg.h> 

void chatterCallback(const ros_test::my_msg::ConstPtr & msg)  
{  
   ROS_INFO("I get: [%d:][%s]}",msg->data1,msg->data2.c_str());
} 
}//这是一个回调函数,当消息到达chatter topic的时候就会被调用。消息是以 boost shared_ptr指针的形式传输,这就意味着你可以存储它而又不需要复制数据  

int main(int argc, char **argv)  
{  
  ros::init(argc, argv, "listener");  
  ros::NodeHandle n;  

  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);  
//告诉master我们要订阅chatter topic上的消息。当有消息到达topic时,ROS就会调用chatterCallback()函数。第二个参数是队列大小,以防我们处理消息的速度不够快,在缓存了1000个消息后,再有新的消息到来就将开始丢弃先前接收的消息。NodeHandle::subscribe()返回ros::Subscriber对象,你必须让它处于活动状态直到你不再想订阅该消息。当这个对象销毁时,它将自动退订上的消息。有各种不同的NodeHandle::subscribe()函数,允许你指定类的成员函数,甚至是Boost.Function对象可以调用的任何数据类型。  

  ros::spin();//ros::spin()进入自循环,可以尽可能快的调用消息回调函数。如果没有消息到达,它不会占用很多CPU,所以不用担心。一旦ros::ok()返回FALSE,ros::spin()就会立刻跳出自循环。这有可能是ros::shutdown()被调用,或者是用户按下了Ctrl-C,使得master告诉节点要shutdown。也有可能是节点被人为的关闭。  
  return 0;  
}  

小结:编写主题订阅者节点步骤:
⑴初始化ROS系统
⑵从chatter主题订阅消息
⑶spin,然后等待消息到达
⑷当消息到达时,chattercallbck()函数被调用

Msg文件的使用
同样的,如上2.1主题发布和2.2主题订阅所述,

#include<ros_test/my_msg.h>
  •  

这引用了ros_test/my_msg消息, 它存放在ros_test package里,是由my_msg.msg文件自动生成的头文件。

    ros_test::my_msg msg; 
    std::stringstream ss;  
    ss << "hello world " << count;  
    msg.data1 = 1000;
    msg.data2 = ss.str();

这里,使用一个由msg file文件产生的‘消息自适应’类在ROS网络中广播消息。把“hello world”赋给msg.data1作为发布的消息。

订阅者节点端使用msg

void chatterCallback(const ros_test::my_msg::ConstPtr & msg)  
{  
   ROS_INFO("I get: [%d:][%s]}",msg->data1,msg->data2.c_str());
} 

编译依赖配置 msg

编辑package.xml , 添加依赖


<build_depend>message_generation</build_depend>

<build_export_depend>message_generation</build_export_depend>

<exec_depend>message_runtime</exec_depend>

       5、编辑CMakeLists.txt

find_package()中添加message_generation


find_package(catkin REQUIRED COMPONENTS

message_generation

roscpp

rospy

std_msgs

)

 

add_message_files()中添加my_msg.msg

add_message_files(FILES

my_msg.msg

)

generate_messages()添加std_msgs

generate_messages(DEPENDENCIES

std_msgs

)

catkin_package()添加message_runtime

catkin_package(CATKIN_DEPENDS

message_runtime)

最后在add_executable(xxx src/xxx.cpp)后面添加一行

add_dependencies(xxx ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) #该项让编译器知道要注意编译顺序

target_link_libraries(xxx
  ${catkin_LIBRARIES} 
)

具体例子如下:

add_executable(talker src/talker.cpp) #可执行文件

target_link_libraries(talker ${catkin_LIBRARIES})#链接库


add_executable(listener src/listener.cpp)

target_link_libraries(listener ${catkin_LIBRARIES})#链接库


add_dependencies(listener ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) #该项让编译器知道要注意编译顺序


add_dependencies(talker ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) #该项让编译器知道要注意编译顺序


include_directories(

include

${catkin_INCLUDE_DIRS}

)

  

6、编译,catkin_make,在devel/include 中可以看到头文件

3、简单的服务器和客户端

3.1服务器端
在package下创建src/ add_two_inits_server.cpp并输入如下程序:

#include "ros/ros.h"  
#include"beginner_tutorials/AddTwoInts.h"//beginner_tutorials/AddTwoInts.h是由编译系统自动根据我们先前创建的srv文件生成的对应该srv文件的头文件。  
//这个add函数提供两个int值求和的服务,int值从请求request里面获取,而返回数据装入响应response内,这些数据类型都定义在srv文件内部,函数返回一个boolean值。  
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值已经相加,并存入了response。然后一些关于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;  
}  

3.2客户端

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

  return 0;  
}  

 Srv文件的使用
如上3.1服务器端和3.2客户端所述,

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

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

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

这里,实例化一个由ROS编译系统自动生成的service类,并给其request成员赋值,即将执行编译后的可执行文件时输入的1、2两个参数均赋值给srv文件中定义的request变量。一个service类包含两个成员request和response。同时也包括两个类定义Request和Response。

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; 
  •  

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

编译依赖配置 srv

编辑package.xml , 添加依赖


<build_depend>message_generation</build_depend>

<build_export_depend>message_generation</build_export_depend>

<exec_depend>message_runtime</exec_depend>

       5、编辑CMakeLists.txt

find_package()中添加message_generation


find_package(catkin REQUIRED COMPONENTS

message_generation

roscpp

rospy

std_msgs

)

 

add_service_files()中添加my_msg.msg

add_service_files(FILES
  AddTwoInts.srv
)

generate_messages()添加std_msgs

generate_messages(DEPENDENCIES

std_msgs

)

catkin_package()添加message_runtime

catkin_package(CATKIN_DEPENDS

message_runtime)

最后在add_executable(xxx src/xxx.cpp)后面添加一行

add_dependencies(xxx ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) #该项让编译器知道要注意编译顺序

target_link_libraries(xxx
  ${catkin_LIBRARIES} 
)

具体例子如下:

add_executable(add_two_inits_server add_two_inits_server.cpp) #可执行文件
target_link_libraries(add_two_inits_server ${catkin_LIBRARIES})#链接库
 
add_executable(client client.cpp)
target_link_libraries(client ${catkin_LIBRARIES})#链接库
 
add_dependencies(add_two_inits_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) #该项让编译器知道要注意编译顺序
 
add_dependencies(client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) #该项让编译器知道要注意编译顺序
 
include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)

  

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值