inference_node 在启动时会注册 10 个 std_srvs::srv::Trigger 类型的服务,全部挂载在根命名空间下,服务名分别为 /init_motors、/deinit_motors、/start_inference、/stop_inference、/reset_joints、/set_zeros、/clear_errors、/refresh_joints、/read_joints 与 /read_imu。这些接口将底层 RobotInterface 的硬件操作与推理生命周期管理暴露为标准的 ROS2 服务,开发者既可以通过命令行快速调试,也可以在自己的节点中以编程方式异步调用。
Sources: inference_node.hpp
服务接口架构
下面的架构图展示了 inference_node 作为服务提供方与外部调用者之间的交互关系。所有服务均通过 ROS2 内部机制分发,调用方无需关心底层 CAN/串口细节。
graph LR
A[CLI / 外部节点] -->|ros2 service call| B(inference_node)
B --> C[/init_motors]
B --> D[/deinit_motors]
B --> E[/start_inference]
B --> F[/stop_inference]
B --> G[/reset_joints]
B --> H[/set_zeros]
B --> I[/clear_errors]
B --> J[/refresh_joints]
B --> K[/read_joints]
B --> L[/read_imu]
Sources: inference_node.hpp
服务列表与前置条件
各服务的功能简述与前置条件如下表所示。由于接口统一使用 std_srvs::srv::Trigger,请求体为空,调用方只需关注服务名称与返回状态。
| 服务名 | 功能说明 | 前置条件 | 典型成功返回 |
|---|---|---|---|
/init_motors |
初始化所有电机并进入可控状态 | 电机未初始化 | Motors initialized successfully |
/deinit_motors |
关闭电机驱动并释放资源 | 电机已初始化 | Motors deinitialized successfully |
/start_inference |
启动策略推理循环 | 推理未运行 | Inference started |
/stop_inference |
暂停策略推理循环 | 推理正在运行 | Inference stopped |
/reset_joints |
将所有关节复位到默认角度 | 推理已停止、电机已初始化 | Joints reset successfully |
/set_zeros |
将当前姿态写入电机零点 | 推理已停止、电机已初始化 | Zeros set successfully |
/clear_errors |
清除电机错误状态 | RobotInterface 已创建 |
Errors cleared successfully |
/refresh_joints |
刷新关节状态(重新使能或同步) | 电机已初始化 | Motors refresh successfully |
/read_joints |
立即读取并发布关节状态到 /joint_states |
电机已初始化 | Joints read successfully |
/read_imu |
立即读取并发布 IMU 数据到 /imu |
RobotInterface 已创建 |
IMU read successfully |
Sources: ros_interface.cpp
调用方式对比
ROS2 服务支持命令行与编程两种调用方式。命令行适合单次调试,而编程客户端适合集成到自动化流程。下表对比了两种方式的适用场景。
| 调用方式 | 适用场景 | 关键命令 / API |
|---|---|---|
| CLI | 单次调试、快速验证 | ros2 service call /service_name std_srvs/srv/Trigger |
| Python 客户端 | 自定义节点、定时任务 | rclpy 异步客户端 call_async |
Sources: README_CN.md
命令行调用示例
通过 ROS2 命令行工具调用服务是最直接的调试手段。所有服务均采用相同的标准格式,仅需替换服务名称即可。下面给出完整的服务调用示例与对应返回说明。
# 初始化电机
ros2 service call /init_motors std_srvs/srv/Trigger
# 开始推理
ros2 service call /start_inference std_srvs/srv/Trigger
# 停止推理
ros2 service call /stop_inference std_srvs/srv/Trigger
# 去初始化电机
ros2 service call /deinit_motors std_srvs/srv/Trigger
Sources: README_CN.md
Python 客户端示例
对于需要在自定义节点中集成服务调用的开发者,可使用 rclpy 编写异步客户端。以下示例展示了如何调用 /start_inference 并处理返回结果。由于 inference_node 中的服务类型均为 std_srvs::srv::Trigger,客户端代码可复用于任意服务,只需修改话题名。
import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
class ServiceCaller(Node):
def __init__(self):
super().__init__('service_caller')
self.client = self.create_client(Trigger, 'start_inference')
while not self.client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('等待服务上线...')
self.req = Trigger.Request()
def send_request(self):
self.future = self.client.call_async(self.req)
rclpy.spin_until_future_complete(self, self.future)
response = self.future.result()
if response.success:
self.get_logger().info(f'调用成功: {response.message}')
else:
self.get_logger().warn(f'调用失败: {response.message}')
def main():
rclpy.init()
node = ServiceCaller()
node.send_request()
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Sources: inference_node.hpp
典型操作流程
实际运行机器人时,服务调用需要遵循特定的时序,以避免状态冲突。必须先初始化电机才能启动推理,而复位关节或标定零点则要求在推理停止后进行。
启动并运行推理
启动并运行推理的标准流程如下:
flowchart TD
A[启动 inference_node] --> B[调用 /init_motors]
B -->|success: true| C[调用 /reset_joints]
C -->|success: true| D[调用 /start_inference]
D -->|success: true| E[机器人进入推理控制模式]
B -->|success: false| F[检查硬件连接与错误日志]
C -->|success: false| F
Sources: ros_interface.cpp
安全下电
安全下电或暂停调试时,建议先停止推理再去初始化电机,防止突然掉电导致关节失控。
flowchart TD
A[调用 /stop_inference] -->|success: true| B[调用 /deinit_motors]
B -->|success: true| C[电机下电完成]
A -->|success: false| D[推理已处于停止状态]
Sources: ros_interface.cpp
零点标定
零点标定是一个对时序要求严格的操作。服务层面的完整流程为:初始化电机、复位关节到默认角度、将机器人手动摆放到目标零位、调用 /set_zeros 写入零点、最后去初始化电机。详细的机械操作说明与阻尼模式控制请参考 电机零点标定。
flowchart TD
A[调用 /init_motors] --> B[调用 /reset_joints]
B --> C[人工摆放机器人至目标零位]
C --> D[调用 /set_zeros]
D -->|success: true| E[调用 /deinit_motors]
D -->|success: false| F[检查推理是否已停止]
Sources: ros_interface.cpp
返回值与异常排查
当服务返回 success: false 时,message 字段会给出明确的拒绝原因。下表汇总了常见错误及其含义,便于快速定位问题。
| 错误提示 | 触发服务 | 原因与修复 |
|---|---|---|
Inference is running, cannot reset joints. |
/reset_joints, /set_zeros |
推理循环正在运行,需先调用 /stop_inference |
Motors are not initialized... |
/reset_joints, /set_zeros, /refresh_joints, /read_joints, /deinit_motors |
电机尚未初始化,需先调用 /init_motors |
Motors are already initialized... |
/init_motors |
电机已处于使能状态,无需重复初始化 |
Inference is already running! |
/start_inference |
推理已在运行,重复调用无效 |
Inference is already stopped! |
/stop_inference |
推理已停止,重复调用无效 |
Robot interface is not initialized... |
/clear_errors, /read_imu |
RobotInterface 实例未就绪,检查节点启动日志 |
Sources: ros_interface.cpp
注意事项与边界条件
所有服务回调均在 inference_node 的主执行器线程中同步处理,因此单个服务的耗时操作(如电机初始化)会短暂阻塞同一节点的其他回调。开发者在设计外部调用逻辑时,应避免高频并发请求,并始终检查返回值的 success 标志。此外,/set_zeros 会直接将当前关节位置写入电机零偏寄存器,调用前务必确认机器人已稳定处于目标姿态,否则会导致后续控制基准错误。
Sources: ros_interface.cpp, inference_node.hpp
延伸阅读与下一步
完成服务接口的学习后,建议结合 启动机器人与推理节点 中的启动脚本实际运行节点,因为所有服务都依赖于 inference_node 的正常启动。节点通过 inference.launch.py 加载配置并注册服务;若希望使用手柄完成相同操作,请参考 手柄控制说明。如需了解底层硬件抽象,可阅读 RobotInterface 硬件抽象层;若希望通过 Python 直接绑定硬件而非走 ROS2 服务,请查看 Python SDK 快速上手。