动手学ROS2-2节点通信-服务之RCLCPP实现

说明:本文基于ubuntu22.04和ros2 humble版本

一、服务基础概念

1.服务通信介绍

服务分为客户端和服务端,平时我们用的手机APP都可以成为客户端,而APP服务器对于软件来说就是服务端。

客户端发送请求给服务端,服务端可以根据客户端的请求做一些处理,然后返回结果给客户端。

服务-客户端模型(请求-响应模型)

 服务和话题的不同之处,话题是没有返回的,适用于单向或大量的数据传递。而服务是双向的,客户端发送请求,服务端响应请求。

服务的一些注意事项:

  • 同一个服务(名称相同)有且只能有一个节点来提供
  • 同一个服务可以被多个客户端调用

2.服务使用样例

启动服务端

打开终端,运行下面的命令,打开一个服务节点

这个服务的功能是将两个数字相加,给定a,b两个数,返回sum也就是ab之和。

ros2 run examples_rclpy_minimal_service service

查看服务列表

ros2 service list

 手动调用服务

在新开启的终端中输入下面的命令(注意a:、b:后的空格)

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"

3.服务常用命令

#查看服务列表

ros2 service list

#手动调用服务

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"

#查看服务接口类型

ros2 service type /add_two_ints

#查找使用某一接口的服务

ros2 service find example_interfaces/srv/AddTwoInts

二、服务之RCLCPP实现

因为还没有学习如何自定义接口,所以我们先借着上一节的两数相加的示例接口,利用rclcpp提供的接口实现两数相加的服务端和客户端。

1.创建功能包和节点

在工作空间,源文件夹下打开终端,运行下面指令

ros2 pkg create example_service_rclcpp --build-type ament_cmake --dependencies rclcpp touch example_service_rclcpp/src/service_server_01.cpp

touch example_service_rclcpp/src/service_client_01.cpp

编写源文件,以面向对象方式创建两个节点

service_server_01.cpp

#include "rclcpp/rclcpp.hpp"

class ServiceServer01 : public rclcpp::Node {
public:
  ServiceServer01(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
  }

private:
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = std::make_shared<ServiceServer01>("service_server_01");
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

service_client_01.cpp

#include "rclcpp/rclcpp.hpp"

class ServiceClient01 : public rclcpp::Node {
public:
  // 构造函数,有一个参数为节点名称
  ServiceClient01(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
  }
private:
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  /*创建对应节点的共享指针对象*/
  auto node = std::make_shared<ServiceClient01>("service_client_01");
  /* 运行节点,并检测退出信号*/
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt

add_executable(service_client_01 src/service_client_01.cpp)
ament_target_dependencies(service_client_01 rclcpp)

add_executable(service_server_01 src/service_server_01.cpp)
ament_target_dependencies(service_server_01 rclcpp)

install(TARGETS
  service_server_01
  DESTINATION lib/${PROJECT_NAME}
)

install(TARGETS
  service_client_01
  DESTINATION lib/${PROJECT_NAME}
)

在工作空间目录下,进行编译测试

colcon build --packages-select example_service_rclcpp

# 运行 service_server_01

source install/setup.bash

ros2 run example_service_rclcpp service_server_01

# 打开新终端运行 service_client_01

source install/setup.bash

ros2 run example_service_rclcpp service_client_01

2.服务端实现

2.1导入接口

两数相加我们需要利用ROS2自带的example_interfaces接口,使用命令行可以查看这个接口的定义

ros2 interface show example_interfaces/srv/AddTwoInts

ament_cmake类型功能包导入消息接口分为三步:

  1. CMakeLists.txt中导入,具体是先find_packagesament_target_dependencies
  2. packages.xml中导入,具体是添加depend标签并将消息接口写入。
  3. 在代码中导入,C++中是#include"消息功能包/xxx/xxx.hpp"

CMakeLists.txt

# 这里我们一次性把服务端和客户端对example_interfaces的依赖都加上
find_package(example_interfaces REQUIRED)

add_executable(service_client_01 src/service_client_01.cpp)
ament_target_dependencies(service_client_01 rclcpp example_interfaces)

add_executable(service_server_01 src/service_server_01.cpp)
ament_target_dependencies(service_server_01 rclcpp example_interfaces)

packages.xml

<depend>example_interfaces</depend>

代码源文件

#include "example_interfaces/srv/add_two_ints.hpp"

2.2编写代码 

#include "example_interfaces/srv/add_two_ints.hpp"
#include "rclcpp/rclcpp.hpp"

class ServiceServer01 : public rclcpp::Node {
public:
    ServiceServer01(std::string name) : Node(name) 
  {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
    // 创建服务
    add_ints_server_ =
      this->create_service<example_interfaces::srv::AddTwoInts>(
        "add_two_ints_srv", std::bind(&ServiceServer01::handle_add_two_ints, 
               this, std::placeholders::_1, std::placeholders::_2));
  }

private:
  // 声明一个服务
  rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr
    add_ints_server_;

  // 收到请求的处理函数
  void handle_add_two_ints(
    const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
    std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response) 
  {
    RCLCPP_INFO(this->get_logger(), "收到a: %ld b: %ld", request->a,
                request->b);
    response->sum = request->a + request->b;
  };
};

create_service,参考rclcpp API文档

 

