在真机强化学习(RL)实验中,我们经常会面临这样的挑战:更换操作对象往往意味着要换夹爪;当原有视角存在盲区时,我们需要增设相机;而某些复杂的任务甚至要求双臂协同操作。面对这些频繁的硬件调整,开发者们通常希望曾经调试好的驱动和控制代码能够直接复用,从而避免重复造轮子。



不同实验中的硬件组合:夹爪与相机、灵巧手、双臂平台
然而,在之前的 RLinf 框架下,更换单一硬件部件往往“牵一发而动全身”,需要修改多处代码。以早期的 Franka 环境为例,环境本身负责初始化相机,而机械臂控制器则负责初始化夹爪。尽管底层驱动已经实现了复用,但设备管理逻辑依然与特定的硬件平台深度耦合;因此,一旦开发者更换部件,就不得不深入修改环境或控制器的代码。
为了彻底解决这一痛点,我们对 RLinf 的真机接口进行了全新重构。我们将机械臂、夹爪和相机等硬件封装成了高度可复用的“积木”模块,并允许通过统一的接口进行按需组合。这种设计实现了设备读写、机器人组合与任务逻辑的全面解耦,让开发者能够像搭乐高积木一样,轻松搭建各种实验平台,并无缝复用已有的硬件适配代码。

像搭乐高一样组装机器人:零部件可复用,组合方式按需调整
组装机器人并统一读写设备
以真机强化学习中的经典抓取任务为例,我们首先来搭建一套单臂实验平台。假设我们已经通过各自的底层驱动,成功实例化了 Franka 机械臂、Robotiq 夹爪和 RealSense 相机这三个对象,并分别将它们命名为 arm、gripper 和 camera。接下来,只需借助 Robot 类,就能将它们轻松组合在一起:
from rlinf.robotics import Robot
robot = Robot(arm=arm, end_effector=gripper, wrist=camera)
这里的参数名称明确定义了各个设备在机器人系统中的具体用途:arm 代表机械臂,wrist 代表腕部相机,而 end_effector 则代表末端执行器(在当前示例中即为夹爪)。在后续的任务代码中,无论是读取观测数据还是发送动作指令,都可以直接通过这些名称来访问对应的硬件部件。
需要注意的是,实例化 Robot 对象仅仅是声明了硬件之间的组合关系,并不会立即建立物理连接。开发者需要显式调用 connect() 方法来连接硬件,随后即可顺畅地读取观测状态并发送动作指令;实验结束后,再通过 disconnect() 方法安全释放连接。在下面的代码示例中,我们展示了如何控制夹爪执行动作(其中 target_width 为目标开口宽度,在该 Robotiq 示例中以米为单位):
robot.connect()
try:
obs = robot.get_observation()
frame = obs["wrist"]["frame"]
pose = obs["arm"]["tcp_pose"]
robot.send_action({
"end_effector": {"target": [target_width]},
})
finally:
robot.disconnect()
在上述代码中,我们仅向 end_effector 发送了目标数值,因此只有夹爪会接收并执行这条指令。任务代码完全基于设备用途的名称来组织观测与动作流,而不同厂商硬件驱动之间那些繁琐的调用差异,则被全部封装在了零部件的底层实现中。凭借这种架构,开发者得以使用同一套标准接口来读取图像、获取末端位姿以及下发控制动作。
用同一套接口替换设备或扩展双臂
那么,当实验需求发生变化,例如需要更换不同型号的夹爪、增设新视角相机,亦或是扩展为双臂系统时,我们该如何调整当前的硬件组合呢?
举例来说,如果我们想将腕部相机从 RealSense 升级为 ZED,只需在初始化时替换对应的相机对象即可,同时保留 wrist 这一命名。这样一来,上层任务逻辑依然可以通过 wrist 读取相机数据,而原有的机械臂 and 夹爪驱动也能继续无缝工作。
当然,命名相同并不代表两款硬件输出的数据格式完全一致。更换相机后,开发者可能仍需要进行重新标定,并调整图像的尺寸和预处理逻辑;同理,更换夹爪时也必须核对动作指令的维度、单位以及控制语义。虽然这些特定硬件的差异仍需开发者进行针对性适配,但那套标准化的设备驱动和统一的读写代码却可以被完美复用。
而在进行复杂的双臂实验时,我们可以先分别准备好左、右两侧的机械臂与夹爪,然后利用 PartGroup 将单侧的硬件整合成一个逻辑组,最后再一并交由 Robot 进行统筹管理:
from rlinf.robotics import PartGroup
left = PartGroup(arm=left_arm, end_effector=left_gripper)
right = PartGroup(arm=right_arm, end_effector=right_gripper)
robot = Robot(left=left, right=right)
通过这种方式,左臂的末端位姿可以直接通过 obs["left"]["arm"]["tcp_pose"] 轻松读取,而右臂的相关数据则被妥善归类在 right 分组之下。同理,控制动作也会按照左右分组进行下发。尽管上层任务代码需要根据双臂结构进行相应调整,但底层的设备读写依然统一依赖于 get_observation() 和 send_action() 方法,这就确保了现有的硬件驱动能够继续发挥作用。
正如乐高积木的拼装原理一样,已经组合好的一组零部件,同样可以作为一个整体模块参与到更庞大的系统构建中。机械臂与夹爪率先组成单臂系统,随后两套单臂系统再进一步组合成双臂机器人,而最初编写的底层驱动,就这样一步步平滑地过渡并应用到了全新的实验平台之上。
把机器人接入真机 RL 任务
完成机器人系统的硬件组合后,开发者下一步需要明确的就是实验的具体任务逻辑。以抓取任务为例,如何判定抓取成功、怎样计算即时奖励、何时终止当前回合,以及如何执行环境复位,这些细节都高度依赖于具体的任务设定。值得庆幸的是,同一套机器人硬件组合,完全可以胜任多种不同的任务场景。
在 RLinf 的设计中,我们将这些核心逻辑交由“任务(Task)”与“环境(Environment)”来处理:底层零部件专注于纯粹的硬件读写,组合层负责高效汇总观测数据并精准分发动作指令。当开发者实现具体的任务逻辑后,只需借助 RobotTaskEnv,就能将机器人实例与任务逻辑无缝组装成一个完整的强化学习环境,进而顺畅地接入到后续的训练或评估流程中。只要硬件配置保持不变,开发者就可以固定现有的设备组合,仅通过调整奖励函数、复位策略及数据处理逻辑,就能快速迭代并实现全新的 RL 任务。
在不同节点上部署机器人零部件
在真实的物理实验环境中,硬件设备往往会分散连接在不同的计算设备上。例如,高带宽的相机可能连接在专用的计算节点上,而机械臂与通过串口通信的夹爪则可能连接在实时控制节点上。为了应对这种分布式场景,开发者可以直接通过设置零部件的 node_rank 属性来指定其运行节点,同时依然沿用前文介绍的组合方式和读写接口。当然,在跨节点运行之前,开发者需要提前配置好集群网络与相关的设备依赖,并结合系统要求的实际控制频率,仔细验证网络通信的时延情况。
通过这种设计,设备的逻辑组合与物理部署位置被彻底解耦,实现了独立配置。开发者完全可以先在本地单机上快速跑通单臂实验,随后再根据复杂的任务场景和硬件拓扑需求,灵活扩展设备组合并调整分布式部署方案。
让调试好的驱动成为可复用的积木
得益于这种清晰的架构分工,当开发者面临更换夹爪的需求时,只需微调对应的零部件实现与数据适配逻辑,便能放心地复用其余所有设备的代码。当系统需要升级为双臂时,只需在组合层进行扩展;当任务目标发生改变时,则只需在任务逻辑层进行调整。这一切都确保了前期辛苦投入的硬件适配工作,能够源源不断地服务于未来的实验。
每一次的适配与调试,都在为后续的科研工作沉淀下极具价值的、可复用的驱动代码。随着 RLinf 生态中可用零部件库的日益丰富,开发者们将能够更加便捷地“开箱即用”已有实现,从而将最宝贵的时间和精力聚焦于核心的任务设计与实验验证上。
我们诚挚邀请大家在自己的真机 RL 实验中试用这一全新接口,并非常期待您通过 GitHub 与我们交流硬件适配经验、反馈遇到的问题;同时,我们也热烈欢迎大家向社区贡献全新的零部件实现。也许,您今天刚刚接入的一款新设备,就会成为其他开发者明天构建实验平台时最得心应手的那块“乐高积木”!
GitHub:RLinf
RLinf 真机接口文档:
中文:rlinf.readthedocs.io/zh-cn/latest/rst_source/concepts/robotics.html
英文:rlinf.readthedocs.io/en/latest/rst_source/concepts/robotics.html
-END-
点击下方卡片,关注【Xbotics具身智能实验室】公众号
你想要的这里都有~~
Ask Me Anything|提问箱
对文章有疑惑,或想聊更深?欢迎把你的问题丢给我们:技术方案、实操踩坑、课程与资料、项目合作、职业发展,都可以问。
怎么问:在评论区留言,或私信公众号
我们会做什么:每周集中整理高质量问题并公开回复,重点问题邀请作者或嘉宾深度解答;典型问题会加入知识库并持续更新。
提问小提示:尽量说明「你的目标—当前做法—期望产出」,附上必要信息(硬件/软件版本、数据规模等),能更快获得有用答案。
一起把问题变成知识,推动社区进步 🚀