- 机器人
- ROS
【免费下载链接】moveit2
:robot: MoveIt for ROS 2
导读
moveit_py是 MoveIt2 的 Python 接口包,其中的servo_client子模块(moveit_py/moveit/servo_client)为开发者提供了一个面向MoveIt Servo(实时伺服控制节点)的客户端实现,允许通过游戏手柄等输入设备直接操控机械臂末端执行器。阅读本文后,你将掌握:该模块如何与servo_node的 Twist 命令话题和启停服务对接、PS4 DualShock 手柄的轴/按键映射与速度指令生成逻辑,以及如何基于其抽象基类扩展自定义遥操作设备或用于学习型(learning-based)策略部署。
一、模块定位:MoveIt2 Python 世界里的遥操作入口
仓库中 moveit_py/moveit/servo_client/init.py 的模块文档给出了清晰定位:
A client providing access to the MoveIt Servo node. This module can be used to configure new devices for teleoperation. It is also possible to use this module for sending position or velocity commands to your robot which can be useful for learning based approaches.
即该模块承担两类职责:
- 遥操作:为不同输入设备(当前支持 PS4 DualShock 手柄)提供与 MoveIt Servo 交互的客户端;
- 指令注入:绕过手柄,直接向机器人发送位置或速度指令,可用于强化学习 / 示教学习等基于学习的方法。
从目录结构看,模块由两层组成:
- teleop.py:定义
TeleopDevice抽象基类,封装所有设备共用的 ROS 2 话题/服务对接逻辑; - devices/ps4_dualshock.py:PS4 DualShock 手柄的具体实现,含轴/按键映射数据类与手柄遥操作类。
此外,官方 API 文档入口 moveit_py/docs/source/index.rst 将moveit_py.servo_client与core、planning、policies一并列为顶层公开模块,说明它属于 MoveItPy 对外稳定提供的 API 面。
二、TeleopDevice:所有遥操作设备的抽象基类
teleop.py 中的TeleopDevice同时继承ABC与rclpy.node.Node,把「设备无关」的 ROS 2 基础设施集中到基类,具体设备只需实现两个抽象方法。
2.1 构造参数
def __init__(self, node_name, device_name, device_config, ee_frame_name): super().__init__(node_name=node_name) self.device_name = device_name self.device_config = device_config self.ee_frame_name = ee_frame_namenode_name:ROS 2 节点名;device_name/device_config:设备标识与配置对象(具体设备可传入自己的映射数据类);ee_frame_name:末端执行器坐标系名,写入TwistStamped.header.frame_id,用于告诉 servo 节点 Twist 指令参考哪个坐标系。
2.2 与 MoveIt Servo 的对接接口
基类在构造时创建了四条 ROS 2 通信链路(话题/服务名可在 servo_node.cpp 与 servo_parameters.yaml 中找到对应实现):
| 类型 | 名称 | 消息/服务类型 | 作用 |
|---|---|---|---|
| Subscriber | /joy | sensor_msgs/Joy | 订阅手柄原始输入,回调即publish_command |
| Publisher | /servo_node/delta_twist_cmds | geometry_msgs/TwistStamped | 向 servo 发送笛卡尔增量 Twist 指令 |
| Client | /servo_node/start_servo | std_srvs/Trigger | 启动伺服装载 |
| Client | /servo_node/stop_servo | std_srvs/Trigger | 停止伺服装载 |
其中 Twist 指令话题正是 servo 参数cartesian_command_in_topic(默认~/delta_twist_cmds)在servo_node节点命名下的实际解析结果,servo 侧通过 servo_node.cpp 的twist_subscriber_订阅该话题。
2.3 启动与停止遥操作
start_teleop()完成两件事:
- 等待并调用
start_servo服务:wait_for_service(10.0)最多等待 10 秒,随后call_async(Trigger.Request())异步触发; - 以多线程执行器后台旋转节点:显式使用
rclpy.executors.MultiThreadedExecutor并把节点加入其中,注释说明原因是"Servo has concurrent processes for moving the robot and avoiding collisions"——即 MoveIt Servo 内部并行处理机器人运动与避障,客户端需要多线程执行器才能保证/joy回调与异步服务调用不被阻塞:
ex = rclpy.executors.MultiThreadedExecutor() ex.add_node(self) self.teleop_thread = threading.Thread(target=ex.spin, daemon=True) self.teleop_thread.start()stop_teleop()则对称地调用stop_servo服务并join()后台线程,实现优雅退出。
2.4 必须由子类实现的两个抽象方法
@abstractmethod def publish_command(self): ... # 将设备输入转换为并发布遥操作指令 @abstractmethod def record(self): ... # 记录轨迹数据(示教/数据采集用途)从源码结构看,record()目前在各实现中均为空实现(pass),属于为轨迹记录预留的扩展点。
三、PS4 DualShock 设备实现:映射与指令生成
仓库当前唯一完整的设备实现位于 devices/ps4_dualshock.py,对应 README 中「Supported Devices」列表第一项。
3.1 设备映射数据类:遵循 Joy 消息约定
模块定义了三个@dataclass,其索引约定与sensor_msgs/Joy消息(axes[]与buttons[]数组)一一对应:
DualShockAxes(轴索引)
| 字段 | 索引 | 说明 |
|---|---|---|
LEFT_STICK_X/LEFT_STICK_Y | 0 / 1 | 左摇杆(俯仰/侧倾输入) |
LEFT_TRIGGER | 2 | 左扳机(前进) |
RIGHT_STICK_X/RIGHT_STICK_Y | 3 / 4 | 右摇杆(平移输入) |
RIGHT_TRIGGER | 5 | 右扳机(后退) |
D_PAD_X/D_PAD_Y | 6 / 7 | 方向键 |
DualShockButtons(按键索引)
| 字段 | 索引 | 字段 | 索引 |
|---|---|---|---|
X/O/TRIANGLE/SQUARE | 0–3 | L1/R1 | 4 / 5 |
L2/R2 | 6 / 7 | SHARE/OPTIONS/HOME | 8 / 9 / 10 |
LEFT_STICK_TRIGGER/RIGHT_STICK_TRIGGER | 11 / 12 |
PS4DualShock数据类将这些映射聚合为Axes与Buttons两个字段,作为设备配置整体传入。
3.2 publish_command:Joy 到 Twist 的转换逻辑
PS4DualShockTeleop.publish_command(data)是遥操作的核心,它将sensor_msgs/Joy原始输入换算为TwistStamped并发布:
twist = TwistStamped() # 右摇杆 → 平面内平移(Z 轴朝前、Y 轴侧向) twist.twist.linear.z = data.axes[RIGHT_STICK_Y] twist.twist.linear.y = data.axes[RIGHT_STICK_X] # 扳机 → 前后进(左右扳机线性组合,系数 ±0.5) lin_x_right = -0.5 * data.axes[RIGHT_TRIGGER] lin_x_left = 0.5 * data.axes[LEFT_TRIGGER] twist.twist.linear.x = lin_x_right + lin_x_left # 左摇杆 → 俯仰/侧倾角速度 twist.twist.angular.y = data.axes[LEFT_STICK_Y] twist.twist.angular.x = data.axes[LEFT_STICK_X] # L1/R1 → 滚转(绕 X 轴)角速度,按键值 0/1 映射为 ±1 roll_positive = data.buttons[R1] roll_negative = -1 * data.buttons[L1] twist.twist.angular.z = float(roll_positive + roll_negative) # 帧与时间戳 twist.header.frame_id = self.ee_frame_name twist.header.stamp = self.get_clock().now().to_msg() self.twist_publisher.publish(twist)值得注意的细节:
- 坐标系:
TwistStamped.header.frame_id被设置为构造时传入的ee_frame_name,因此输出的是「相对末端执行器坐标系」的增量速度,配合 servo 参数apply_twist_commands_about_ee_frame: true(servo_parameters.yaml)使用,可实现手柄操作与机械臂末端姿态解耦; - 扳机组合:前进/后退不是简单取一个扳机值,而是左右扳机的线性组合,支持按住一侧扳机时获得半速、同时按压时相互抵消(速度为零)的直觉操作;
- 单位约定:Joy 轴值域为
[-1, 1],正好契合 servo 参数command_in_type的"unitless"模式(servo_parameters.yaml 描述为"unitless commands are in the range [-1:1] as if from joystick"),并在 servo 侧乘以scale.linear(默认 0.4 m/s)与scale.rotational(默认 0.8 rad/s)得到实际速度。
构造函数提供了便于直接实例化的默认值:
PS4DualShockTeleop( ee_frame_name, # 必填,如 "panda_link8" node_name="ps4_dualshock_teleop", device_name="ps4_dualshock", device_config=PS4DualShock(), )四、自定义遥操作设备:扩展指南
模块文档明确指出「this module can be used to configure new devices for teleoperation」,扩展一个新设备只需三步:
- 继承
TeleopDevice(可参考 ps4_dualshock.py 的写法),在__init__中调用super().__init__(...)并透传node_name、device_name、device_config、ee_frame_name; - 实现
publish_command(self, data):订阅/joy的sensor_msgs/Joy会作为回调参数传入,将其映射为TwistStamped发布到/servo_node/delta_twist_cmds; - 实现
record(self):填充自己的轨迹记录逻辑(当前各设备均为空实现,属预留接口)。
基类已经替你完成了节点创建、话题/服务绑定、start_servo/stop_servo调用与多线程执行器管理,新设备只需关注「输入 → Twist」的映射策略。类型桩文件 teleop.pyi 与 ps4_dualshock.pyi 可辅助 IDE 补全与静态检查。
五、支持设备现状与规划
README 的「Supported Devices」列表如实记录了当前状态:
- PS4 DualShock Controller:✅ 已支持(即上文第三节的完整实现);
- Oculus Quest 2:🕐 计划支持(README 原文"plans to support this device")。
Oculus Quest 2 属于 VR 头显输入设备,其交互方式(六自由度手柄姿态、手势)与游戏手柄的轴/按键模型差异较大,仓库中目前尚无对应实现文件,这一点在 README 中已被明确标注为规划项。如果读者需要 VR 遥操作,可结合「自定义设备扩展」一节自行实现,或关注该项目后续进展。
六、遥操作之外:servo_client 在学习型机器人中的应用
servo_client模块文档强调的另一大用途是learning based approaches——向机器人直接注入速度指令。仓库中与之配套的是 moveit_py/moveit/policies 模块,其模块文档(policies/init.py)写明:
This module can be used to configure moveit_servo for learning based applications.
其中 policy.py 的Policy抽象基类与 servo_client 共享同一套理念:
- 通过
register_command()创建指令发布器,支持geometry_msgs/PoseStamped、geometry_msgs/Twist、control_msgs/JointJog三种命令类型(policy.py),其中 Twist 指令同样面向 moveit_servo 的指令话题; - 通过
ApproximateTimeSynchronizer对多个传感器话题(如sensor_msgs/Image)做时间同步,将前向推理结果作为控制指令发布。
也就是说,servo_client提供的是「人手/手柄 → 机器人」的实时回路,而policies提供的是「神经网络策略 → 机器人」的实时回路,两者都以moveit_servo作为底层实时控制通道,这正解释了模块文档中「既可用于遥操作,也可用于学习型方法」的定位。
七、与 moveit_servo 的协作要点
要让servo_client真正驱动机械臂,需要与 moveit_ros/moveit_servo 节点协同,几个关键对接点:
- 话题对齐:客户端发布
/servo_node/delta_twist_cmds(TwistStamped),与 servo 参数cartesian_command_in_topic默认值在servo_node命名下的解析结果一致(参见 panda_simulated_config.yaml 中的cartesian_command_in_topic: ~/delta_twist_cmds); - 输入单位:手柄轴值域
[-1,1]对应 servo 的command_in_type: "unitless"模式;若使用"speed_units"(单位 m/s 与 rad/s),则需在设备端自行完成速度标定(scale.linear默认 0.4 m/s、scale.rotational默认 0.8 rad/s,见 servo_parameters.yaml); - 启停服务:
/servo_node/start_servo与/servo_node/stop_servo的调用由基类统一封装,无需在设备实现中重复处理; - 坐标系一致性:
ee_frame_name必须与机器人 URDF 中实际存在的末端坐标系名一致,否则 Twist 指令的frame_id无法被正确变换。
总结
moveit_py.servo_client是 MoveIt2 Python 生态中面向实时遥操作的精简客户端:TeleopDevice抽象基类完成了与servo_node的所有 ROS 2 对接(Twist 话题、启停服务、多线程执行器),PS4 DualShock 实现给出了「手柄轴/按键 → Twist 指令」的完整参考映射,而自定义设备扩展只需聚焦publish_command与record两个方法。结合policies模块,它还能支撑学习型控制策略的部署,是连接交互设备、智能策略与 MoveIt Servo 实时控制能力的桥梁。
相关参考文件:
- servo_client README
- TeleopDevice 抽象基类
- PS4 DualShock 实现
- servo_node 订阅实现
- servo 参数定义
- Panda 示例配置
- Policy 基类
- 机器人
- ROS
【免费下载链接】moveit2
:robot: MoveIt for ROS 2
相关推荐
使用Go客户端操作Fluid项目中的Dataset和Runtime资源
使用Go客户端操作Fluid项目中的Dataset和Runtime资源 前言 在云原生环境中,Fluid项目提供了一种高效的数据编排和管理方案。本文将详细介绍如
云原生存储大数据人工智能Swagger Codegen Bash 客户端 AnotherFakeApi 使用指南:深入解析 testSpecialTags 操作
Swagger Codegen Bash 客户端 AnotherFakeApi 使用指南:深入解析 testSpecialTags 操作 导读 AnotherF
开发工具代码生成API设计Python 客户端操作 Kubernetes LeaseCandidateList:`V1beta1LeaseCandidateList` 模型源码级解读与实战
Python 客户端操作 Kubernetes LeaseCandidateList: V1beta1LeaseCandidateList 模型源码级解读与实战
后端云原生容器编排
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考