  • ServiceT,消息接口example_interfaces::srv::AddTwoInts
  • service_name,服务名称
  • callback,回调函数,使用成员函数作为回调函数,std::bind进行转换
  • qos_profile,服务质量配置文件,默认rmw_qos_profile_services_default
  • group,调用服务的回调组,默认nullptr

2.3在工作空间目录下编译测试

colcon build --packages-select example_service_rclcpp

source install/setup.bash

ros2 run example_service_rclcpp service_server_01

打开新的终端

# 查看声明的服务

ros2 service list

# 使用命令行进行服务调用

ros2 service call /add_two_ints_srv example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"

3.客户端实现

3.1API接口

 

 函数async_send_request()同时传入两个参数

  • request,请求的消息,这里用于放a,b两个数。
  • CallBack,回调函数,异步接收服务器的返回的函数。

 这个函数是用于等待服务上线的,这个函数并不在rclcpp::Client中定义,而是在其父类中定义的。

参数就一个,等待的时间,返回值是bool类型的,上线了就是true,不上线就是false。

之所以会用的这个函数的原因是,再发送请求之前保证服务端启动了,避免发送一个请求出去而无人响应的尴尬局面。

3.2代码

#include "example_interfaces/srv/add_two_ints.hpp"

class ServiceClient01 : public rclcpp::Node {
public:
  // 构造函数,有一个参数为节点名称
  ServiceClient01(std::string name) : Node(name) 
 {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
    // 创建客户端
    client_ = this->create_client<example_interfaces::srv::AddTwoInts> 
    ("add_two_ints_srv");
  }

  void send_request(int a, int b) {
    RCLCPP_INFO(this->get_logger(), "计算%d+%d", a, b);

    // 1.等待服务端上线
    while (!client_->wait_for_service(std::chrono::seconds(1))) 
    {
      //等待时检测rclcpp的状态
      if (!rclcpp::ok()) 
      {
        RCLCPP_ERROR(this->get_logger(), "等待服务的过程中被打断...");
        return;
      }
      RCLCPP_INFO(this->get_logger(), "等待服务端上线中");
    }

    // 2.构造请求的
    auto request =
      std::make_shared<example_interfaces::srv::AddTwoInts_Request>();
    request->a = a;
    request->b = b;

    // 3.发送异步请求,然后等待返回,返回时调用回调函数
    client_->async_send_request(
      request, std::bind(&ServiceClient01::result_callback_, this,
                         std::placeholders::_1));
  };

private:
  // 声明客户端
  rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;

  void result_callback_(
    rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture
      result_future) 
  {
    auto response = result_future.get();
    RCLCPP_INFO(this->get_logger(), "计算结果:%ld", response->sum);
  }
};


int main(int argc, char** argv) 
{
  rclcpp::init(argc, argv);
  /*创建对应节点的共享指针对象*/
  auto node = std::make_shared<ServiceClient01>("service_client_01");
  //增加这一行,node->send_request(5, 6);,计算5+6结果
  node->send_request(5, 6);
  /* 运行节点,并检测退出信号*/  
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

void result_callback_(  )

回调函数的参数是客户端AddTwoInts类型的SharedFuture对象,定义如下

 利用C++11的新特性std::shared_future创建的SharedResponse类模板。

类模板 std::shared_future 提供访问异步操作结果的机制,使用get函数即可获取结果。

3.3服务测试

在工作空间目录下开启终端

colcon build --packages-select example_service_rclcpp

source install/setup.bash

ros2 run example_service_rclcpp service_client_01

#开启新的终端

source install/setup.bash

ros2 run example_service_rclcpp service_server_01

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ROS2中,C++节点通信主要通过ROS2提供的rclcpp实现rclcpp库是一个ROS2的C++客户端库,它提供了创建ROS2节点、发布和订阅话题、服务调用等功能。 下面是一个简单的例子,演示如何使用rclcpp库在C++节点之间进行通信: 1. 首先,我们需要包含必要的头文件: ```cpp #include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" ``` 2. 创建一个发布者: ```cpp auto publisher = node->create_publisher<std_msgs::msg::String>("my_topic", 10); ``` 其中,`node`是一个`rclcpp::Node`对象,`std_msgs::msg::String`是消息类型,`my_topic`是话题名称,`10`是话题队列长度。 3. 创建一个消息: ```cpp auto message = std_msgs::msg::String(); message.data = "Hello, world!"; ``` 4. 发布消息: ```cpp publisher->publish(message); ``` 5. 创建一个订阅者: ```cpp auto subscription = node->create_subscription<std_msgs::msg::String>("my_topic", 10, [](const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "I heard: '%s'", msg->data.c_str()); }); ``` 其中,`std_msgs::msg::String`是消息类型,`my_topic`是话题名称,`10`是话题队列长度,`[](const std_msgs::msg::String::SharedPtr msg) {...}`是消息回调函数,用于处理接收到的消息。 6. 运行节点: ```cpp rclcpp::spin(node); ``` 以上是一个简单的例子,演示了如何在C++节点之间使用ROS2进行通信。在实际应用中,我们可以根据需要创建多个发布者和订阅者,以实现节点之间的复杂通信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值