上一篇文章概括了ROS的通信机制以及介绍了话题通信,接下来继续完成整个ROS通信机制的搭建,包括服务通信和参数服务器,在话题通信中用C++来创建节点文件,接下来将用python来创建文件,以熟悉两种语言的执行方式。
服务通信
理论模型
在 ROS (Robot Operating System) 中,服务 (Service) 是一种同步通信机制,它允许节点之间进行请求-响应式的交互。这种通信方式的理论模型基于客户端和服务端的交互,具体包括以下几个关键部分:
-
服务端 (Service Server):
- 服务端是提供服务的节点。它通常会在某个节点内部等待并监听来自客户端的请求。服务端会根据接收到的请求执行相应的操作,然后将结果作为响应返回给客户端。
- 服务端通过 ROS 提供的
ros::ServiceServer
或 Python 的rospy.Service
接口来实现。服务端首先需要定义一个服务类型,这个类型包括请求消息 (Request) 和响应消息 (Response)。
-
客户端 (Service Client):
- 客户端是请求服务的节点,它通过发送请求消息给服务端,并等待服务端返回响应消息。客户端在得到服务端的响应后,可以继续处理后续任务。
- 客户端通过 ROS 提供的
ros::ServiceClient
或 Python 的rospy.ServiceProxy
接口来实现。客户端发送一个请求消息,ROS 会在后台处理与服务端的通信,直到服务端完成操作并返回响应。
-
服务类型 (Service Type):
- 服务类型定义了请求消息和响应消息的结构。每个服务都有一个请求和响应部分,这些部分通过 ROS 消息类型来定义。请求和响应的消息格式是 ROS 中标准化的,通常是通过
.srv
文件来定义的。
- 服务类型定义了请求消息和响应消息的结构。每个服务都有一个请求和响应部分,这些部分通过 ROS 消息类型来定义。请求和响应的消息格式是 ROS 中标准化的,通常是通过
-
同步通信:
- 服务通信是同步的,这意味着在客户端发送请求后,客户端会等待服务端的响应。在等待期间,客户端的执行会被阻塞,直到服务端返回结果。
- 这种同步方式与 ROS 的消息发布-订阅机制(异步)不同,服务通信适用于那些需要请求某个计算并返回结果的场景。
-
工作流程:
- 服务端启动并注册服务。
- 客户端调用服务并发送请求。
- 服务端接收到请求后执行相应的操作并生成响应。
- 服务端将响应返回给客户端。
- 客户端接收到响应并继续后续操作。
客户端实现
1.导包
2.初始化 ros 节点
3.创建服务端对象
4.组织请求数据,发送请求
5.处理响应
代码示例:
import rospy
from server.srv import addintsRequest,addints
"""
1.导包
2.初始化ros节点
3.创建服务端对象
4.组织请求数据,发送请求
5.处理响应
"""
if __name__ == "__main__":
#初始化节点
rospy.init_node("jiedianming")
#给客户端对象赋值,告诉客户端话题名称,以及自创的srv文件
client = rospy.ServiceProxy("add",addints)
#发送请求,用call函数,发送srv中分割线上的数据,并接受响应(分割线下的)
response = client.call(12,45)
#处理响应
rospy.loginfo("response is %d",response.sum)
服务端实现
1.导包
2.初始化 ros 节点
3.创建服务端对象
4.处理逻辑(回调函数)
5.spin()(回头)
示例代码:
import rospy
from server.srv import addintsResponse,addints
#from server.srv import *
"""
1.导包
2.初始化ros节点
3.创建服务端对象
4.处理逻辑(回调函数)
5.spin()(回头)
"""
def donum(request):
num1 = request.num1
num2 = request.num2
sum = num1 + num2
response = addintsResponse()
response.sum = sum
rospy.loginfo("num1 = %d, num2 = %d, sum = %d ",num1,num2,sum)
return response
if __name__ == "__main__":
# 2. 初始化节点
rospy.init_node("pppp")
# 3. 创建服务端
server = rospy.Service("add",addints,donum)#服务话题 服务类型 回调函数
# 4. 处理逻辑(回调)
# 5. spin()
rospy.spin()
运行服务
当创建完节点后我们将启动服务
-
在 scripts 目录下打开终端
chmod +x *.py//获取可执行权限
-
CMakelist:
catkin_install_python(PROGRAMS scripts/server01_p.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} )
(这段代码的功能是,当你执行 catkin_make
或 catkin build
时,它会自动将 scripts
文件夹中的 Python 脚本(例如 demo01.py
)复制到 ROS 包的 bin
目录中,这样脚本就可以像其他 ROS 可执行文件一样被调用。)
自定义srv
自定义srv创建方式和自定义msg的操作类似,可以看我上一篇文章,了解自定义msg创建过程
示例文件:
#客户端请求,传递到服务端的回调函数
int32 num1
int32 num2
---
#服务器响应
int32 sum
参数服务器
增与改
C++:
`#include "ros/ros.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"paramset");
ros::NodeHandle nh;
//方案1:句柄
nh.setParam("name","xiaoming");
nh.setParam("radius",0.15);
//方案2:参数对象
ros::param::set("name","xiaoming");
ros::param::set("radius",0.15);
//修改
//方案1:句柄
nh.setParam("name","xiaoming");
nh.setParam("radius",0.155);
//方案2:参数对象
ros::param::set("name","xiaoming");
ros::param::set("radius",0.175);
return 0;
}
python:
import rospy
if __name__ == "__main__":
rospy.init_node("parameter")
rospy.set_param("type","cheche")
查
在 ROS 中,ros::NodeHandle
提供了多种方法用于与参数服务器交互,这些方法允许获取、设置、检查和搜索参数。下面是你提到的几种常见的 NodeHandle
参数操作函数的详细介绍:
nh.param
函数
nh.param
用于获取或设置一个参数的值。如果该参数不存在,且你为其指定了默认值,那么它会返回默认值。
语法:
bool ros::NodeHandle::param(const std::string& param_name, T& param, const T& default_value) const;
param_name
:需要获取或设置的参数的名称。param
:变量,用于存储从参数服务器获取的值。default_value
:如果参数在服务器中不存在,返回的默认值。
示例:
int loop_rate;
nh.param("loop_rate", loop_rate, 10); // 如果 "loop_rate" 参数不存在,则默认值为 10
`
nh.getParam
函数
nh.getParam
用于从参数服务器获取指定名称的参数。如果参数不存在,它会返回false
,并且参数变量不会被修改。
语法:
bool ros::NodeHandle::getParam(const std::string& param_name, T& param) const;
param_name
:需要获取的参数的名称。param
:用于存储从参数服务器获取的值。
示例:
int loop_rate;
if (nh.getParam("loop_rate", loop_rate)) {
ROS_INFO("Loop rate is: %d", loop_rate);
} else {
ROS_WARN("Parameter 'loop_rate' not found!");
}
nh.paramNames
函数
nh.paramNames
返回当前节点的所有参数名称的列表。你可以用它来获取所有已设置的参数的名称。
语法:
std::vector<std::string> ros::NodeHandle::getParamNames() const;
- 返回值:一个包含所有参数名称的
std::vector<std::string>
。
示例:
std::vector<std::string> param_names = nh.getParamNames();
for (const auto& param_name : param_names) {
ROS_INFO("Parameter: %s", param_name.c_str());
}
nh.hasParam
函数
nh.hasParam
用于检查某个参数是否存在于参数服务器上。如果参数存在,它返回true
,否则返回false
。
语法:
bool ros::NodeHandle::hasParam(const std::string& param_name) const;
param_name
:需要检查的参数名称。- 返回值:如果参数存在,返回
true
;如果参数不存在,返回false
。
示例:
if (nh.hasParam("loop_rate")) {
ROS_INFO("Parameter 'loop_rate' exists.");
} else {
ROS_WARN("Parameter 'loop_rate' does not exist.");
}
nh.searchParam
函数
nh.searchParam
用于在父级节点和当前节点中查找给定的参数名称。如果找到了,返回该参数的完整名称。
语法:
std::string ros::NodeHandle::searchParam(const std::string& param_name) const;
param_name
:需要查找的参数名称。- 返回值:参数的完整名称(如果找到),否则返回空字符串。
示例:
std::string full_param_name = nh.searchParam("loop_rate");
if (!full_param_name.empty()) {
ROS_INFO("Found parameter '%s' with full name: %s", "loop_rate", full_param_name.c_str());
}
else {
ROS_WARN("Parameter 'loop_rate' not found.");
}
`
总结:
nh.param
:获取参数并设置默认值。nh.getParam
:从参数服务器获取参数,失败时返回false
。nh.getParamNames
:返回所有参数名称的列表。nh.hasParam
:检查某个参数是否存在。nh.searchParam
:在父节点和当前节点中查找某个参数的完整名称。
键
在ROS中,函数中的“键”通常是指在函数调用或参数传递中使用的“键值对”(key-value pair)。这些键通常指代某些参数的名称,而对应的值则是参数的具体内容。
在ROS中,许多函数或方法都使用参数字典(如ros::param
)来传递配置信息。例如,节点启动时可以从参数服务器中获取值,这些值会以“键-值”对的方式组织,以下是一些常见的例子:
-
ROS参数服务器:可以通过键(key)来获取或设置参数(value)。
- 例如:
ros::param::get("key_name", value);
其中"key_name"
是键,value
是存储对应数据的变量。
- 例如:
-
发布者和订阅者:在创建发布者和订阅者时,也可能传递一些配置参数,例如队列大小、消息类型等,这些可以看作是函数调用中的“键”。
- 例如:
ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name", 1000);
这里"topic_name"
可以视为一个键。
- 例如:
在 ROS 中,“键” 主要是用来标识和获取相关的参数或配置项的标识符。