零、Service机制
- Client与Server向ROS MASTER注册
- MASTER根据Client订阅的信息,查阅注册表中是否有提供对应Service的Server
- Client收到Server的TCP通信地址
- Client用TCP通信尝试与Server连接,并发送Service的请求数据
- Server接收Service请求与参数,开始执行Service
- Server执行Service完毕后,向Client发送应答数据
一、客户端 Client
- 初始化ROS节点
- 创建句柄
- 检测所需的服务是否存在
- 创建Client实例
- 封装要请求的数据内容
- 发布数据请求服务
- 等待Service反馈结果
1.创建源码
/*
ask the service /spawn
data type of the service is turtlesim::Spawn
*/
#include<ros/ros.h>
#include<turtlesim/Spawn.h>
int main(int argc,char **argv)
{
//ROS INIT
ros::init(argc,argv,"turtle_spawn");
//create nodeHandle
ros::NodeHandle node;
//when /spawn service is detected
//create a client and connect to /spawn service
ros::service::waitForService("/spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
//initialize the ask data for turtlesim::Spawn
turtlesim::Spawn srv;
srv.request.x = 2.0;
srv.request.y = 2.0;
srv.request.name = "turtle2";
//ask for the service
ROS_INFO("Call service to spawn turtle[x:0.3%f y:0.3%f name:%s]",
srv.request.x,srv.request.y,srv.request.name.c_str());
add_turtle.call(srv);
//show the result
ROS_INFO("Spawn turtle successfully![name:%s]",srv.response.name.c_str());
return 0;
}
2.修改CMakeLists.txt
为成功编译
在build部分下添加
add_executable(turtle_spawn src/turtle_spawn.cpp)
target_link_libraries(turtle_spawn ${
catkin_LIBRARIES})
二、服务器 Server
1.编写源码
/*
This node will execuate /turtle_command
data type of the service is std_srvs/Trigger
*/
#include<ros/ros.h>
#include<geometry_msgs/Twist.h>
#include<std_srvs/Trigger.h>
//a global publisher
ros::Publisher turtle_vel_pub;
//figure out the sport statement of turtle
//false is stop, and true is moving
bool pubCommand = false;
//Function:
//CallBack Function for service, the input parameter is req, and the output parameter is res
//when the request is called,system will execute this function
//回调函数,输入参数为std_srvs::trigger::Request类型的req,输出参数为std_srvs::Trigger::Response类型的res
bool CommandCallBack(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res)
{
//not
pubCommand = !pubCommand;
//print the ask data
ROS_INFO("Publish turtle velocity command [%s]",pubCommand==true?"YES":"NO");
//set feedback data to the Client
//执行Service成功,返回res给Client
res.success = true;
res.message = "Turtle command state Changed";
}
int main(int argc,char **argv)
{
//ROS INIT
ros::init(argc,argv,"turtle_command_server");
//Create NodeHandle
ros::NodeHandle n;
//Create server /turtle_command
//register the CallBack Function
//注册Server名为command_service,接收名为/turtle_command的Service请求,触发回调函数CommandCallBack
ros::ServiceServer command_service = n.advertiseService("/turtle_command",CommandCallBack);
//Create a Publisher and pub the topic /turtle1_cmd_vel
//message type is geometry_msgs::Twist
//length of line is 10
turtle_vel_pub=n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);
//waiting for the ask
ROS_INFO("Ready to receive turtle command.");
//set the rate of rotation
ros::Rate loop_rate(10);
while(ros::ok())
{
//check the CallBackFunction line
ros::spinOnce();//循环等待回调函数
//pub the velocity command if the flag is ture
//the main process was set in the main function
//so the CallBackFunction will be easy
if(pubCommand)
{
geometry_msgs::Twist vel_msg;
vel_msg.linear.x=0.5;
vel_msg.angular.z=0.2;
turtle_vel_pub.publish(vel_msg);
}
}
}
2.修改CMakeLists.txt
为成功编译
在build部分下添加
add_executable(turtle_command_server src/turtle_command_server.cpp)
target_link_libraries(turtle_command_server ${
catkin_LIBRARIES})
3.测试
roscore
rosrun turtlesim turtlesim_node
rosrun learning_service turtle_command_server
执行结果:服务器准备接受service请求
[ INFO] [1647765013.874899023]: Ready to receive turtle command.
通过rosservice的call指令向服务器发起请求,内容为空(该service数据格式允许request部分为空)
rosservice call /turtle_command "{}"
该命令的反馈结果:
success: True
message: “Change turtle command state”
在服务器端结果:
[ INFO] [1647765059.771672656]: Publish turtle velocity command [YES]
海龟状态:开始画圆圈
再次执行
rosservice call /turtle_command "{}"
该命令的反馈结果:
success: True
message: “Change turtle command state”
在服务器端结果:
[ INFO] [1647765367.266284668]: Publish turtle velocity command [NO]
海龟状态:停止画圆圈
三、Service数据的定义与使用
在目录下建立srv文件夹用于存储.srv文件
1.修改Person.srv内容
string name
uint8 age
uint8 sex
uint8 unknown = 0
uint8 male= 1
uint8 female= 2
---
string result
三条横线用于划分request部分和response部分
在编译生成头文件时,两个部分将分开保存为三个头文件
Person.h PersonRequest.h PersonResponse.h
2.修改package.xml
添加消息生成、运行的功能包依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
3.修改CMakeLists.txt
在find_package中添加message_generation包依赖
find_package(catkin REQUIRED COMPONENTS
geometry_msgs
roscpp
rospy
std_msgs
turtlesim
message_generation
)
在
#
Declare ROS messages, services and actions
#
部分
添加
add_service_files(FILES Person.srv)
generate_messages(DEPENDENCIES std_msgs)
无须添加Person.srv的路径,编译时会自动查找功能包下的srv文件
添加生成信息的依赖包std_msgs
4.编写Client源码
/*
This node will request service /show_person
ServiceDataType is learning_service::Person
*/
#include<ros/ros.h>
#include "learning_service/Person.h"
int main(int argc,char **argv)
{
//ROSINIT
ros::init(argc,argv,"person_client");
//Create NodeHandle
ros::NodeHandle n;
//Searching for service /show_person
//Create a Client and connect it with service /show_person
ros::service::waitForService("/show_person");
ros::ServiceClient person_client = n.serviceClient<learning_service::Person>("/show_person");
//Initialize the date which would be send to service /show_person
learning_service::Person srv;
srv.request.name="Herman123";
srv.request.age=20;
srv.request.sex=learning_service::Person::Request::male;
//send request to Service /show_person
ROS_INFO("Call service to show person[name:%s age:%d sex:%d]",srv.request.name.c_str(),srv.request.age,srv.request.sex);
person_client.call(srv);
//show the response
ROS_INFO("Result:%s",srv.response.result.c_str());
return 0;
}
5.编写Server源码
/*
/show_person server
ServiceDataType is learning_service::Person
*/
#include<ros/ros.h>
#include"learning_service/Person.h"
//Service CALLBACKFUNCTION
bool PersonCallBackFunction(learning_service::Person::Request &req,learning_service::Person::Response &res)
{
//show request data
ROS_INFO("Person name:%s age:%d sex:%d",req.name.c_str(),req.age,req.sex);
//set response data
res.result="response:OK";
return true;
}
int main(int argc,char** argv)
{
//ROSINIT
ros::init(argc,argv,"person_server");
//Create NodeHandle
ros::NodeHandle n;
//Create a server /show_person
//register the CALLBACKFUNCTION
//Server provide the Service /show_person
//when server receive the request from Client
//Server will call the CBF
ros::ServiceServer person_service= n.advertiseService("/show_person",PersonCallBackFunction);
//waiting for request and call the CBF
ROS_INFO("READY TO SHOW PERSON INFORMATION");
ros::spin();
return 0;
}
6.修改CMakeLists.txt
添加依赖
add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${
catkin_LIBRARIES})
add_dependencies(person_server ${
PROJECT_NAME}_gencpp)
add_executable(person_client src/person_client.cpp)
target_link_libraries(person_client ${
catkin_LIBRARIES})
add_dependencies(person_client ${
PROJECT_NAME}_gencpp)
7.测试
roscore
尝试调换服务器和客户端的启动顺序
当客户端先启动时,客户端无法找到服务,就会等待服务
rosrun learning_service person_client
客户端Client反馈信息:
[ INFO] [1647790441.760583113]: waitForService: Service [/show_person]
has not been advertised, waiting…
rosrun learning_service person_server
服务器Server提示消息:
[ INFO] [1647790528.243310236]: READY TO SHOW PERSON INFORMATION
此时客户端Client提示与反馈消息:
[ INFO] [1647790528.250326397]: waitForService: Service [/show_person]
is now available. [ INFO] [1647790528.250425869]: Call service to show
person[name:Herman123 age:20 sex:1] [ INFO] [1647790528.258398762]:
Result:response:OK
服务器Server消息:
[ INFO] [1647790528.257503152]: Person name:Herman123 age:20 sex:1