本专栏旨在通过对ROS2的系统学习,掌握ROS2底层基本分布式原理,并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。
🚀详情:《ROS2从入门到精通》
服务是 ROS 图中节点之间的另一种通信方法。服务基于服务器-客户端模型,不同于话题的发布者-订阅者模型。话题允许节点订阅数据流并获取持续更新,而服务只在客户端特别调用时才提供数据。二者更详细的对比请参考第5节
实验目标:客户端提交请求给
turtlesim
功能包的/spawn
服务,在界面上生成新的乌龟。
服务器
本实验中无需编程,为turtlesim::Spawn
定义的/spwan
服务
客户端
void OnResultCallBack(rclcpp::Client<turtlesim::srv::Spawn>::SharedFuture result) {
auto response = result.get();
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Request service successfully! [turtle id: %s]", response->name.c_str());
}
void request() {
auto spawn = std::make_shared<turtlesim::srv::Spawn::Request>();
spawn->name = "winter_turtle";
spawn->x = 1.0;
spawn->y = 1.0;
spawn->theta = 1.57;
while (!client_->wait_for_service(std::chrono::seconds(1))) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
auto result = client_->async_send_request(spawn, std::bind(&ClientNode::OnResultCallBack, this, std::placeholders::_1));
}
服务通信的效果如下所示:
实验目标:客户端提交请求给
turtlesim
功能包的/spawn
服务,在界面上生成新的乌龟。
服务器
本实验中无需编程,为turtlesim::Spawn
定义的/spwan
服务
客户端
class ClientNode(Node):
def __init__(self, name):
super().__init__(name)
self.client = self.create_client(Spawn, '/spawn')
while not self.client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.request = Spawn.Request()
def sendRequest(self):
self.request.name = "winter_turtle"
self.request.x = 1.0
self.request.y = 1.0
self.request.theta = 1.57
self.future = self.client.call_async(self.request)
服务通信的效果如下所示:
自定义服务的通用流程如下:
- 功能包下新建
srv
文件夹,在其中添加自定义服务xxx.srv
,注意请求和响应数据结构使用---
分割- 功能包
package.xml
中添加编译依赖与执行依赖<buildtool_depend>rosidl_default_generatorsbuildtool_depend> <exec_depend>rosidl_default_runtimeexec_depend> <member_of_group>rosidl_interface_packagesmember_of_group>
- 1
- 2
- 3
- 功能包
CMakeLists.txt
中添加编译消息相关依赖find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "xxx.srv" DEPENDENCIES xxx_srvs ) ament_export_dependencies(rosidl_default_runtime)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 编译自定义消息,在
install/
中生成由/include xxx.srv
编译的C++可识别的xxx.hpp
头文件- 引入
xxx.hpp
即可调用自定义服务
下面给出一个实例
添加如下自定义服务实现一个加法服务,并按上面步骤配置依赖
# client
int32 a
int32 b
---
# server
int32 sum
定义一个服务器、一个客户端,限于篇幅只贴出部分代码,完整代码见文末。
class ServerNode : public rclcpp::Node
{
public:
ServerNode() : Node("lab_srv_server_own") {
server_ = create_service<own_srv_lab::srv::Add>(
"/add_service",
std::bind(&ServerNode::OnAddSrvCallBack, this, std::placeholders::_1, std::placeholders::_2)
);
}
private:
rclcpp::Service<own_srv_lab::srv::Add>::SharedPtr server_;
void OnAddSrvCallBack(
const std::shared_ptr<own_srv_lab::srv::Add::Request> request,
std::shared_ptr<own_srv_lab::srv::Add::Response> response
) {
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %d" " b: %d", request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%d]", response->sum);
}
};
ClientNode() : Node("lab_srv_client_own") {
client_ = create_client<own_srv_lab::srv::Add>("/add_service");
}
void request(int a, int b) {
auto add_srv = std::make_shared<own_srv_lab::srv::Add::Request>();
add_srv->a = a;
add_srv->b = b;
while (!client_->wait_for_service(std::chrono::seconds(1))) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
auto result = client_->async_send_request(add_srv, std::bind(&ClientNode::OnResultCallBack, this, std::placeholders::_1));
}
服务通信效果如下所示:
对比 | 话题 | 服务 |
---|---|---|
通信模式 | 发布-订阅 | 请求-响应 |
同步性 | 异步 | 同步 |
缓冲区 | 有 | 无 |
实时性 | 弱 | 强 |
节点关系 | 多对多 | 一对多(1个server对应一个服务) |
通信格式 | .msg | .srv |
使用场景 | 连续高频的数据传输,例如激光雷达、里程计传输数据 | 偶尔调用的功能,例如图像识别 |
完整代码通过下方博主名片联系获取
🔥 更多精彩专栏: