🔌 第04课:服务(Service)

ROS2基础 ✅ Docker验证通过

📋 课程目标

🧠 服务通信模型

服务(Service)采用请求/响应(Request/Response)模式,是一种同步的、一对一的通信方式。客户端发送请求,服务端处理后返回响应。

Service Client Service Server ┌─────────────────┐ ┌─────────────────┐ │ │ Request ───────► │ │ │ send_request() │ │ callback() │ │ │ ◄────── Response │ │ │ 处理响应 │ │ 处理请求 │ └─────────────────┘ └─────────────────┘ 特点: ✅ 同步通信(请求-响应) ✅ 一对一(一个Client对一个Server) ✅ 适合触发式操作(开关、查询、配置) ❌ 不适合高频数据流 ❌ 不适合长时间任务(会阻塞)

📊 话题 vs 服务 对比

特性TopicService
通信模式发布/订阅请求/响应
方向单向双向
参与者多对多一对一
实时性异步,持续流同步,触发式
数据丢失可能丢失保证响应
适用场景传感器数据流开关、查询、配置
典型例子激光扫描数据触发拍照、获取状态

🐍 Python:服务端

#!/usr/bin/env python3
"""机器人控制服务端 - 提供运动控制接口"""

import rclpy
from rclpy.node import Node
from example_interfaces.srv import SetBool, Trigger
from std_srvs.srv import SetBool as StdSetBool
import math


class RobotControlServer(Node):
    """机器人控制服务端"""

    def __init__(self):
        super().__init__('robot_control_server')

        # 机器人状态
        self.is_enabled = False
        self.battery_level = 100.0
        self.total_distance = 0.0

        # 创建服务1:启用/禁用机器人
        self.enable_srv = self.create_service(
            SetBool, 'enable_robot', self.enable_callback
        )

        # 创建服务2:触发紧急停止
        self.estop_srv = self.create_service(
            Trigger, 'emergency_stop', self.estop_callback
        )

        # 创建服务3:获取状态
        # 使用自定义回调
        self.status_srv = self.create_service(
            Trigger, 'get_status', self.status_callback
        )

        # 模拟电池消耗
        self.battery_timer = self.create_timer(1.0, self.update_battery)

        self.get_logger().info('🤖 机器人控制服务端已启动')
        self.get_logger().info('  可用服务: enable_robot, emergency_stop, get_status')

    def enable_callback(self, request, response):
        """启用/禁用回调"""
        self.is_enabled = request.data
        response.success = True
        if request.data:
            response.message = '✅ 机器人已启用'
            self.get_logger().info('机器人已启用')
        else:
            response.message = '⏸️ 机器人已禁用'
            self.get_logger().info('机器人已禁用')
        return response

    def estop_callback(self, request, response):
        """紧急停止回调"""
        self.is_enabled = False
        response.success = True
        response.message = '🛑 紧急停止已触发!机器人已禁用'
        self.get_logger().error('紧急停止触发!')
        return response

    def status_callback(self, request, response):
        """状态查询回调"""
        state = '启用' if self.is_enabled else '禁用'
        response.success = True
        response.message = (
            f'状态: {state} | '
            f'电量: {self.battery_level:.1f}% | '
            f'总里程: {self.total_distance:.2f}m'
        )
        self.get_logger().info(f'状态查询: {response.message}')
        return response

    def update_battery(self):
        """模拟电池消耗"""
        if self.is_enabled:
            self.battery_level = max(0, self.battery_level - 0.5)
            self.total_distance += 0.3

            if self.battery_level < 10 and self.battery_level % 5 < 0.5:
                self.get_logger().warn(f'⚠️ 电量低: {self.battery_level:.0f}%')


def main(args=None):
    rclpy.init(args=args)
    node = RobotControlServer()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

🐍 Python:客户端

#!/usr/bin/env python3
"""机器人控制客户端 - 调用控制服务"""

import sys
import rclpy
from rclpy.node import Node
from example_interfaces.srv import SetBool
from std_srvs.srv import Trigger


class RobotControlClient(Node):
    """机器人控制客户端"""

    def __init__(self):
        super().__init__('robot_control_client')

        # 创建客户端
        self.enable_client = self.create_client(SetBool, 'enable_robot')
        self.estop_client = self.create_client(Trigger, 'emergency_stop')
        self.status_client = self.create_client(Trigger, 'get_status')

        # 等待服务可用
        self.get_logger().info('等待服务可用...')
        self.enable_client.wait_for_service(timeout_sec=5.0)
        self.status_client.wait_for_service(timeout_sec=5.0)
        self.get_logger().info('服务已就绪!')

    def send_enable_request(self, enable: bool):
        """发送启用/禁用请求"""
        request = SetBool.Request()
        request.data = enable

        self.get_logger().info(f'请求: {"启用" if enable else "禁用"}机器人...')
        future = self.enable_client.call_async(request)

        # 同步等待结果
        rclpy.spin_until_future_complete(self, future)
        return future.result()

    def send_estop_request(self):
        """发送紧急停止请求"""
        request = Trigger.Request()
        self.get_logger().warn('请求: 紧急停止!')
        future = self.estop_client.call_async(request)
        rclpy.spin_until_future_complete(self, future)
        return future.result()

    def send_status_request(self):
        """查询状态"""
        request = Trigger.Request()
        future = self.status_client.call_async(request)
        rclpy.spin_until_future_complete(self, future)
        return future.result()


def main(args=None):
    rclpy.init(args=args)
    client = RobotControlClient()

    # 示例操作序列
    print('\n===== 机器人控制演示 =====')

    # 1. 查询初始状态
    result = client.send_status_request()
    print(f'初始状态: {result.message}')

    # 2. 启用机器人
    result = client.send_enable_request(True)
    print(f'启用结果: {result.message}')

    # 3. 再次查询状态
    result = client.send_status_request()
    print(f'当前状态: {result.message}')

    # 4. 禁用机器人
    result = client.send_enable_request(False)
    print(f'禁用结果: {result.message}')

    # 5. 测试紧急停止
    result = client.send_estop_request()
    print(f'急停结果: {result.message}')

    client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

🔧 C++:服务端与客户端

// robot_control_server_cpp.cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/set_bool.hpp"
#include "std_srvs/srv/trigger.hpp"

#include <memory>
#include <string>

class RobotControlServerCpp : public rclcpp::Node {
public:
    RobotControlServerCpp() : Node("robot_control_server_cpp"),
        is_enabled_(false), battery_(100.0), distance_(0.0) {

        // 创建服务
        enable_srv_ = this->create_service<example_interfaces::srv::SetBool>(
            "enable_robot_cpp",
            std::bind(&RobotControlServerCpp::enable_callback, this,
                      std::placeholders::_1, std::placeholders::_2));

        status_srv_ = this->create_service<std_srvs::srv::Trigger>(
            "get_status_cpp",
            std::bind(&RobotControlServerCpp::status_callback, this,
                      std::placeholders::_1, std::placeholders::_2));

        RCLCPP_INFO(this->get_logger(), "C++机器人控制服务端已启动");
    }

private:
    void enable_callback(
        const std::shared_ptr<example_interfaces::srv::SetBool::Request> request,
        std::shared_ptr<example_interfaces::srv::SetBool::Response> response) {

        is_enabled_ = request->data;
        response->success = true;
        response->message = request->data ? "✅ 机器人已启用" : "⏸️ 机器人已禁用";
        RCLCPP_INFO(this->get_logger(), "%s", response->message.c_str());
    }

    void status_callback(
        const std::shared_ptr<std_srvs::srv::Trigger::Request>,
        std::shared_ptr<std_srvs::srv::Trigger::Response> response) {

        std::string state = is_enabled_ ? "启用" : "禁用";
        response->success = true;
        response->message = "状态: " + state +
            " | 电量: " + std::to_string(static_cast<int>(battery_)) + "%" +
            " | 里程: " + std::to_string(distance_).substr(0, 5) + "m";
        RCLCPP_INFO(this->get_logger(), "状态查询: %s", response->message.c_str());
    }

    rclcpp::Service<example_interfaces::srv::SetBool>::SharedPtr enable_srv_;
    rclcpp::Service<std_srvs::srv::Trigger>::SharedPtr status_srv_;
    bool is_enabled_;
    double battery_;
    double distance_;
};

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<RobotControlServerCpp>());
    rclcpp::shutdown();
    return 0;
}

📦 自定义服务类型

# my_interfaces/srv/ComputePath.srv
# Request(请求)
---
# Response(响应)
geometry_msgs/PoseStamped[] waypoints
float64 total_distance
float64 estimated_time
bool success
string message

# my_interfaces/srv/GetSensorData.srv
# Request
string sensor_name
int64 num_samples
---
# Response
float64[] samples
float64 mean
float64 std_dev
float64 min_val
float64 max_val
bool success
string error_message
💡 自定义服务文件的规则:请求和响应部分用 --- 分隔。每部分定义字段,使用基本类型或已有消息类型。构建后会自动生成Python和C++的类。

🔄 异步调用与回调

#!/usr/bin/env python3
"""异步服务调用示例"""

import rclpy
from rclpy.node import Node
from example_interfaces.srv import SetBool


class AsyncServiceClient(Node):
    def __init__(self):
        super().__init__('async_service_client')
        self.client = self.create_client(SetBool, 'enable_robot')
        self.client.wait_for_service()

    def call_async_with_callback(self, enable: bool):
        """异步调用,通过回调处理结果"""
        request = SetBool.Request()
        request.data = enable

        future = self.client.call_async(request)
        future.add_done_callback(self.response_callback)

    def response_callback(self, future):
        """异步响应回调"""
        try:
            response = future.result()
            self.get_logger().info(
                f'响应: success={response.success}, '
                f'message="{response.message}"'
            )
        except Exception as e:
            self.get_logger().error(f'服务调用失败: {e}')


def main(args=None):
    rclpy.init(args=args)
    client = AsyncServiceClient()

    # 异步调用不会阻塞
    client.call_async_with_callback(True)
    client.get_logger().info('请求已发送,等待响应...')

    # 继续spin等待回调
    rclpy.spin(client)
    client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

🖥️ 服务命令行工具

命令功能示例
ros2 service list列出所有服务ros2 service list
ros2 service type查看服务类型ros2 service type /enable_robot
ros2 service call调用服务ros2 service call /enable_robot example_interfaces/srv/SetBool "{data: true}"
ros2 service find查找服务类型ros2 service find example_interfaces/srv/SetBool
ros2 interface show查看接口定义ros2 interface show example_interfaces/srv/SetBool

⚙️ 服务最佳实践

✅ 推荐做法

❌ 避免做法

🎯 练习题

📝 练习1:计算器服务

创建一个计算器服务,支持加减乘除:

📝 练习2:图像捕获服务

创建一个图像捕获服务:

📝 练习3:服务链式调用

创建一个工作流:客户端先调用"初始化"服务,再调用"启动"服务,最后调用"停止"服务。每步依赖上一步的结果。

📝 练习4:命令行服务操作

# 启动服务端
ros2 run my_pkg robot_control_server

# 在命令行调用服务
ros2 service call /enable_robot example_interfaces/srv/SetBool "{data: true}"
ros2 service call /get_status std_srvs/srv/Trigger "{}"
ros2 service call /emergency_stop std_srvs/srv/Trigger "{}"

🏆 成就解锁

🏅 服务通信专家

经验值:+150 XP