ROS2 中的服务就类似于 RPC 调用,每个节点提供服务,其它节点可以调用服务并获取返回值

服务查看

  1. 查看节点提供的服务

    1
    
    ros2 node info /turtlesim
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    Service Servers:
       /clear: std_srvs/srv/Empty
       /kill: turtlesim/srv/Kill
       /reset: std_srvs/srv/Empty
       /spawn: turtlesim/srv/Spawn
       /turtle1/set_pen: turtlesim/srv/SetPen
       /turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute
       /turtle1/teleport_relative: turtlesim/srv/TeleportRelative
       /turtlesim/describe_parameters: rcl_interfaces/srv/DescribeParameters
       /turtlesim/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
       /turtlesim/get_parameters: rcl_interfaces/srv/GetParameters
       /turtlesim/get_type_description: type_description_interfaces/srv/GetTypeDescription
       /turtlesim/list_parameters: rcl_interfaces/srv/ListParameters
       /turtlesim/set_parameters: rcl_interfaces/srv/SetParameters
       /turtlesim/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  2. 服务和话题一样,包括服务名和服务参数定义,可以查看服务的详细定义

    1
    
    ros2 service info /spawn
    1
    2
    3
    
    Type: turtlesim/srv/Spawn
    Clients count: 0
    Services count: 1
  3. 查看服务类型定义

    1
    
    ros2 interface show turtlesim/srv/Spawn
    1
    2
    3
    4
    5
    6
    
    float32 x
    float32 y
    float32 theta
    string name # Optional.  A unique name will be created and returned if this is empty
    ---
    string name

这个定义表示输入参数为 x, y, theta, name ,其中 name 是可选的,返回值为 name

  1. ros2 命令行调用服务
    1
    
    ros2 service call /spawn turtlesim/srv/Spawn "{x: 1.0, y: 1.0, theta: 0}"
    调用后的结果
    1
    2
    3
    4
    5
    
    waiting for service to become available...
    requester: making request: turtlesim.srv.Spawn_Request(x=1.0, y=1.0, theta=0.0, name='')
    
    response:
    turtlesim.srv.Spawn_Response(name='turtle2')

定义服务描述文件

  1. demo_interfaces 下的 srv 目录自定义一个服务描述文件,名称叫 AddInt.srv

    1
    2
    3
    4
    
    int64 a
    int64 b
    ---
    int64 sum
  2. CMakeLists.txt 中添加编译

    1
    2
    3
    4
    5
    
    rosidl_generate_interfaces(${PROJECT_NAME}
      "msg/Demo.msg"
      "srv/AddInt.srv"
      DEPENDENCIES std_msgs
    )
  3. 使用 colcon build 编译

C++ 节点实现一个服务

  1. 创建一个功能包

    1
    
    ros2 pkg create  cpp_server_demo --destination-directory src --build-type ament_cmake --dependencies rclcpp demo_interfaces --license Apache-2.0
  2. 写服务文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    #include "rclcpp/executors.hpp"
    #include "demo_interfaces/srv/add_int.hpp"
    
    void AddInt(demo_interfaces::srv::AddInt_Request::SharedPtr request,
                demo_interfaces::srv::AddInt_Response::SharedPtr response) {
        response->sum = request->a + request->b;
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
                    request->a, request->b);
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
    }
    
    int main(int argc, char* argv[]) {
        rclcpp::init(argc, argv);
    
        auto node = rclcpp::Node::make_shared("add_int_server");
        auto service = node->create_service<demo_interfaces::srv::AddInt>("add_int", AddInt);
    
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add ints.");
    
        rclcpp::spin(node);
    
        rclcpp::shutdown();
        return 0;
    }
  3. 编译完成后运行就可以看到对应服务了

C++ 实现一个节点调用服务

  1. 创建一个功能包

    1
    2
    
    ros2 pkg create  cpp_client_demo --destination-directory src --build-type ament_cmake --dependencies rclcpp demo
    _interfaces --license Apache-2.0
  2. 客户端代码

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    #include "rclcpp/executors.hpp"
    #include "demo_interfaces/srv/add_int.hpp"
    
    using namespace std::chrono_literals;
    
    int main(int argc, char* argv[]) {
        rclcpp::init(argc, argv);
    
        auto node = rclcpp::Node::make_shared("add_client");
        auto client = node->create_client<demo_interfaces::srv::AddInt>("add_int");
        auto request = std::make_shared<demo_interfaces::srv::AddInt_Request>();
        request->a = 100;
        request->b = 200;
    
        while (!client->wait_for_service(1s)) {
            if (!rclcpp::ok()) {
                RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
                return 0;
        }
            RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
        }
    
        auto result = client->async_send_request(request);
        if (rclcpp::spin_until_future_complete(node, result) == rclcpp::FutureReturnCode::SUCCESS) {
            RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
        } else {
            RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
        }
        rclcpp::shutdown();
        return 0;
    }