程序演示
下面是我写的一个服务程序,服务文件ctrl.srv
的构成为:1
2
3int8 cmd
---
bool ret
客户端发出命令,如果cmd!=0,服务端会打开摄像头程序;如果cmd=0,服务端会关闭摄像头程序。
注意:最好是将所有自定义的服务和消息文件放到一个单独的package里面,否则会出现一个package的修改会影响到另一个用到它的消息/服务的package的编译.
新建srv文件后,容易忘记在CMakeLists里添加这个文件,导致编译失败
服务端
1 |
|
可以看出服务端的程序与话题中的订阅者程序高度相似,control
函数就是个回调函数。
system
函数调用roslaunch
时,由于是在fork出的子进程里执行,launch的节点会一直阻塞不返回,在命令最后加&,让子进程返回
service回调函数里只能return true
或false
,若return整数会导致客户端的call
结果为false,但实际成功,这样会影响判断。
service客户端发命令后,出现报错 ERROR: service [/service_name] responded with an error: b’’ ,原因在于service服务端的回调函数必须 return true
advertiseService
的部分源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15bool ServiceManager::advertiseService(const AdvertiseServiceOptions& ops)
{
boost::recursive_mutex::scoped_lock shutdown_lock(shutting_down_mutex_);
if (shutting_down_)
{
return false;
}
{
boost::mutex::scoped_lock lock(service_publications_mutex_);
// 如果service已经发布,就报错,然后返回,其实这里改成报警比较合适
if (isServiceAdvertised(ops.service))
{
ROS_ERROR("Tried to advertise a service that is already advertised in this node [%s]", ops.service.c_str());
return false;
}
如果涉及到耗时的工作,回调函数应该这样写1
2
3
4
5
6bool serviceCB(mow_msgs::task::Request &req, mow_msgs::task::Response &res)
{
// ......
res.ret = "to charge";
// 耗时的工作
}
如果把耗时的工作放在res
之前,那么客户端提前退出时(比如ctrl+C
),会报错没有收到返回值,虽然不影响,但是不优雅。
客户端
1 |
|
构建client的时候后面的路径要写绝对路径,有时候需要加个 /
客户端的程序与话题发布者的程序高度相似,exists()
检查服务端的服务是否启动,若未启动则返回。call
调用了服务,成功会返回true
,而且调用完成后,可以在客户端程序里获得服务端的返回值
最后,先运行服务端,然后运行客户端,如果是rosrun control_cam controlCam 1
则在服务端所在终端启动摄像头程序,若是0则关闭。
另一种使用方式
1 | std_srvs::Empty srv; |
常用函数
ros::ServiceClient
常用函数:1
2
3
4
5
6
7
8
9bool call (Service &service) //调用service,client发起通信。成功返回true
bool exists () //检查相应名称的服务是否可用
std::string getService () //返回客户端通信的服务名称
// 单例模式
static const ServiceManagerPtr & instance ()
bool unadvertiseService (const std::string &serv_name)
如果要解除service,用法是ros::ServiceManager::instance()::unadvertiseService
,也就是使用单例模式进行全局访问。 API说isServiceAdvertised
可以判断某service是否发布,但可惜是private
问题
切换算法时,会有个报警 Tried to advertise a service that is already advertised in this node
。这个其实无任何影响,报警在ROS底层的源码,原因在于GlobalPlanner::initialize
函数有一句:1
make_plan_srv_ = private_nh.advertiseService("make_plan", &GlobalPlanner::makePlanService, this);
这里是已经注册了service,如果想要去掉这个报警,可以这样改:1
2
3
4std::string makePlanServiceName = "/move_base/"+name+"/make_plan";
ros::ServiceManager::instance()->unadvertiseService(makePlanServiceName);
make_plan_srv_ = private_nh.advertiseService("make_plan", &GlobalPlanner::makePlanService, this);
其中的bool ros::ServiceManager::unadvertiseService(const std::string& serv_name)
作用是 Unadvertise a service. This call unadvertises a service, which must have been previously advertised, using advertiseService().
md5不匹配
调用service时报错md5不匹配
,其实是调用失败了,call
的返回值是false,首先应检查客户端定义是否正确和服务是否存在:
客户端一直阻塞
我在move_base.cpp
里写了一个函数 func,是rosservice的客户端,调用service clear_costmaps
。在MoveBase
的其他函数里,调用这个函数,结果发现会一直阻塞。后来注意到服务端程序就是MoveBase::clearCostmapsService
,也就是在同一个类里
客户端call service阻塞的原因只有两个:
- 多个客户端call 同一个service,而服务端一次只能处理一个请求
- 服务端程序没有return值
服务端程序是MoveBase
的,而且有了返回值,那么只可能是第一个原因了。