ROS 6:服务端客户端实现
一、客户端实现
实现目标:使用客户端发送一个请求,请求turtlesim的spawn服务来新建一直小海龟,并给定其初始化的坐标位置。发送requext的代码是阻塞式的,需要等到response才会继续执行后续的语句。
创建一个客户端的流程大体为:
graph TB a(初始化节点) a -->b(创建client实例) b -->c(发布服务请求数据) c -->d(等待server处理之后的应答结果)
首先,按照惯例,我们创建一个功能包:
cd ~/ros_ws/src
catkin_create_pkg learn_server roscp rospy std_msgs geometry_msgs turtlesim
在功能包的src底下创建第一个cpp文件
c++实现
代码实现
一、导入头文件,初始化节点并创建句柄。
由于本次需要使用turtlesim的spawn服务,所以需要导入对应的头文件:
#include <ros/ros.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv)
{
ros::init(argc, argv, "turtle_spawn");
ros::NodeHandle node;
二、然后就是需要客户端查询是否存在spawn服务,如果没有就会阻塞在这一句代码一直等待。
如果spawn服务出现了,就创建一个客户端,用来给spawn服务发送请求的,请求的数据类型是turtlesim::spawn
。
// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
ros::service::waitForService("/spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
//尖括号存放数据类型,后面跟上请求服务的名字
三、封装好请求数据。/spawn里需要存储以下类型的数据,初始化好预设值。
// 初始化turtlesim::Spawn的请求数据
turtlesim::Spawn srv;
srv.request.x = 2.0;
srv.request.y = 2.0;
srv.request.name = "turtle2";
四、发送请求数据。使用call方法,将封装好的请求数据发送出去。
call也是阻塞性的函数,在数据发布出去之后,会一直等待服务器的反馈数据。获取到服务器的反馈之后1,才会去执行下一句代码。
由于在turtlesim成功调用服务之后,会返回这只turtle的名字,所以我们需要获取返回的turtle名。(可见ROS二:基本命令行工具)
// 请求服务调用
ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]", srv.request.x, srv.request.y, srv.request.name.c_str());
add_turtle.call(srv);
// 显示服务调用结果
ROS_INFO("Spwan turtle successfully [name:%s]", srv.response.name.c_str());
return 0;
配置编译文件
在CMakeLists.txt输入以下文本,添家要编译的文件以及链接库。
add_executable(turtle_client src/turtle_client.cpp)
target_link_libraries(turtle_client ${catkin_LIBRARIES})
运行结果
[ INFO] [1658379402.461341999]: Call service to spwan turtle[x:2.000000, y:2.000000, theta:2.000000,name:turtle2]
[ INFO] [1658379402.512673909]: Spwan turtle successfully [name:turtle2]
可以在turtlesim_node界面看到新建海龟成功的日志信息
[ INFO] [1658379402.512145247]: Spawning turtle [turtle2] at x=[2.000000], y=[2.000000], theta=[2.000000]
以及在图形界面看到新生成的海龟。
python实现
# -*- coding: utf-8 -*-
import rospy
import sys
from turtlesim.srv import Spawn
def turtle_client():
rospy.init_node('turtle_client')
# 发现/spawn服务后,创建一个客户端,连接名为/spawn的service
rospy.wait_for_service('/spawn')
try:
# 创建客户端
add_turtle = rospy.ServiceProxy('/spawn', Spawn)
# 请求服务调用,输入请求数据
# 请求数据,类似cpp的call方法。response获取返回的turtle名字
response = add_turtle(2.0, 2.0, 2.0, "turtle2")
return response.name
except rospy.ServiceException, e:
print "Service call failed: %s"%e
return 0
if __name__ == "__main__":
print "Spwan turtle successfully [name:%s]" %(turtle_client())
二、服务端实现
实现目标:给海龟发速度指令。服务端通过获取客户端的指令控制海龟的运动与否。
服务端实现的大致流程为:
graph TB a(初始化节点) a -->b(创建server) b -->c(循环等待服务请求) c -->d(获取到请求 进入回调函数) d -->e(在回调函数中完成服务功能 并反馈应答数据)
c++实现
我们如果想要知道Trigger里面包含什么类型,可以用rossrv show
指令查看。
rossrv show std_srvs/Trigger
---
bool success
string message
由于service包含有request和response两个部分,三横线 "—"以上是request的结构内容,以下是response的内容。
可以看到Trigger的response结构里包含有一个布尔的flag,以及字符串类型的消息。
代码实现
导入头文件
#include <ros/ros.h>
#include <geometry_msgs/Twist.h> //topic数据类型
#include <std_srvs/Trigger.h> //service数据类型
声明publisher等一些全局变量:
ros::Publisher turtle_pub; //创建全局的publisher
bool pub_turtle_sport_flag = false; //turtle是否运动的flag标记
主函数
int main(int argc,char **argv){
ros::init(argc,argv,"turtle_server");
ros::NodeHandle node;
// 自己起一个名为/turtle_command的server,注册回调函数CommandCallBack
ros::ServiceServer command_service = node.advertiseService("/turtle_command",CommandCallBack);
// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
turtle_pub = node.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);
// 设置循环的频率
ROS_INFO("Waiting to receive command");
ros::Rate loop_rate(10);
然后写循环,判断标志符并根据标志发布指令
while(ros::ok){
// 查看一次回调函数队列,看是否有服务数据传进来
ros::spinOnce();
// 如果标志为true,则发布速度指令
if(pub_turtle_sport_flag)
{
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = 0.5;
vel_msg.angular.z = 0.2;
turtle_pub.publish(vel_msg);
}
//按照循环频率延时
loop_rate.sleep();
}
主函数写完,写回调函数。
// service回调函数,输入参数req,输出参数res
bool CommandCallBack(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res)
{
pub_turtle_sport_flag = !pub_turtle_sport_flag;
// 显示请求数据
ROS_INFO("Publish command [%s]", pub_turtle_sport_flag==true?"Yes":"No");
// 设置反馈数据
res.success = true;
res.message = "Change turtle command state!";
return true;
}
配置编译文件
add_executable(turtle_server src/turtle_server.cpp)
target_link_libraries(turtle_server ${catkin_LIBRARIES})
运行结果
首先,按照惯例,运行roscore
,rosrun turtlesim turtlesim_node
,并使用rosrun <功能包名> <可执行文件名>
运行自己的代码,然后新建终端,运行以下指令,给自己的service发送空消息
rosservice call /turtle_command "{}"
每运行一次上述指令,布尔值的flag:pub_turtle_sport_flag
会自己取反,控制小乌龟是否按照topic运行。
python实现
由于python中并没有上述c++代码中ros::spinOnce();
的机制,只有不断循环的rospy.spin()
,所以在开发商存在一定的区别。
所以我们需要使用多线程机制进行开发。
载入头文件,并初始化全局变量:
# -*- coding: utf-8 -*-
import rospy
import thread,time
from geometry_msgs.msg import Twist
from std_srvs.srv import Trigger, TriggerResponse
pubCommand = False;
turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)
从主函数开始介绍
if __name__ == "__main__":
turtle_server()
首先是建立第一个函数,初始化节点以及创建server。
def turtle_server():
rospy.init_node('turtle_server_node')
# 创建一个名为/turtle_command的server,注册回调函数CommandCallback
ser_turtle = rospy.Service('/turtle_command', Trigger, CommandCallback)
# 循环等待回调函数
print "Ready to receive turtle command."
# 创建线程。第一个是线程函数,第二个是传给这个函数的参数。
# 由于我们不需要传参,后面的括号无需填写
thread.start_new_thread(turtle_thread, ())
rospy.spin()
众所周知,线程所在的起始位置是在某个函数的开头,所以为其封装个函数
def turtle_thread():
while True:
if pub_Command:
vel_msg = Twist()
vel_msg.linear.x = 0.8
vel_msg.angular.z = 0.5
turtle_vel_pub.publish(vel_msg)
time.sleep(0.1)
当全局变量flag获取到反馈的时候,就需要调用回调函数。所以我们为其封装个回调函数
def CommandCallback(req):
global pub_Command
pub_Command = bool(1-pub_Command)
# 显示请求
rospy.loginfo("Publish turtle command![%d]", pub_Command)
# 反馈数据
return TriggerResponse(True, "Change turtle command state!")
虽然实现的效果和c++是一样的,但是注意实现方式是很有区别的,一个是封装了ros::spinOnce();
,一个是使用多线程