欢迎使用 Robonix!
犀照世界 灵通万物 为机器筑心 为具身立智
Robonix 目标是探索如何从系统层面构造具身智能“大脑”, 目标是为具身智能大脑提供跨越异构硬件的系统底座,支持具身智能机器人的用户/开发者方便地开发和部署自己的新模型,让自己的机器人能容易地掌握新技能、完成新任务。Robonix 将 AI 模型和具身硬件“软硬解耦”,将模型视为程序,希望支持模型“一次训练,任何机器部署运行”,同时针对"感知–理解-规划-行动"过程的数据处理以及环境交互等共性问题,设计 “感知-互联-认知-控制” 具身智能系统服务框架,方便模型和技能的开发和运行。我们期望 Robonix 能帮助各种机器人容易地构筑智脑,推动具身智能软硬件独立发展生态,让具身智能机器人更好用和易用。
快速开始
- 环境要求
- 1. 获取源码与编译
- 2. 启动 robonix-server
- 3. 使用 ridlc 与生成代码
- 4. 创建 package 并开发业务逻辑
- 5. 使用 rbnx 编译/运行/停止 package
- 6. 常用路径与验证
本节说明:获取源码与编译、Robonix 术语与通信模型、如何启动 robonix-server、如何使用 ridlc 与生成代码、如何创建 package 并开发业务逻辑、如何使用 rbnx 编译/运行/停止 package。
环境要求
- Ubuntu 22.04 + ROS 2 Humble
- Rust (rustup)
- Python 3.10+
ros-humble-example-interfaces、ros-humble-test-msgs(rclrs 依赖)rmw_zenoh_cpp(可选,默认使用 Zenoh 作为 RMW)
1. 获取源码与编译
1.1 获取源码
git clone https://github.com/syswonder/robonix
cd robonix
git submodule update --init --recursive
cd rust
sudo apt install -y ros-humble-example-interfaces ros-humble-test-msgs ros-humble-rmw-zenoh-cpp python3-grpcio python3-protobuf
1.2 编译与安装
推荐使用顶层 Makefile 一次性编译或安装 ridlc、rbnx、robonix-server:
cd rust
# 编译(默认 debug;会依次构建 ridlc、robonix-cli、robonix-server)
make build
# 安装到 ~/.cargo/bin(需已 PATH)
make install
# 清理各项目 target
make clean
- Release 编译:
make release再make build,或安装时BUILD_MODE=release make install。 - 单独编译某一项:
make build-ridlc、make build-cli、make build-server;单独安装:make install-ridlc、make install-cli、make install-server。 - 查看所有目标:
make help。
首次执行 make build(或直接构建 robonix-server)时会通过 ridlc 解析 RIDL、生成 Rust/Python/ROS 接口,并触发 colcon 构建 robonix_interfaces_ros2。若需 ROS 环境,请先:
source /opt/ros/humble/setup.bash
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
若 colcon 构建报错 canonicalize_version() got an unexpected keyword argument 'strip_trailing_zero',是 setuptools 与 packaging 版本不兼容,请先执行:pip3 install --user --upgrade 'packaging>=22.0' 'setuptools>=72'(或使用 sudo pip3 install --upgrade packaging setuptools)。./start_server 使用的 setup_zenoh_rclrs_env 会在构建前自动尝试升级,并取消 PYTHONNOUSERSITE 以便使用用户已升级的 setuptools。
不用 Make 时,可手动编译 robonix-server 与 callquery(rbnx 需在 robonix-cli 下单独构建):
source /opt/ros/humble/setup.bash
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
cargo build --manifest-path robonix-server/Cargo.toml --bin robonix-server --bin callquery
1.3 Robonix 术语与通信模型
- RIDL(Robonix Interface Definition Language):接口定义语言,描述“谁提供什么、请求/响应类型”。不定义 channel 名或部署,channel 由运行时分配。
- channel(通道):某一 interface 在运行时的具体通信端点(query/command 对应 ROS 2 service/action 名,stream 对应 topic 名)。由 robonix-server 在节点注册或 client 解析时分配;RIDL 与 manifest 中不写 channel 名。详见 RFC001 §7。
- 通信原语:
- stream:单向流,对应 ROS 2 topic(如位姿流)。
- command:带结果的控制命令,对应 ROS 2 action(如运动指令、skill 执行)。
- query:请求-响应,对应 ROS 2 service(如 ping、语义地图查询)。
- 节点(node):向 robonix-server 注册的逻辑实体。每个节点可基于 RIDL 生成的 stub 提供若干 interface:
- 被调用方:实现某 interface 的 server,通常为常驻进程(如 robonix-server 内的 ping 服务、语义地图 query server、机械臂 PRM、skill 节点)。
- 调用方:使用某 interface 的 client,不要求常驻(可一次性调用后退出)。
- interface:由 RIDL 定义;生成代码提供
create_*_client/create_*_server(或 stream 的 publisher/subscriber、command 的 client/server),业务逻辑在 stub 中补全。
通用性:不仅是 skill,运行在系统之上的任意进程(不属于标准服务、原语实现、skill 的“用户级”应用)都可以用同一套框架相互通信,只需约定好 RIDL 接口即可。类比 Android:系统服务、HAL、用户 app 均可通过 AIDL 通信;Robonix 中,robonix-server、prm 厂商、skill、以及任意自定义 package 都通过 RIDL 接口通信。本仓库示例对应关系:
| 示例 | 类型 | 说明 |
|---|---|---|
stream_demo | stream | 位姿流:stream_server 发布、stream_client 订阅,两进程通过 robonix/prm/base/pose_cov 通信 |
query_demo | query | 语义查询:semantic_server 提供、semantic_client 调用,通过 robonix/system/map/semantic_query 通信 |
skill_demo | command | greet 命令:skill_server 提供、skill_client 调用,通过 package-local skill_demo/skill/greet 通信 |
python_ping_client | query client | 调用 robonix-server 内置 ping,通过 robonix/system/debug/ping 通信 |
抽象硬件(相机、底盘、机械臂等)的接口形态见 抽象硬件原语。
2. 启动 robonix-server
robonix-server 提供 gRPC meta API(默认 0.0.0.0:50051):节点注册、Query/Stream/Command channel 的分配与解析,并内置 ping query 服务。
cd rust
./start_server
start_server会:清理占用端口的旧进程 -> 执行setup_zenoh_rclrs_env(source ROS、设置 RMW、colcon build、source overlay)→ 启动robonix-server。- 环境变量(可选):
ROBONIX_META_GRPC_ADDR(监听地址)、ROBONIX_META_GRPC_ENDPOINT(客户端连接地址,默认127.0.0.1:50051)、RMW_IMPLEMENTATION=rmw_zenoh_cpp、RUST_LOG。
确认运行:日志中出现 meta-runtime: registered node 'robonix-server'、ping query runtime ready;可用 ./examples/callquery robonix-server robonix/system/debug/ping '"test"' 验证返回 pong:"test"。
3. 使用 ridlc 与生成代码
3.1 ridlc 怎么用
- 日常:一般不需要单独跑 ridlc。构建
robonix-server时,build.rs会调用 ridlc 库,处理robonix-interfaces/ridl/下的.ridl,生成 Rust 与 Python 代码。 - 单独使用(如自建接口树):
cargo run --manifest-path ridlc/Cargo.toml -- -I <include> -o <out> -i <ridl_dir_or_file> --lang python
需至少一个-I类型搜索路径,-o为生成输出目录。
3.2 RIDL 命名空间 -> Python 模块与函数名
| RIDL 命名空间 | Python 模块路径 |
|---|---|
robonix/system/debug | robonix.system.debug |
robonix/prm/base | robonix.prm.base |
robonix/system/map | robonix.system.map |
robonix/system/skill | robonix.system.skill |
规则:robonix/a/b/c -> robonix.a.b.c(斜杠变点号)。
3.3 接口类型 -> 生成代码速查
| 原语 | 生成文件名 | Python 主要函数 |
|---|---|---|
| query | {name}_query.py | create_{name}_client, create_{name}_server |
| stream | {name}_stream.py | create_{name}_publisher, create_{name}_subscriber |
| command | {name}_command.py | create_{name}_client, create_{name}_server |
示例:query ping → robonix.system.debug.ping_query 模块,create_ping_client(...) / create_ping_server(...)。详细规范见 ridlc 开发手册。
4. 创建 package 并开发业务逻辑
本节给出步骤总览与一个最小示例;完整目录结构、manifest 逐字段说明、三种典型 package(机械臂 PRM、语义地图 query server、skill server)的代码骨架与对照表,见 Package 开发指南。
业务逻辑补全要点:生成代码负责 channel 注册与 ROS 绑定;你只需在指定位置写业务。Query server 用 server.start(handler) 传入 handler;Command server 用 server.execute = fn 赋值 execute;Stream 发布方在定时器或循环中 publish(msg),订阅方用 subscriber.start(callback) 传入回调。详见 ridlc 开发手册 §5。相机/机械臂/地图等厂商接入流程见 硬件/服务厂商接入指南。
4.1 步骤总览
- 确定要实现的接口:在 RIDL 里找到对应 query/command/stream(如
robonix/system/debug/ping、robonix/prm/base/pose_cov),得到生成模块与函数名(如 query 的create_ping_server/create_ping_client,stream 的create_pose_cov_publisher/create_pose_cov_subscriber)。 - 创建目录:在任意位置新建 package 根目录,包含
robonix_manifest.yaml以及与包名同名的 Python 子包(如python_ping_client/python_ping_client/)。setup.py、setup.cfg由rbnx build自动生成;package.xml可选(有则使用,可添加自定义 ROS2 依赖;无则自动生成)。rbnx -p可传该目录的路径或 package 名(按约定查找,见下文)。 - 写 manifest:
package(id、name、version、vendor、description、license)+nodes(每项一个 node:id建议com.syswonder.xxx,entry为模块:函数,如python_ping_client.call_ping:main)。 - 写业务代码:在 entry 指向的模块里连接 meta API(gRPC),用生成代码的
create_*_server或create_*_client,在 handler/execute 或 call 处实现逻辑。 - 用 rbnx 构建与运行:
rbnx build -p <path>或rbnx build -g <name>→rbnx start -p <package> -n <node_id>(需先启动 robonix-server);start 会阻塞直到该 package 进程退出,无需 stop。
4.2 最小示例:Python ping 客户端
- 目录(示例):本仓库中的
rust/examples/python_ping_client/仅作参考;你可把 package 放在任意路径,用rbnx build -p /path/to/your_package指定。 - Manifest 要点:
nodes[].id为 node id(本例为 client,可不被调用);entry: python_ping_client.call_ping:main表示启动时执行python_ping_client.call_ping的main。 - 业务逻辑:
python_ping_client/call_ping.py的main()中连接ROBONIX_META_GRPC_ENDPOINT,create_ping_client(runtime_client, requester_id, target),构造SystemDebugPing.Request(),client.call(req),打印响应。 - 运行:在 package 根目录或
rust下执行rbnx build -p <路径或包名>,再rbnx start -p <路径或包名>(示例中为python_ping_client,执行一次 ping 后进程退出)。
4.3 三类典型 package 速查
| 类型 | RIDL 接口 | 业务逻辑补全位置 |
|---|---|---|
| 机械臂 PRM(command server) | robonix/prm/arm/joint_trajectory | server.execute = fn,fn 内收 trajectory、控机械臂、返回 Result |
| 语义地图(query server) | robonix/system/map/semantic_query | server.start(handler),handler 内根据 request.filter 查地图、填 response.objects |
| Skill 节点(command server) | skill_demo/skill/greet(package-local) | server.execute = fn,fn 内处理 typed request、可选 goal_handle.publish_feedback()、返回 result |
| 位姿/传感器流(stream) | robonix/prm/base/pose_cov 等 | 发布方:定时器/循环中 publish(msg);订阅方:subscriber.start(callback),callback 内处理 msg |
共性:生成代码负责注册/解析 channel 和 ROS 绑定;你只在上述补全位置写业务。node id 必须与 manifest 中 nodes[].id 一致,且建议全局唯一(如 com.syswonder.prm_arm)。完整补全说明见 ridlc §5。
更详细的目录树、manifest 逐字段、完整代码示例与 colcon 配置,见 Package 开发指南。
5. 使用 rbnx 编译/运行/停止 package
无单独 deployment:直接用 rbnx 对 package 做 build/start/stop。
rbnx build -p <path> # 构建本地路径(如 examples/skill_demo),默认增量
rbnx build -p <path> --clean # 清空 rbnx-build 后全量重建
rbnx build -g <name> # 构建系统已安装包(rbnx install 安装的)
rbnx start -p <package> -n <node_id> # 启动并阻塞直到进程退出(Ctrl+C 结束)
包管理:rbnx install --github <repo> 或 rbnx install --path <dir> 安装到 ~/.robonix/packages;rbnx list 列出已安装包;rbnx info <name> 查看包详情。
-p可为 package 根目录的绝对或相对路径;也可为 package 名,此时 rbnx 在examples/、当前目录、rust/examples/或 ~/.robonix/packages 下查找。启动时需指定 node:rbnx start -p <package> -n <node_id>。- 启动前需已构建且 robonix-server 已运行(client 需连接 meta gRPC);server 型 package 启动后会向 robonix-server 注册。
示例:
cd rust
rbnx build -p python_ping_client
rbnx start -p python_ping_client # 阻塞直到 ping 执行完并退出
6. 常用路径与验证
| 路径 | 说明 |
|---|---|
rust/robonix-interfaces/ridl/ | RIDL 接口定义 |
rust/robonix-server/src/generated/ | 生成的 Rust 绑定 |
rust/robonix-server/target/rclrs_interfaces_ws/python_pkg/ | 生成的 Python 包 |
rust/robonix-server/target/rclrs_interfaces_ws/install/ | colcon 安装空间 |
验证 Zenoh:echo $RMW_IMPLEMENTATION 应为 rmw_zenoh_cpp;ps aux | grep zenoh 可见 Zenoh 相关进程。
一键验证:先启动 robonix-server,再 ./examples/callquery robonix-server robonix/system/debug/ping '"hello"' 得到 pong:"hello";或 rbnx start -p python_ping_client 后使用该 package 的客户端行为验证。
第二章:用户手册
本章介绍日常使用与运维。
概述
- robonix-server:gRPC meta API + ping query 服务,负责节点注册与 Query channel 分配/解析。详见 robonix-server。
- callquery:Rust 命令行客户端,用于调用已注册的 query。
- python_ping_client:Python 示例客户端,演示 Python 包如何调用某个节点的 query。
使用流程
- 启动 robonix-server:
./start_server - 调用 query:
./callquery robonix-server robonix/system/debug/ping '"hello"'或使用 Python 示例:rbnx start -p python_ping_client(需先rbnx build -p python_ping_client)
robonix-server
作用
- 提供 gRPC meta API(默认
0.0.0.0:50051):节点注册、Query channel 分配与解析 - 运行 ping query 服务(
robonix/system/debug/ping) - 使用 rclrs + rmw_zenoh 作为 ROS 2 传输层
启动
cd rust
./start_server
start_server 会执行 setup_zenoh_rclrs_env(source ROS、设置 RMW、colcon build、source overlay),然后启动 robonix-server。
环境变量
| 变量 | 说明 |
|---|---|
RMW_IMPLEMENTATION | 默认 rmw_zenoh_cpp |
ROBONIX_META_GRPC_ADDR | 监听地址,默认 0.0.0.0:50051 |
ROBONIX_META_GRPC_ENDPOINT | 客户端连接地址,默认同 ROBONIX_META_GRPC_ADDR |
RUST_LOG | 日志级别,如 info、debug |
确认运行
- 日志中出现
meta-runtime: registered node 'robonix-server'、ping query runtime ready ./callquery robonix-server robonix/system/debug/ping '"test"'返回pong:"test"
说明:callquery 仅支持 request/response 均为 std_msgs/msg/String 的 query。semantic_query、map_manager、model_manager、skill_library 等使用 robonix_msg 类型(如 Object[]),需用 Python 客户端(如 query_demo)调用。
第三章:开发文档
本章说明如何基于 RIDL 编写 package、使用生成代码、补全业务逻辑并构建运行。
文档索引
| 文档 | 内容 |
|---|---|
| Package 开发指南 | 从 RIDL 到生成代码、命名规则、补全逻辑、构建与运行 |
| 抽象硬件原语 | 相机、底盘、传感器、机械臂、夹爪、力/力矩等接口形态 |
| ridlc 开发手册 | ridlc 工具、RIDL 语法、代码生成规范 |
| 硬件/服务厂商接入指南 | 相机、机械臂、地图等厂商如何接入 RIDL 接口,按需实现接口子集 |
ridlc 开发手册
本文档描述 ridlc 的代码生成规范与通信实现,供实现者与开发者参考。RIDL 语义定义见 RFC001。
RIDL 语法速览(关键词、文件规则、命名等)见 RFC001 §3。
类型引用简写:import pkg/msg/Name 后,字段类型可使用短名 Name,也可使用全名 pkg/msg/Name;若多个 import 的末段相同则需用全名避免歧义。详见 RFC001 §5.1。
LLM/Agent 友好注释:@desc("...") 用于接口或字段,描述用途、含义与格式。会写入 DESCRIPTOR.annotations,供 AI 理解接口、正确构造请求。详见 RFC001 §5.2。
业务逻辑补全:生成代码只提供 channel 注册、ROS 绑定和工厂函数;用户需在指定位置补全回调或业务逻辑。§5 详细说明每种原语下在哪里补全、补全什么、如何调用,是编写 server/client/stream 代码的核心参考。
0. 文件与接口约定
- 一文件一接口:每个
.ridl文件只定义一个接口。 - 接口名 = 文件名:接口名必须与文件名(不含扩展名)一致,如
depth.ridl定义stream depth,move.ridl定义command move。类似 Java:一个文件一个 public 类,类名与文件名一致。 - 接口语法:
command/stream/query 接口名 [注解...] { 字段... };字段为方向 字段名 类型 [注解...];。注解可多个,如@desc、@frame。完整语法见 RFC001 §3.1。
1. 通信实现映射
当前 ridlc 采用 ROS 2 作为后端。各原语与 ROS 2 / gRPC 的映射如下:
| 原语 | ROS 2 | gRPC(可选实现) |
|---|---|---|
| stream | topic | server/client streaming |
| command | action | 双向流或 server streaming |
| query | service | unary RPC |
2. 代码生成规范
2.1 命名规律
| 原语 | 生成文件 | Python 工厂 | Rust 模块 |
|---|---|---|---|
| query | {name}_query.py | create_{name}_client, create_{name}_server | {name}_query |
| stream | {name}_stream.py | create_{name}_publisher, create_{name}_subscriber | {name}_stream |
| command | {name}_command.py | create_{name}_client, create_{name}_server | {name}_command |
2.2 命名空间映射
robonix/a/b/c→ Python 包robonix.a.b.crobonix/a/b/c→ Rust 模块robonix_a_b_c- ROS 2 类型名:namespace 去掉
robonix/后按层级 PascalCase 拼接 + 接口名,如robonix/system/debug+ping→SystemDebugPing
3. 运行时与 channel
生成代码通过 gRPC meta API 完成 Register*/Resolve* 调用。channel 由 robonix-server 分配,生成代码据此创建 ROS service/topic/action 并绑定。
4. 注解
RIDL 支持的注解(如 @desc、@frame、@requires_interface、@interruptible)见 RFC001 §5.2。ridlc 解析后传入 codegen,具体语义由生成代码与运行时实现。
5. 用户逻辑补全(Python)
生成代码提供工厂函数和基类,用户需补全「回调」或「业务逻辑」。下表说明每种通信模式下,在哪里补全、补全什么。
5.1 Query(请求-响应)
| 角色 | 补全位置 | 补全方式 | 说明 |
|---|---|---|---|
| Server | server.start(handler) | 传入 handler 函数 | 每次收到请求时调用 |
| Client | 无回调 | 直接调用 client.call(request) | 同步阻塞,返回 Response \| None |
Server 补全示例:
server = create_semantic_query_server(runtime_client, node_id=node_id)
def handler(request, response):
# request: 服务 Request 类型;response: 服务 Response 类型;需返回 response
response.objects = [...] # 填充 response
return response
server.start(handler) # 传入 handler
rclpy.spin(server)
Client 补全示例:
client = create_semantic_query_client(runtime_client, requester_id=..., target=...)
request = client._srv_type.Request()
request.filter.data = "room"
response = client.call(request)
if response is None:
raise RuntimeError("call failed or timed out")
for obj in response.objects:
print(obj.id, obj.label)
5.2 Stream(发布/订阅)
| 角色 | 补全位置 | 补全方式 | 说明 |
|---|---|---|---|
| Publisher | 定时器或业务逻辑 | 调用 publisher.publish(msg) | 构造消息并发布 |
| Subscriber | subscriber.start(callback) | 传入 callback(msg) 函数 | 每次收到消息时调用 |
Publisher 补全示例:
publisher = create_pose_cov_publisher(runtime_client, node_id=node_id)
def publish_once():
msg = publisher._msg_type()
msg.header.frame_id = "map"
msg.pose.pose.position.x = 1.25
publisher.publish(msg) # 调用 publish
timer = publisher.create_timer(0.5, publish_once)
rclpy.spin(publisher)
Subscriber 补全示例:
subscriber = create_pose_cov_subscriber(runtime_client, requester_id=..., target=...)
def on_msg(msg):
# msg: 消息类型(如 PoseWithCovarianceStamped)
print(msg.pose.pose.position)
subscriber.start(on_msg) # 传入 callback
rclpy.spin(subscriber)
5.3 Command(动作/技能)
Command 有 input/output/result 三个字段,对应 ROS2 Action 的 Goal/Feedback/Result。
| 角色 | 补全位置 | 补全方式 | 说明 |
|---|---|---|---|
| Server | server.execute | 赋值 server.execute = your_execute 或子类重写 | 处理 Goal,可选发 Feedback,返回 Result |
| Client | 无回调(简单用法) | 调用 client.send(request) → goal_handle → get_result_async() | 需 rclpy.spin_until_future_complete |
| Client | send_goal_async(..., feedback_callback=...) | 传入 feedback_callback 接收 output | 接收执行过程中的 feedback |
Server 补全示例:
server = create_greet_server(runtime_client, node_id=node_id)
def execute(request, goal_handle):
# request: Action.Goal;goal_handle: ServerGoalHandle | None
# output (feedback): 可选,通过 goal_handle.publish_feedback(fb) 发送
if goal_handle:
fb = server._action_type.Feedback()
fb.feedback.progress = "processing..."
goal_handle.publish_feedback(fb)
# result: 必须返回
result = server._action_type.Result()
result.response.message = f"Hello, {request.request.name}!"
return result
server.execute = execute # 赋值 execute
server.start()
rclpy.spin(server)
Client 补全示例(简单用法,不接收 feedback):
client = create_greet_client(runtime_client, requester_id=..., target=...)
request = client._action_type.Goal()
request.request = GreetRequest()
request.request.name = "world"
goal_handle = client.send(request)
if goal_handle is None or not goal_handle.accepted:
raise RuntimeError("goal not accepted")
result_future = goal_handle.get_result_async()
rclpy.spin_until_future_complete(client, result_future, timeout_sec=10.0)
wrapped = result_future.result()
if wrapped is None:
raise RuntimeError("timeout waiting for result")
print(wrapped.result.response.message, wrapped.result.response.success)
Client 补全示例(接收 feedback):
client = create_greet_client(runtime_client, requester_id=..., target=...)
request = client._action_type.Goal()
request.request = GreetRequest()
request.request.name = "world"
def on_feedback(fb_msg):
# fb_msg 为 FeedbackMessage,访问自定义 output 字段:fb_msg.feedback.feedback.<字段名>
print("feedback:", fb_msg.feedback.feedback.progress)
goal_future = client._client.send_goal_async(request, feedback_callback=on_feedback)
rclpy.spin_until_future_complete(client, goal_future, timeout_sec=10.0)
goal_handle = goal_future.result()
if goal_handle is None or not goal_handle.accepted:
raise RuntimeError("goal not accepted")
result_future = goal_handle.get_result_async()
rclpy.spin_until_future_complete(client, result_future, timeout_sec=10.0)
wrapped = result_future.result()
# 处理 wrapped.result
Client 接收 feedback 注意:feedback_callback 收到的是 FeedbackMessage(含 goal_id 和 feedback),不是裸 Feedback。访问自定义 output 字段需:fb_msg.feedback.feedback.<字段名>(第一个 feedback 为 FeedbackMessage 的字段,第二个为 action Feedback 中 output 字段名,如 progress)。
5.4 快速对照表
| 原语 | Server/Provider 补全 | Client/Consumer 补全 |
|---|---|---|
| Query | server.start(handler),handler(request, response) -> response | 构造 Request → client.call(request) → 处理 Response \| None |
| Stream | publisher.publish(msg)(在定时器或业务逻辑中调用) | subscriber.start(callback),callback(msg) 处理消息 |
| Command | server.execute = fn,fn(request, goal_handle) -> result;可选 goal_handle.publish_feedback(fb) | 构造 Goal → client.send(request) 或 send_goal_async(..., feedback_callback=on_fb) → goal_handle.get_result_async() → rclpy.spin_until_future_complete → wrapped.result;feedback 回调内用 fb_msg.feedback.feedback.<字段名> 访问 output |
Package 开发指南
- 1. 概念与前置条件
- 2. 从 RIDL 到生成代码的映射
- 3. 创建 package 的完整步骤
- 4. 三种典型 package 对照表
- 5. 使用生成的 Python 客户端(以 ping 为例)
- 6. 使用生成的 Python 服务端
- 7. 补全业务逻辑的位置小结
- 8. 参考
本文档详细说明:如何从零创建一个 Robonix package、如何根据 RIDL 找到生成代码与函数名、如何在 server/client 中补全业务逻辑、如何用 rbnx 构建与运行。按步骤操作即可完成一个可运行的 package。
1. 概念与前置条件
1.1 Package 是什么
- Package = 一个可被 rbnx 单独构建、启动、停止的应用单元。
- 根目录必须有 robonix_manifest.yaml,其中声明 package 身份(id、name、version 等)和 node 列表(每个 node 有一个启动 entry,如
模块:函数)。 - Node = 运行起来的一个进程;node id(在 manifest 的
nodes[].id)即通信时的目标标识,建议使用com.syswonder.xxx这类唯一 ID。 - 构建与启停只通过 rbnx:
rbnx build -p <path>或rbnx build -g <name>(-p 本地路径,-g 系统已安装包名)、rbnx start -p <package> -n <node>(每次只启动一个 node,start 会阻塞直到进程退出)。
1.2 你需要先准备好
- 已能成功执行
make build并启动./start_server(robonix-server 提供 meta API 与 ping 服务)。 - 知道要实现的接口对应哪条 RIDL(例如
robonix/system/debug/ping、robonix/prm/arm/joint_trajectory)。若不清楚,见下文“从 RIDL 到生成代码的映射”或 抽象硬件原语。
1.3 谁可以用这套框架通信
不仅是 skill,运行在系统之上的任意进程(不属于标准服务、原语实现、skill 的“用户级”应用)都可以用同一套 RIDL 通信框架相互通信,只需约定好 RIDL 接口即可。类比 Android:系统服务、HAL、用户 app 均可通过 AIDL 通信;Robonix 中,robonix-server、prm 厂商、skill、以及任意自定义 package 都通过 RIDL 接口通信。
| 示例 | 原语 | 说明 |
|---|---|---|
stream_demo | stream | stream_server 发布位姿,stream_client 订阅;两进程通过 robonix/prm/base/pose_cov 通信 |
query_demo | query | semantic_server 提供语义查询,semantic_client 调用;通过 robonix/system/map/semantic_query 通信 |
skill_demo | command | skill_server 提供 greet 命令,skill_client 调用;通过 package-local skill_demo/skill/greet 通信 |
python_ping_client | query client | 调用 robonix-server 内置 ping;通过 robonix/system/debug/ping 通信 |
2. 从 RIDL 到生成代码的映射
2.1 RIDL 命名空间 -> Python 模块路径
| RIDL namespace | Python 导入路径 |
|---|---|
robonix/system/debug | robonix.system.debug |
robonix/prm/base | robonix.prm.base |
robonix/prm/arm | robonix.prm.arm |
robonix/system/map | robonix.system.map |
robonix/system/skill | robonix.system.skill |
规则:robonix/a/b/c -> robonix.a.b.c(斜杠变点号)。
2.2 接口类型 -> 文件名与函数名
| 原语 | RIDL 名示例 | 生成文件名 | 主要函数 |
|---|---|---|---|
| query | ping | ping_query.py | create_ping_client, create_ping_server |
| stream | pose | pose_stream.py | create_pose_publisher, create_pose_subscriber |
| command | motion_cmd | motion_cmd_command.py | create_motion_cmd_client, create_motion_cmd_server |
2.3 快速查表:常见接口
| RIDL 接口 ID | Python 导入 | 服务端/发布方 | 客户端/订阅方 |
|---|---|---|---|
robonix/system/debug/ping | from robonix.system.debug.ping_query import create_ping_server | create_ping_server(runtime_client, node_id) | create_ping_client(runtime_client, requester_id, target) |
robonix/system/map/semantic_query | from robonix.system.map.semantic_query_query import create_semantic_query_server | create_semantic_query_server(runtime_client, node_id) | create_semantic_query_client(...) |
robonix/prm/base/move | from robonix.prm.base.move_command import create_move_server | create_move_server(runtime_client, node_id) | create_move_client(...) |
skill_demo/skill/greet (package-local) | from skill_demo.skill.greet_command import create_greet_server | create_greet_server(runtime_client, node_id) | create_greet_client(...) |
robonix/prm/base/pose_cov | from robonix.prm.base.pose_cov_stream import create_pose_cov_publisher | create_pose_cov_publisher(runtime_client, node_id) | create_pose_cov_subscriber(runtime_client, requester_id, target) |
2.4 ROS 类型名
query 的 srv 类型:robonix/system/debug + ping -> robonix_interfaces_ros2.srv.SystemDebugPing。
规则:{NamespacePascal}{InterfacePascal}。
2.5 ROS msg 包来源与 package.xml
默认来源:rbnx build 使用本仓库 robonix-interfaces/lib 下自维护的 IDL(rcl_interfaces、common_interfaces 等),不依赖系统 ROS msg 包。
- 来源:
robonix-interfaces/lib/rcl_interfaces、robonix-interfaces/lib/common_interfaces(如builtin_interfaces、std_msgs、geometry_msgs、action_msgs、nav_msgs、sensor_msgs、trajectory_msgs等) - 构建流程:ridlc 将上述包复制到 workspace 的
vendor/,colcon 在 vendor 中构建,产物进入 install 空间 - 使用方式:与系统包完全一致,例如
from std_msgs.msg import String、from geometry_msgs.msg import PoseWithCovarianceStamped,字段定义以本仓库为准
自定义 package.xml:若需使用其他 ROS2 系统包(如 sensor_msgs、geometry_msgs 等 apt 安装的包),可在 package 根目录添加 package.xml,在其中声明 <depend>。rbnx build 会使用该文件,不会覆盖。示例见 rust/examples/prm_camera_vendor/package.xml、rust/examples/skill_demo/package.xml。
3. 创建 package 的完整步骤
步骤一:确定要实现的接口
- 若做 query 的 server:在 RIDL 里找到对应 query(如
robonix/system/map/semantic_query),记下 namespace 与接口名,得到 Python 模块robonix.system.map.semantic_query_query和create_semantic_query_server。 - 若做 command 的 server:同理找到 command(如
robonix/prm/arm/joint_trajectory),得到create_joint_trajectory_server。 - 若做 stream 的 provider(发布方):在 RIDL 里找到对应 stream(如
robonix/prm/base/pose_cov),得到模块robonix.prm.base.pose_cov_stream和create_pose_cov_publisher(runtime_client, node_id);在循环中publish(msg)发布数据。 - 若做 stream 的 consumer(订阅方):同上得到
create_pose_cov_subscriber(...),需指定提供该 stream 的 target node id(与对方 manifest 里nodes[].id一致);在回调中处理收到的消息。 - 若做 client(调用已有 query/command server):用
create_*_client(runtime_client, requester_id, target),其中target为提供该接口的 node id(与对方 manifest 里nodes[].id一致)。
步骤二:创建目录结构
在任意位置新建一个目录作为 package 根目录即可。推荐结构(Python package):
my_package/ # package 根目录(任意名,建议与 package.name 一致)
├── robonix_manifest.yaml # 必选:manifest
└── my_package/ # Python 包名(与 manifest 中 package.name 一致)
├── __init__.py
└── main.py # 入口模块,manifest 里 entry 写 my_package.main:main
要点:
robonix_manifest.yaml必须在 package 根目录。setup.py、setup.cfg、resource/由rbnx build根据 manifest 自动生成,无需手写。package.xml:若源码中已有,则直接使用(可添加自定义 ROS2 依赖如std_msgs、sensor_msgs等系统包);若无则自动生成。- entry 格式为
模块:函数,例如my_package.main:main表示执行from my_package.main import main并调用main()。 - 若 package 名是
python_ping_client,则目录通常为python_ping_client/,其下 Python 包也为python_ping_client/,入口如python_ping_client.call_ping:main。
步骤三:编写 robonix_manifest.yaml
在 package 根目录创建 robonix_manifest.yaml,内容示例:
manifestVersion: 1
package:
id: com.robonix.example.my_package # 稳定 ID,可被其他 package 引用
name: my_package # 包名,与目录名/colcon 包名一致
version: 0.1.0
vendor: robonix
description: 一句话描述
license: MulanPSL-2.0
nodes:
- id: com.syswonder.example.my_node # node id,通信时的目标标识,建议唯一
type: python
entry: my_package.main:main # 该 node 的启动入口:模块:函数
字段说明:
| 字段 | 必填 | 说明 |
|---|---|---|
manifestVersion | ✓ | 固定写 1 |
package.id | ✓ | 稳定标识,如 com.robonix.example.xxx |
package.name | ✓ | 包名,rbnx 的 -p 可写此名 |
package.version | ✓ | 版本号 |
package.vendor | ✓ | 厂商/组织 |
package.description | ✓ | 简短描述 |
package.license | ✓ | 许可证 |
nodes | ✓ | node 列表,至少一项 |
nodes[].id | ✓ | node id,通信时使用,建议 com.syswonder.xxx |
nodes[].type | - | 默认 python |
nodes[].entry | ✓ | 启动入口,如 my_package.main:main |
一个 package 可以有多个 node(多个 nodes 项),每次 rbnx start -p <package> -n <node_id> 只启动一个 node。
步骤四:编写业务代码
业务代码放在 package 根目录下的 Python 包内(与 package.name 同名的子目录),每个 node 的 entry 对应一个“模块:函数”。业务逻辑补全位置:query 用 server.start(handler) 传入 handler;command 用 server.execute = fn 赋值;stream 发布方在定时器/循环中 publish(msg),订阅方用 subscriber.start(callback)。详见 ridlc 开发手册 §5。下面按 4.1 通用入口约定,再分 4.2–4.5 给出四种典型实现及完整目录与代码。
4.1 入口函数约定与目录关系
- entry 格式:
包名.模块名:函数名,例如python_ping_client.call_ping:main表示从python_ping_client包下的call_ping模块导入main并执行。 - 入口函数(如
main())必须完成:- 连接 robonix meta API(gRPC),得到
runtime_client(RobonixRuntimeStub)。 - 若为 server:用生成代码的
create_*_server(runtime_client, node_id)创建服务,node_id与 manifest 里该 component 的id一致;绑定 handler 或 execute 后调用server.start(...),最后rclpy.spin(server)常驻。多个 server/publisher 时,每个create_*返回独立 Node,需用MultiThreadedExecutor将全部节点加入后统一 spin,详见 厂商接入指南。 - 若为 client:用
create_*_client(runtime_client, requester_id, target)创建客户端,构造请求并client.call(req, timeout_sec=...),打印或处理响应后退出。
- 连接 robonix meta API(gRPC),得到
目录结构关系示例(以包名 my_package 为例):
my_package/ # package 根目录
├── robonix_manifest.yaml
└── my_package/ # Python 包,名与 package.name 一致
├── __init__.py
├── call_ping.py # entry: my_package.call_ping:main
├── semantic_server.py # entry: my_package.semantic_server:main
├── joint_trajectory_server.py
└── skill_server.py
meta 地址默认 127.0.0.1:50051,可通过环境变量 ROBONIX_META_GRPC_ENDPOINT 覆盖。必须通过 rbnx start 启动,rbnx 会 source colcon install 的 setup,生成代码路径自动可用。
4.2 Query Server 示例(语义地图 semantic_query)
实现 RIDL 接口 robonix/system/map/semantic_query 的 server:请求带 filter(String),响应填 objects(robonix_msg/msg/Object[])。node id 与 manifest 中该 node 的 id 一致。
目录中新增文件(在 Python 包 my_package 下):
my_package/semantic_server.py
完整代码:
# my_package/semantic_server.py
import grpc
import rclpy
from robonix_runtime_pb2_grpc import RobonixRuntimeStub
from robonix_msgs.msg import Object
from robonix.system.map.semantic_query_query import create_semantic_query_server
def main():
endpoint = "127.0.0.1:50051" # 或从 ROBONIX_META_GRPC_ENDPOINT 读取
node_id = "com.robonix.example.map_semantic" # 与 manifest nodes[].id 一致
rclpy.init()
channel = grpc.insecure_channel(endpoint)
runtime_client = RobonixRuntimeStub(channel)
server = create_semantic_query_server(runtime_client, node_id)
def handler(request, response):
# 业务逻辑:根据 request.filter 查语义地图,填充 response.objects(Object[])
filter_str = request.filter.data if request.filter else ""
obj1 = Object()
obj1.id, obj1.label = "obj1", "table"
obj2 = Object()
obj2.id, obj2.label = "obj2", "cup"
response.objects = [obj1, obj2]
return response
server.start(handler)
rclpy.spin(server)
if __name__ == "__main__":
main()
manifest 中对应 node 示例:
nodes:
- id: com.syswonder.map.semantic
type: python
entry: my_package.semantic_server:main
业务逻辑补全位置:handler(request, response) 内,根据 request.filter 查地图,将结果写入 response.objects(robonix_msg/msg/Object[])。
4.3 Query Client 示例(ping)
实现调用 robonix/system/debug/ping 的 client:连接 meta、解析 channel、构造 ping 请求、发送并打印响应。入口一次执行后退出。
目录中新增文件:
my_package/call_ping.py(或本仓库示例中的python_ping_client/python_ping_client/call_ping.py)
完整代码(与仓库内 rust/examples/python_ping_client/python_ping_client/call_ping.py 一致):
# my_package/call_ping.py
# Must be run via rbnx start (not direct python)
import sys
import grpc
from robonix_runtime_pb2_grpc import RobonixRuntimeStub
from robonix.system.debug.ping_query import create_ping_client
from robonix_interfaces_ros2.srv import SystemDebugPing
from std_msgs.msg import String
def main():
endpoint = "127.0.0.1:50051"
target = "robonix-server" # 提供 ping 的 node id
requester_id = "my_ping_client"
payload = sys.argv[1] if len(sys.argv) > 1 else "hello"
channel = grpc.insecure_channel(endpoint)
runtime_client = RobonixRuntimeStub(channel)
client = create_ping_client(runtime_client, requester_id, target)
req = SystemDebugPing.Request()
req.data = String()
req.data.data = payload
response = client.call(req, timeout_sec=10.0)
print(response.data.data)
if __name__ == "__main__":
main()
manifest 示例(client 作为单次调用的 node,可不被他人调用,id 仅标识本进程):
nodes:
- id: my_ping_client
type: python
entry: my_package.call_ping:main
业务逻辑补全位置:在 client.call(req) 之后,对 response 做解析、打印或后续逻辑。
4.4 Command Server 示例(机械臂 joint_trajectory)
实现 RIDL 接口 robonix/prm/arm/joint_trajectory 的 server:接收 input.trajectory(JointTrajectory),执行轨迹后返回 result.status(CommandResult)。生成代码提供 create_joint_trajectory_server(runtime_client, node_id),返回对象的 execute(request, goal_handle) 由你实现,并赋给 server.execute 后调用 server.start()。
目录中新增文件:
my_package/joint_trajectory_server.py
完整代码:
# my_package/joint_trajectory_server.py
import grpc
import rclpy
from robonix_runtime_pb2_grpc import RobonixRuntimeStub
from robonix.prm.arm.joint_trajectory_command import create_joint_trajectory_server
from robonix_msgs.msg import CommandResult
def main():
endpoint = "127.0.0.1:50051"
node_id = "com.syswonder.prm.arm" # 与 manifest nodes[].id 一致
rclpy.init()
channel = grpc.insecure_channel(endpoint)
runtime_client = RobonixRuntimeStub(channel)
server = create_joint_trajectory_server(runtime_client, node_id)
def execute(request, goal_handle=None):
# request 为 action 的 Goal 类型,含 trajectory 字段(JointTrajectory)
trajectory = request.trajectory
# 业务逻辑:将 trajectory 下发给真实机械臂驱动,等待执行完成
result = server._action_type.Result()
result.status = CommandResult()
result.status.success = True
result.status.message = "ok"
return result
server.execute = execute
server.start()
rclpy.spin(server)
if __name__ == "__main__":
main()
manifest 示例:
nodes:
- id: com.syswonder.prm.arm
type: python
entry: my_package.joint_trajectory_server:main
业务逻辑补全位置:在 execute(request, goal_handle) 内解析 request.trajectory(JointTrajectory),驱动机械臂,构造并返回 action Result(含 status 字段为 CommandResult)。
4.5 Per-Skill Command 示例(greet)
每个 skill 使用独立的 command RIDL,带类型化 input/result(ROS msg)。package 可在 ridl/ 下定义 package-local RIDL,在 msg/ 下放置 .msg 文件;rbnx build 会在 rbnx-build/ws 内自动生成 {package}_msgs、{package}_interfaces、{package}_interfaces_ros2,无需在源码中创建。
源码目录结构(以 skill_demo 为例,仅需手写以下内容):
skill_demo/
├── robonix_manifest.yaml
├── msg/ # 仅 .msg 文件,rbnx build 自动生成 skill_demo_msgs
│ ├── GreetRequest.msg # string name
│ └── GreetResult.msg # string message, bool success
├── ridl/skill/
│ └── greet.ridl # namespace skill_demo/skill
└── skill_demo/
├── skill_server.py
└── skill_client.py
ridl/skill/greet.ridl 示例:
namespace skill_demo/skill
import skill_demo_msgs/msg/GreetRequest
import skill_demo_msgs/msg/GreetResult
command greet @desc("A simple skill to greet a person.") {
input request GreetRequest @desc("Name to greet");
result response GreetResult @desc("Greeting message and success");
version 1.0;
}
约定:package-local RIDL 的 namespace 必须以 manifest 的 package.name 为前缀(如 skill_demo/skill)。
Server 代码:
# skill_demo/skill_server.py
import grpc
import rclpy
from skill_demo.skill.greet_command import create_greet_server
from robonix_runtime_pb2_grpc import RobonixRuntimeStub
def main():
endpoint = "127.0.0.1:50051"
node_id = "skill_server" # 与 manifest nodes[].id 一致
channel = grpc.insecure_channel(endpoint)
runtime_client = RobonixRuntimeStub(channel)
server = create_greet_server(runtime_client, node_id=node_id)
def execute(request, goal_handle=None):
result = server._action_type.Result()
name = request.request.name
result.response.message = f"Hello, {name}!"
result.response.success = True
return result
server.execute = execute
server.start()
rclpy.spin(server)
if __name__ == "__main__":
main()
Client 代码(skill_demo/skill_client.py):
# skill_demo/skill_client.py
from skill_demo.skill.greet_command import create_greet_client
from skill_demo_msgs.msg import GreetRequest
client = create_greet_client(runtime_client, requester_id=..., target=...)
# 1. 构造 Goal(input)
request = client._action_type.Goal()
request.request = GreetRequest()
request.request.name = "world"
# 2. 可选:接收 feedback(output),callback 内用 fb_msg.feedback.feedback.progress 访问
def on_feedback(fb_msg):
print("feedback:", fb_msg.feedback.feedback.progress)
goal_future = client._client.send_goal_async(request, feedback_callback=on_feedback)
rclpy.spin_until_future_complete(client, goal_future, timeout_sec=10.0)
goal_handle = goal_future.result()
# 3. 获取 Result
if goal_handle and goal_handle.accepted:
result_future = goal_handle.get_result_async()
rclpy.spin_until_future_complete(client, result_future, timeout_sec=10.0)
wrapped = result_future.result()
if wrapped:
print(wrapped.result.response.message, wrapped.result.response.success)
manifest 示例:
nodes:
- id: skill_server
type: python
entry: skill_demo.skill_server:main
- id: skill_client
type: python
entry: skill_demo.skill_client:main
业务逻辑补全位置:在 execute(request, goal_handle) 内使用 request.request.name 等类型化字段,返回 result.response.message、result.response.success。
小结:生成代码负责向 robonix-server 注册/解析 channel 和 ROS 绑定;你只在 query 的 handler、command 的 execute、或 client 的 call 之后写业务逻辑。node_id 必须与 manifest 中对应 component 的 id 一致。
步骤五:用 rbnx 构建与运行
rbnx build 会根据 manifest 自动生成 setup.py、setup.cfg、resource/。package.xml:若源码中已有则使用(可添加自定义 ROS2 依赖,如 std_msgs、sensor_msgs、geometry_msgs 等系统包);若无则自动生成,依赖 robonix-interfaces/lib 下的 msg 包(见 §2.5)。在 rust 目录下(或 -p 指定为 package 路径):
rbnx build -p my_package
rbnx start -p my_package # 阻塞直到进程退出
- build:校验 manifest、执行 colcon 等,产物在 package 下的
rbnx-build等目录。 - build:
rbnx build -p <path>或rbnx build -g <name>。-p为本地路径(如 examples/skill_demo);-g为系统已安装包名(rbnx install安装的包)。默认增量构建;加--clean可清空rbnx-build后全量重建。 - start:
rbnx start -p <package> -n <node_id>每次只启动一个 node,按该 node 的 entry 启动进程并阻塞直到退出;需先启动 robonix-server,meta 地址可通过环境变量ROBONIX_META_GRPC_ENDPOINT或 rbnx--endpoint传入。
-p 可为 package 根目录的绝对或相对路径;也可为 package 名字,此时 rbnx 在 examples/、cwd、rust/examples/ 下查找含 robonix_manifest.yaml 的目录,或系统安装的包(~/.robonix/packages)。
4. 三种典型 package 对照表
| 类型 | RIDL 接口 | 生成函数(server/发布方) | 业务补全位置 | 运行方式 |
|---|---|---|---|---|
| 机械臂 PRM(command) | robonix/prm/arm/joint_trajectory | create_joint_trajectory_server | execute:收 trajectory,控机械臂,返回 CommandResult | 常驻,rbnx start |
| 语义地图(query) | robonix/system/map/semantic_query | create_semantic_query_server | start(handler):request.filter -> 查地图 -> response.objects(Object[]) | 常驻,rbnx start |
| Skill 节点(command) | skill_demo/skill/greet(package-local) | create_greet_server | execute:typed request/result(GreetRequest/GreetResult) | 常驻,rbnx start |
| 位姿/传感器流(stream) | robonix/prm/base/pose_cov 等 | create_pose_cov_publisher | 循环中 publish(msg);订阅方用 create_pose_cov_subscriber,在回调中处理 msg | 常驻(发布方)或按需(订阅方),rbnx start |
5. 使用生成的 Python 客户端(以 ping 为例)
import grpc
from robonix_runtime_pb2_grpc import RobonixRuntimeStub
from robonix.system.debug.ping_query import create_ping_client
from robonix_interfaces_ros2.srv import SystemDebugPing
from std_msgs.msg import String
channel = grpc.insecure_channel("127.0.0.1:50051")
runtime_client = RobonixRuntimeStub(channel)
client = create_ping_client(runtime_client, "my_client", "robonix-server")
req = SystemDebugPing.Request()
req.data = String()
req.data.data = "hello"
response = client.call(req, timeout_sec=10.0)
print(response.data.data)
6. 使用生成的 Python 服务端
server = create_ping_server(runtime_client, node_id="my_provider")
def handler(request, response):
response.data = String()
response.data.data = f"pong:{request.data.data}"
return response
server.start(handler)
rclpy.spin(server)
7. 补全业务逻辑的位置小结
| 原语 | Server/Provider | Client/Consumer |
|---|---|---|
| query | server.start(handler),handler 内填 response | 构造 Request → client.call(request) → 处理 Response \| None |
| stream | 定时器/循环中 publish(msg);subscriber 用 start(callback) | subscriber.start(callback),callback(msg) 内处理 |
| command | server.execute = fn,fn 内处理 Goal、可选 goal_handle.publish_feedback(fb)、返回 Result | 构造 Goal → client.send(request) 或 send_goal_async(..., feedback_callback=on_fb) → goal_handle.get_result_async() → spin_until_future_complete → wrapped.result;feedback 内用 fb_msg.feedback.feedback.<字段名> 访问 output |
详见 ridlc 开发手册 §5。
8. 参考
- 完整示例:本仓库
rust/examples/下python_ping_client(query client)、query_demo、stream_demo、skill_demo、prm_camera_vendor(相机厂商)、prm_arm_vendor(机械臂厂商)、map_semantic_service(地图服务),均含package.xml可参考;package 可放在任意路径。 - 抽象硬件原语:相机、底盘、传感器、机械臂、夹爪、力/力矩等接口形态,见 抽象硬件原语。
- 硬件/服务厂商接入:相机、机械臂、地图等厂商如何接入 RIDL 接口、按需实现接口子集,见 硬件/服务厂商接入指南。
- Manifest 规范:RFC002。
- RIDL 与 channel:RFC001。
抽象硬件原语(Primitives)
本章描述 Robonix 系统中各类抽象硬件的接口形态。每种硬件对应一个命名空间(robonix/prm/*),厂商按需实现接口子集,无需实现全部;同一领域内硬件形态各异(如纯深度相机、RGB 相机、红外相机),不存在统一最小契约。设计参考 Android HAL:接口定义与实现解耦,运行时通过 meta 解析 channel 完成通信。
硬件类型索引
| 类型 | 命名空间 | 说明 |
|---|---|---|
| 相机 (Camera) | robonix/prm/camera | RGB、深度、RGB-D、红外、内参等视觉传感器 |
| 底盘 (Base) | robonix/prm/base | 导航、运动控制、位姿、里程计、急停等 |
| 通用传感器 (Sensor) | robonix/prm/sensor | 点云、激光雷达、IMU |
| 机械臂 (Arm) | robonix/prm/arm | 末端执行器运动、关节运动、状态、轨迹 |
| 夹爪 (Gripper) | robonix/prm/gripper | 开合、宽度设置与状态 |
| 力/力矩 (Force-Torque) | robonix/prm/force_torque | 六维力/力矩传感器 |
相机 (Camera)
命名空间:robonix/prm/camera
概述
相机原语抽象视觉传感器,支持 RGB、深度、RGB-D、红外及内参等接口。厂商根据硬件能力实现子集:仅 RGB 相机实现 rgb、intrinsics;纯深度相机只实现 depth(可选 intrinsics);RGB-D 相机实现 rgb、depth、rgbd、intrinsics。
接口列表
| 接口 | 原语类型 | 载荷 | 说明 |
|---|---|---|---|
rgb | stream | sensor_msgs/msg/Image | RGB 图像流 |
depth | stream | sensor_msgs/msg/Image | 深度图(16-bit 或 32-bit) |
rgbd | stream | robonix_msg/msg/RGBD | RGB + 深度同步输出 |
ir | stream | sensor_msgs/msg/Image | 红外图像 |
intrinsics | stream | sensor_msgs/msg/CameraInfo | 相机内参(K、D、分辨率等) |
典型组合
| 硬件类型 | 建议实现 |
|---|---|
| 仅 RGB 相机 | rgb, intrinsics |
| 纯深度相机 | depth(+ 可选 intrinsics) |
| RGB-D 相机 | rgb, depth, rgbd, intrinsics |
| 带红外相机 | rgb, ir, intrinsics(可选 depth) |
参考系
相机输出通常以相机光学中心为原点。若需与地图/机器人坐标系对齐,应在接口或上层服务中声明 @frame 注解,或由调用方根据 TF 转换。
底盘 (Base)
命名空间:robonix/prm/base
概述
底盘原语抽象移动机器人底座,涵盖导航、运动控制、位姿估计、里程计、急停、重定位等。不同机器人可能支持不同子集(如仅差速、仅导航、或带路径跟踪控制器)。
接口列表
| 接口 | 原语类型 | 载荷 | 说明 |
|---|---|---|---|
navigate | command | goal: geometry_msgs/msg/PoseStamped | 导航到目标位姿 |
move | command | cmd_vel, odom, status | 速度控制 + 里程计反馈 |
stop | command | req, success | 急停 |
relocalize | command | req, success | 重定位(如 AMCL 重设) |
controller | command | path, success | 路径跟踪控制器 |
pose_cov | stream | geometry_msgs/msg/PoseWithCovarianceStamped | 带协方差的位姿估计 |
odom | stream | nav_msgs/msg/Odometry | 里程计 |
goal_status | query | goal_id → NavigationStatus | 导航目标状态查询 |
battery_ok | query | req → ok | 电池是否正常 |
status | query | req → res | 通用状态字符串 |
joint_state | stream | sensor_msgs/msg/JointState | 底盘关节状态(如万向轮) |
典型组合
| 场景 | 建议实现 |
|---|---|
| 差速底盘 | move, odom, stop |
| 导航底盘(Nav2) | navigate, pose_cov, goal_status, stop |
| 路径跟踪 | controller, pose_cov, odom |
| 带电池监控 | battery_ok |
通用传感器 (Sensor)
命名空间:robonix/prm/sensor
概述
通用传感器原语涵盖点云、激光雷达、IMU 等,与相机、底盘分离,便于厂商按传感器类型独立实现。
接口列表
| 接口 | 原语类型 | 载荷 | 说明 |
|---|---|---|---|
pointcloud | stream | sensor_msgs/msg/PointCloud2 | 3D 点云 |
lidar | stream | sensor_msgs/msg/LaserScan | 2D 激光扫描 |
imu | stream | sensor_msgs/msg/Imu | 惯性测量单元(加速度、角速度、姿态) |
典型组合
| 硬件 | 建议实现 |
|---|---|
| 3D 激光雷达 | pointcloud |
| 2D 激光雷达 | lidar |
| IMU | imu |
| 深度相机 + 点云 | 由 camera 提供 depth,或单独实现 pointcloud |
机械臂 (Arm)
命名空间:robonix/prm/arm
概述
机械臂原语抽象多自由度机械臂,支持末端执行器位姿控制、关节位置控制、关节轨迹执行及关节状态反馈。
接口列表
| 接口 | 原语类型 | 载荷 | 说明 |
|---|---|---|---|
move_ee | command | pose → status | 末端执行器位姿控制 |
move_joint | command | joint_positions → status | 关节位置控制 |
joint_trajectory | command | trajectory → status | 关节轨迹执行 |
state_joint | stream | sensor_msgs/msg/JointState | 关节状态流 |
典型组合
| 场景 | 建议实现 |
|---|---|
| 仅笛卡尔控制 | move_ee, state_joint |
| 仅关节控制 | move_joint, state_joint |
| 轨迹控制 | joint_trajectory, state_joint |
| 全功能 | 以上全部 |
夹爪 (Gripper)
命名空间:robonix/prm/gripper
概述
夹爪原语抽象抓取执行器,支持开合命令、宽度设置及宽度状态反馈。常与机械臂配合使用。
接口列表
| 接口 | 原语类型 | 载荷 | 说明 |
|---|---|---|---|
close | command | req → status | 闭合夹爪 |
open | command | req → status | 张开夹爪 |
set_width | command | width → status | 设置目标宽度 |
state_width | stream | std_msgs/msg/Float64 | 当前宽度 |
典型组合
| 硬件 | 建议实现 |
|---|---|
| 二指夹爪(开/关) | close, open |
| 可调宽度夹爪 | set_width, state_width |
| 全功能 | close, open, set_width, state_width |
力/力矩传感器 (Force-Torque)
命名空间:robonix/prm/force_torque
概述
力/力矩原语抽象六维力/力矩传感器,常用于机械臂腕部或足式机器人足端,用于力控、碰撞检测等。
接口列表
| 接口 | 原语类型 | 载荷 | 说明 |
|---|---|---|---|
wrench | stream | geometry_msgs/msg/WrenchStamped | 力/力矩(含参考系与时间戳) |
典型组合
单接口,实现即提供完整能力。若传感器支持多坐标系输出,可注册多个实例或由上层根据 frame_id 区分。
硬件/服务厂商接入指南
本文档说明相机、机械臂等硬件厂商以及地图等系统服务如何接入 Robonix RIDL 接口。设计参考 Android HAL:厂商实现接口子集,按需注册,无需实现全部。
各硬件类型的接口形态详见 抽象硬件原语。
1. 接入流程概览
- 查阅 RIDL 接口定义(robonix-interfaces/ridl/prm/*)
- 选择本硬件/服务支持的接口子集
- 创建 package,编写 manifest 与 entry
- 实现所选接口的 server/publisher,向 meta API 注册
- rbnx build + rbnx start 构建与运行
代码分工:ridlc 生成 create_*_server、create_*_publisher、RobonixRuntimeStub 等;你只写 main 里的连接、回调实现、挂载与启动。
必备流程:rclpy.init() → grpc.insecure_channel(endpoint) → RobonixRuntimeStub(channel) 得到 runtime_client,用于向 meta 注册 channel。所有 server/publisher 都依赖此 client。
业务逻辑补全:command 用 server.execute = callback 挂载,callback 内处理 Goal、可选 goal_handle.publish_feedback()、返回 Result;query 用 server.start(handler) 传入,handler 内根据 request 填 response;stream 在定时器/循环中 publish(msg),订阅方用 subscriber.start(callback)。详见 ridlc 开发手册 §5。
运行方式:必须通过 rbnx start 启动,不支持直接 python -m 运行。
2. 核心原则:按需实现,无需全量
与 Android HAL 类似:RIDL 的 prm/ 目录下定义了大量原语接口,厂商只需实现本硬件实际支持的接口,不必实现整个目录。
| 场景 | 需实现的接口 | 不必实现 |
|---|---|---|
| 仅 RGB 相机 | rgb, intrinsics | depth, rgbd, ir |
| 纯深度相机 | depth(+ 可选 intrinsics) | rgb, rgbd, ir |
| RGB-D 相机 | rgb, depth, rgbd, intrinsics | ir(若硬件不支持) |
| 机械臂(无夹爪) | move_ee, move_joint, state_joint, joint_trajectory | gripper 全部 |
| 机械臂 + 夹爪 | 上述 + gripper.close, gripper.open 等 | 按硬件能力 |
| 语义地图服务 | robonix/system/map/semantic_query | 其他 system 接口 |
运行时行为:node 启动时,仅对实际实现的接口调用 create_*_server / create_*_publisher 并注册。未实现的接口不会被注册,调用方解析时若目标 node 未提供该接口则失败。
可选:manifest 声明:为便于文档与工具检查,可在 manifest 中可选声明 provides(见 §6)。
3. 相机厂商接入示例
3.1 场景
某厂商提供 RGB-D 相机,支持 rgb、depth、rgbd、intrinsics,不支持 ir。
3.2 目录结构
prm_camera_vendor/
├── robonix_manifest.yaml
├── package.xml # 可选:自定义 ROS2 依赖(如 sensor_msgs)
└── prm_camera_vendor/
├── __init__.py
└── camera_node.py # entry: prm_camera_vendor.camera_node:main
3.3 manifest
manifestVersion: 1
package:
id: com.vendor.camera.rgbd
name: prm_camera_vendor
version: 0.1.0
vendor: Example Camera Co.
description: RGB-D camera driver implementing prm::camera (rgb, depth, rgbd, intrinsics)
license: MulanPSL-2.0
# 可选:声明本 package 提供的接口,便于文档与工具
# provides:
# - robonix/prm/camera/rgb
# - robonix/prm/camera/depth
# - robonix/prm/camera/rgbd
# - robonix/prm/camera/intrinsics
nodes:
- id: com.vendor.camera.rgbd
type: python
entry: prm_camera_vendor.camera_node:main
3.4 实现要点
代码分工:create_*_publisher 等由 ridlc 生成;你只需写 main 里的连接、定时/循环 publish。
必备流程:rclpy.init() → grpc.insecure_channel(endpoint) → RobonixRuntimeStub(channel) 得到 runtime_client。
stream 无挂载:直接 publisher.publish(msg),在 timer 或循环里调用即可。
多节点 spin:每个 create_*_publisher 返回独立的 rclpy Node,需用 MultiThreadedExecutor 将全部 publisher 加入 executor 后统一 spin,否则只 spin 一个节点会导致其他 publisher 无法正常工作。
# prm_camera_vendor/camera_node.py
import rclpy
from rclpy.executors import MultiThreadedExecutor
from robonix.prm.camera.rgb_stream import create_rgb_publisher
from robonix.prm.camera.depth_stream import create_depth_publisher
from robonix.prm.camera.rgbd_stream import create_rgbd_publisher
from robonix.prm.camera.intrinsics_stream import create_intrinsics_publisher
def main():
rclpy.init()
runtime_client = ... # gRPC stub
node_id = "com.vendor.camera.rgbd"
# 仅注册本硬件支持的 4 个 stream
rgb_pub = create_rgb_publisher(runtime_client, node_id)
depth_pub = create_depth_publisher(runtime_client, node_id)
rgbd_pub = create_rgbd_publisher(runtime_client, node_id)
intrinsics_pub = create_intrinsics_publisher(runtime_client, node_id)
def publish_frame():
rgb, depth = camera.capture()
rgb_pub.publish(rgb)
depth_pub.publish(depth)
rgbd_pub.publish(RGBD(rgb=rgb, depth=depth))
intrinsics_pub.publish(camera.get_intrinsics())
timer = rgb_pub.create_timer(0.5, publish_frame)
executor = MultiThreadedExecutor()
for node in (rgb_pub, depth_pub, rgbd_pub, intrinsics_pub):
executor.add_node(node)
try:
executor.spin()
finally:
timer.cancel()
executor.shutdown()
for node in (rgb_pub, depth_pub, rgbd_pub, intrinsics_pub):
node.destroy_node()
if rclpy.ok():
rclpy.shutdown()
4. 机械臂厂商接入示例
4.1 场景
某厂商提供机械臂 + 夹爪,实现 move_ee、move_joint、state_joint、joint_trajectory、gripper.close、gripper.open。
4.2 目录结构
prm_arm_vendor/
├── robonix_manifest.yaml
├── package.xml # 可选:自定义 ROS2 依赖(如 std_msgs、trajectory_msgs)
└── prm_arm_vendor/
├── __init__.py
├── arm_node.py # 机械臂 command/stream
└── gripper_node.py # 夹爪 command(可选:与 arm 同进程或分 node)
4.3 manifest(单 node 同时提供 arm + gripper)
manifestVersion: 1
package:
id: com.vendor.arm.robot_arm
name: prm_arm_vendor
version: 0.1.0
vendor: Example Arm Co.
description: Robot arm + gripper implementing prm::arm and prm::gripper
license: MulanPSL-2.0
nodes:
- id: com.vendor.arm.robot_arm
type: python
entry: prm_arm_vendor.arm_node:main
4.4 实现要点
代码分工:create_*_server、RobonixRuntimeStub 等由 ridlc 生成;你只需写 main 里的连接、回调实现、挂载与启动。
必备流程:rclpy.init() → grpc.insecure_channel(endpoint) → RobonixRuntimeStub(channel) 得到 runtime_client,用于向 meta 注册 channel。
回调挂载:command server 通过 server.execute = your_callback 挂载;start() 后收到 action 请求时会调用该回调。
多节点 spin:每个 create_*_server 返回独立的 rclpy Node,需用 MultiThreadedExecutor 将全部 server 加入 executor 后统一 spin。若只 rclpy.spin(某一个),其他 server 的 action 请求将无法被处理。
# prm_arm_vendor/arm_node.py
import rclpy
from rclpy.executors import MultiThreadedExecutor
from robonix.prm.arm.move_ee_command import create_move_ee_server
from robonix.prm.arm.joint_trajectory_command import create_joint_trajectory_server
from robonix.prm.gripper.close_command import create_close_server
from robonix.prm.gripper.open_command import create_open_server
def main():
rclpy.init()
runtime_client = ...
node_id = "com.vendor.arm.robot_arm"
# 注册 command servers
move_ee_srv = create_move_ee_server(runtime_client, node_id)
joint_traj_srv = create_joint_trajectory_server(runtime_client, node_id)
close_srv = create_close_server(runtime_client, node_id)
open_srv = create_open_server(runtime_client, node_id)
# 实现 execute 回调,调用真实硬件驱动
move_ee_srv.execute = lambda req: arm_driver.move_to_pose(req.pose)
joint_traj_srv.execute = lambda req: arm_driver.execute_trajectory(req.trajectory)
close_srv.execute = lambda req: gripper_driver.close()
open_srv.execute = lambda req: gripper_driver.open()
move_ee_srv.start()
joint_traj_srv.start()
close_srv.start()
open_srv.start()
# 可选:state_joint stream 发布关节状态,若实现则需加入 executor
# state_pub = create_state_joint_publisher(runtime_client, node_id)
executor = MultiThreadedExecutor()
nodes = (move_ee_srv, joint_traj_srv, close_srv, open_srv)
for node in nodes:
executor.add_node(node)
try:
executor.spin()
finally:
executor.shutdown()
for node in nodes:
node.destroy_node()
if rclpy.ok():
rclpy.shutdown()
5. 地图服务接入示例
5.1 场景
实现 robonix/system/map/semantic_query 的 query server,提供语义地图查询。
5.2 目录结构
map_semantic_service/
├── robonix_manifest.yaml
├── package.xml # 可选:自定义 ROS2 依赖
└── map_semantic_service/
├── __init__.py
└── semantic_server.py
5.3 manifest
manifestVersion: 1
package:
id: com.robonix.service.map_semantic
name: map_semantic_service
version: 0.1.0
vendor: robonix
description: Semantic map query service implementing robonix/system/map/semantic_query
license: MulanPSL-2.0
nodes:
- id: com.robonix.map.semantic
type: python
entry: map_semantic_service.semantic_server:main
5.4 实现要点
代码分工:create_semantic_query_server 由 ridlc 生成;你只需写 main 里的连接、handler 实现、start(handler)。
必备流程:同上(rclpy.init、grpc channel、runtime_client)。
handler 接法:query server 通过 server.start(handler) 传入,收到请求时调用 handler(request, response)。
# map_semantic_service/semantic_server.py
from robonix.system.map.semantic_query_query import create_semantic_query_server
def main():
runtime_client = ...
node_id = "com.robonix.map.semantic"
server = create_semantic_query_server(runtime_client, node_id)
def handler(request, response):
filter_str = request.filter.data
# 查地图、过滤,填充 response.objects (Object[])
response.objects = map_backend.query(filter_str)
return response
server.start(handler)
rclpy.spin(server)
6. Manifest 可选字段:provides
为便于文档与工具(如列出某 package 提供的接口),可在 manifest 中可选声明:
provides:
- robonix/prm/camera/rgb
- robonix/prm/camera/depth
- robonix/prm/camera/rgbd
- robonix/prm/camera/intrinsics
- 非强制:运行时以实际注册为准;未在 provides 中声明但已注册的接口仍可用。
- 建议:厂商填写 provides,与实现保持一致,便于集成方查阅。
7. 与 Android HAL 的类比
| Android | Robonix |
|---|---|
| HIDL 接口定义 | RIDL 接口定义(ridl/prm/, ridl/system/) |
| 厂商实现 HAL | 厂商实现 package,提供 server/publisher |
| 按能力实现(LEGACY/LIMITED/FULL) | 按硬件能力实现接口子集 |
| 通过 hw_module_t 加载 | 通过 rbnx start 启动 node,向 meta API 注册 |
| 框架通过 Binder 调用 HAL | 调用方通过 Resolve* 解析 channel 后 ROS 调用 |
8. 参考
- 示例 package:
rust/examples/prm_camera_vendor/、rust/examples/prm_arm_vendor/、rust/examples/map_semantic_service/ - Package 开发指南
- ridlc 开发手册
- RFC001: RIDL
- RFC002: Package
RFC 001: Robonix Interface Definition Language (RIDL)
| 版本 | 日期 | 作者 |
|---|---|---|
| 0.1 | 2026-03-11 | 韩喻泷 |
1. 目标与范围
RIDL 定义接口契约:接口身份(命名空间 + 名称)、通信语义(stream/command/query)、载荷类型、接口版本。RIDL 仅描述"有什么接口、长什么样";不定义 package 构建、channel 名或部署拓扑,channel 由运行时分配(§7)。
不在本 RFC 内:Package 目录与 manifest(RFC002)、channel 分配实现细节、ridlc 代码生成与通信实现(见开发手册)。
2. 命名空间
格式:robonix/{domain}/{subdomain}/...,层级用 / 分隔,小写。
示例:robonix/prm/base、robonix/system/debug/ping、robonix/system/map/semantic_query。
3. 语法速览
非正式说明,便于快速上手。
3.1 接口语法
command 接口名 [注解...] {
input 字段名 类型 [注解...]; // 至少 input 或 result 之一
[output 字段名 类型 [注解...];] // 可选,进度反馈
[result 字段名 类型 [注解...];]
version 版本号;
}
stream 接口名 [注解...] {
output 字段名 类型 [注解...]; // 或 input,二选一
version 版本号;
}
query 接口名 [注解...] {
request 字段名 类型 [注解...];
response 字段名 类型 [注解...];
version 版本号;
}
注解为 @key 或 @key(param=value),可多个。接口级写在接口名后、{ 前;字段级写在字段类型后、; 前。@desc("...") 为 LLM/Agent 友好描述,与 @frame、@interruptible 等并列。
3.2 文件结构
namespace robonix/域/子域
[import 类型路径;]...
接口定义(见上)
3.3 规则摘要
- 关键词:
namespace、import、stream、command、query、input、output、result、request、response、version。注解@desc等。 - 文件规则:一文件一接口;接口名必须与文件名一致(如
depth.ridl定义stream depth)。 - 命名:namespace 小写、用
/分隔;接口名、字段名小写或 snake_case;类型用package/msg/Name或 import 后短名。 - 字段:以
;结尾。
4. 通信原语
RIDL 四类原语均为传输无关的语义抽象,具体实现由 codegen/运行时选择。
| 原语 | 语义 | 典型用法 |
|---|---|---|
| stream | 单向数据流 | 位姿/传感器、控制指令 |
| command | 长任务:输入 + 进度 + 结果 | 运动、skill 执行 |
| query | 同步请求-响应 | 状态查询、ping |
4.1 query
一对一、同步请求-响应。RIDL 结构:request、response 各声明字段,类型引用 message。
4.2 command
带输入、可选进度反馈、最终结果的长任务。RIDL 结构:input、output(进度)、result。
4.3 stream
单向:每个 stream 仅 output 或仅 input。以 provider 为视角:output = provider 发布;input = provider 订阅。
5. 类型系统
- RIDL 不定义独立 IDL,引用兼容 ROS IDL 规范的类型(
package/msg/Name、package/srv/Name)。 - Wire 格式:具体序列化与传输由实现决定。
- 数组:
[]后缀,如robonix_msg/msg/Object[]。 - robonix_msg:定义 Object、Relation、FrameMapping、SkillInstance、Dict 等语义结构,RIDL 中优先使用。
5.1 类型引用与 import
import 引入类型后,字段中可使用短名(仅类型名),无需写全称:
namespace robonix/prm/base
import geometry_msgs/msg/PoseStamped
command navigate @interruptible {
input goal PoseStamped; // 短名,等价于 geometry_msgs/msg/PoseStamped
version 1.0;
}
全称 geometry_msgs/msg/PoseStamped 仍可使用。短名与全称可混用。
5.2 注解
格式 @key 或 @key(param=value)。接口级注解写在接口名后、{ 前;字段级注解写在字段类型后、; 前。
| 注解 | 作用域 | 参数 | 含义 |
|---|---|---|---|
@desc | 接口/字段 | ("...") | LLM/Agent 友好:接口用途或字段含义、格式、示例,供 AI 理解与正确构造请求 |
@requires_interface | 接口 | ("ns/iface1", "ns/iface2") | 实现本接口时必须同时实现所列接口 |
@frame | 字段 | ("map") | 字段(位姿/点)的参考系,收发双方据此对齐坐标系 |
@interruptible | 接口 | 无 | command 支持运行时中断(如 cancel);ROS action 有 cancel API 但 IDL 不声明 |
// navigate.ridl
namespace robonix/hal/base
import geometry_msgs/msg/PoseStamped
command navigate @interruptible @desc("Navigate robot to goal pose.") {
input goal PoseStamped @desc("Target pose (frame_id, position, orientation)");
version 1.0;
}
// arm_control.ridl
namespace robonix/hal/arm
import robonix_msg/msg/ArmCommand
import robonix_msg/msg/CommandResult
command arm_control @requires_interface("robonix/hal/arm/get_params") @desc("Arm control command.") {
input cmd ArmCommand @desc("Arm command payload");
result status CommandResult @desc("Success/failure");
version 1.0;
}
// pose.ridl
namespace robonix/hal/localization
import geometry_msgs/msg/PoseStamped
stream pose @desc("Localization pose stream.") {
output pose PoseStamped @frame("map") @desc("Pose in map frame");
version 1.0;
}
6. 语法示例
namespace robonix/system/debug
import std_msgs/msg/String
query ping @desc("Echo test; verify connectivity.") {
request data String @desc("Arbitrary string to echo");
response data String @desc("Same string echoed back");
version 1.0;
}
namespace robonix/system/map
import std_msgs/msg/String
import robonix_msg/msg/Object
query semantic_query @desc("Query objects in semantic map by filter.") {
request filter String @desc("JSON filter, e.g. {\"room\":\"kitchen\"}");
response objects Object[] @desc("Objects with id, label, pose");
version 1.0;
}
namespace robonix/hal/base
import geometry_msgs/msg/Twist
import nav_msgs/msg/Odometry
import robonix_msg/msg/CommandResult
command motion_cmd @desc("Direct velocity control.") {
input cmd Twist @desc("Linear/angular velocities");
output odom Odometry @desc("Current odometry");
result status CommandResult @desc("Success/failure");
version 1.0;
}
namespace robonix/hal/localization
import geometry_msgs/msg/PoseWithCovarianceStamped
stream pose @desc("Pose with covariance stream.") {
output state PoseWithCovarianceStamped @desc("Pose + covariance matrix");
version 1.0;
}
7. 运行时 channel
channel 为 interface 在运行时的具体通信端点,由 robonix-server 分配,RIDL 与 manifest 不写 channel 名(§7)。
- Server 注册:node 提供 interface 时向 meta API 注册,获得 channel。
- Client 解析:调用方指定 (node_id, interface_id),通过 meta API 解析得到 channel 后发起通信。
channel 分配与生命周期由运行时与生成代码负责。
8. 接口版本与演进
每个接口声明 version(如 1.0)。不兼容变更应递增版本或新命名空间。
9. 与 RFC002 的边界
RFC001:接口契约(RIDL 语法、原语、类型、channel 概念)。RFC002:package、manifest、node、rbnx 构建与启停。二者通过 (node_id, interface_id) 在运行时衔接。
10. 原语领域(prm)
robonix/prm/* 命名空间下的接口为抽象硬件原语。厂商按需实现子集,无需实现全部;同一领域内硬件形态各异(如纯深度相机、RGB 相机、红外相机),不存在统一最小契约。运行时通过 meta 解析 channel,调用方按需查询可用接口。
10.1 ROS IDL 无法做到的部分
| 能力 | ROS IDL | RIDL |
|---|---|---|
| 消息/服务类型定义 | ✓ | 引用 ROS msg/srv |
| 通信语义(stream/command/query) | ✗ 仅 topic/service/action | ✓ 传输无关抽象 |
| 接口身份(命名空间 + 名称) | ✗ 依赖 topic 名约定 | ✓ 显式 namespace |
| 接口间依赖(requires_interface) | ✗ 无 | ✓ @requires_interface |
| 参考系声明(frame) | ✗ 无 | ✓ @frame |
| 可中断性(interruptible) | ✗ action 有 cancel 但 IDL 不声明 | ✓ @interruptible |
| 运行时 channel 分配 | ✗ 固定 topic 名 | ✓ 由 meta 分配 |
ROS IDL(.msg/.srv/.action)只描述载荷结构,不描述接口契约或通信语义。RIDL 在原语层补充这些能力。
RFC 002: Robonix Package Management
Robonix 包管理规范
| 版本 | 日期 | 作者 |
|---|---|---|
| 0.1 | 2026-03-11 | 韩喻泷 |
- 1. 目标与术语
- 2. Package 目录与 manifest 文件
- 3. robonix_manifest.yaml 结构
- 4. rbnx 命令
- 5. 构建与运行产物(约定)
- 6. 设计原则
- 7. 与 RFC001 的边界
1. 目标与术语
1.1 目标
Package 即“一个可构建、可启动的应用”的交付单元。本 RFC 规定:
- Package 的身份与元数据(通过
robonix_manifest.yaml描述)。 - Node 与 entry 的语义:一个 package 可声明多个 node,每个 node 对应一个进程,由一个启动 entry 描述如何拉起。
- 构建与启停:仅通过 rbnx 工具完成,无单独 deployment 规范(不引入“部署描述文件”或“部署阶段”)。
Manifest 核心是版本、权限(预留)以及各 node 的启动 entry;不要求声明“实现了哪些 RIDL 接口”(类比 Android:不要求声明实现了多少 AIDL)。任意 package(标准服务、原语实现、skill、用户级应用)均通过同一套 RIDL 接口框架通信,只需约定好接口即可。
1.2 术语
- Package:可构建、可启动的单元;根目录包含
robonix_manifest.yaml,并可包含源码、依赖描述等。 - Node:运行起来的一个进程。在通信模型中,node 是 stream/query/command 的目标标识(调用方指定“发给哪个 node”)。
- Node id:在系统内唯一标识一个 node;与 manifest 中
nodes[].id对应,也是通信时解析 channel 所用的目标标识,建议使用com.syswonder.xxx等反向域名风格。 - Entry:描述如何启动一个 node,格式为
模块:函数(如python_ping_client.call_ping:main),由 rbnx start 时用于构造启动命令。
2. Package 目录与 manifest 文件
2.1 约定
- Package 根目录:包含且仅包含一个 robonix_manifest.yaml(文件名固定)。
- 其他内容(Python 包、C++ 包、资源文件等)由具体构建策略决定;rbnx build 会依据 manifest 与当前实现执行校验、colcon 构建等,输出到 package 局部的构建目录(如
rbnx-build),不要求统一顶层子目录名。
2.2 文件位置
robonix_manifest.yaml 必须位于 package 的根目录。rbnx 通过该文件识别目录为一个 package(与 -p 解析规则配合)。
3. robonix_manifest.yaml 结构
3.1 顶层结构
manifestVersion: 1
package:
id: <package 稳定标识>
name: <包名>
version: <版本号>
vendor: <厂商>
description: <简短描述>
license: <许可证 SPDX 或名称>
# 可选
# permissions: []
nodes:
- id: <node id,建议 com.xxx.xxx>
type: python # 可选,默认 python
entry: <模块:函数>
# 可有多项,即多个 node;每次 rbnx start -p -n <node_id> 只启动一个
- manifestVersion:当前固定为
1,用于将来 schema 演进。 - package:必填,描述 package 身份与版本。
- permissions:可选,预留;将来可声明能力/权限需求。
- nodes:必填且至少一项;每项描述一个 node 的启动 entry(id、type、entry)。每次
rbnx start -p <package> -n <node_id>只启动一个 node。
3.2 package 字段说明
| 字段 | 必填 | 说明 |
|---|---|---|
package.id | ✓ | 稳定标识,可用于仓库、依赖引用等;建议反向域名,如 com.robonix.example.ping_client。 |
package.name | ✓ | 包名,用于构建产物、日志、rbnx 解析等;通常与目录名或主模块名一致。 |
package.version | ✓ | 版本号,语义化版本或项目约定格式。 |
package.vendor | ✓ | 厂商或组织名。 |
package.description | ✓ | 简短文字描述。 |
package.license | ✓ | 许可证标识(如 MulanPSL-2.0)或名称。 |
3.3 nodes(node 列表)字段说明
| 字段 | 必填 | 说明 |
|---|---|---|
nodes | ✓ | Node 列表;至少一项。每项对应一个进程(node)。rbnx start -p <package> -n <node_id> 每次只启动一个 node。 |
nodes[].id | ✓ | Node id。运行时的进程标识,也是 stream/query/command 通信时的目标节点标识。必须在系统内唯一,建议 com.syswonder.xxx 风格。 |
nodes[].type | - | 节点类型,当前支持 python;可省略,默认 python。 |
nodes[].entry | ✓ | 该 node 的启动入口,格式 模块:函数(如 mypkg.main:main)。rbnx start 会据此构造启动命令。 |
3.4 Node id 与通信的关系
Manifest 中的 node id(nodes[].id)与运行时通信一致:
- 当某 node 作为 server 提供某 RIDL 接口时,会使用该 node id 向 robonix-server 注册 channel。
- 当调用方要调用某 node 的某接口时,需指定 (node_id, interface_id);meta API 据此解析出 channel,再完成 ROS 调用。
因此 node id 的命名应全局唯一、可读、稳定,推荐反向域名,如 com.syswonder.hal_arm、com.syswonder.map_semantic。
3.5 完整示例
manifestVersion: 1
package:
id: com.robonix.example.ping_client
name: python_ping_client
version: 0.1.0
vendor: robonix
description: Python 客户端,调用 robonix/system/debug/ping
license: MulanPSL-2.0
# 节点列表:每个 node 一个启动 entry;node id 即通信时的目标标识
nodes:
- id: com.syswonder.example.ping_client
type: python
entry: python_ping_client.call_ping:main
4. rbnx 命令
4.1 概览
构建与启停均由 rbnx 完成,无单独“deploy”步骤或部署描述文件。
rbnx build -p <package> # 构建
rbnx start -p <package> -n <node_id> # 启动指定 node,阻塞直到进程退出
4.2 -p 参数:package 解析
-p 可为以下之一:
- 路径:指向包含
robonix_manifest.yaml的目录(绝对或相对路径)。 - 名字:rbnx 在以下位置按名称查找带
robonix_manifest.yaml的子目录(名字即该子目录名或 package.name):- 当前工作目录下的
examples/<name> - 当前工作目录下的
<name> - 当前工作目录下的
rust/examples/<name>
- 当前工作目录下的
未找到则报错并提示已尝试的路径。
4.3 build
- 校验 manifest 语法与必填字段。
- 根据当前实现执行构建(如 colcon 构建、安装依赖、生成代码等);构建产物落在 package 局部的构建目录(如
rbnx-build)及 install 空间,供 start 使用。 - 具体步骤(如是否调用 ridlc、colcon 包列表)以实现为准;本 RFC 仅约定“由 rbnx 统一入口、含 manifest 校验”。
4.4 start
- 解析
-p得到 package 根目录,-n指定要启动的 node id;加载 manifest。 - 根据该 node 的
type与entry构造启动命令(如 Python:source 环境 +python3 -m <module>等),在 package 或约定的工作目录下启动进程;进程在运行时以该 node 的 id 作为 node id(例如向 meta API 注册时使用)。阻塞直到该进程退出。 - 可选:通过
--endpoint或环境变量ROBONIX_META_GRPC_ENDPOINT指定 robonix-server 的 meta gRPC 地址,默认如127.0.0.1:50051。
5. 构建与运行产物(约定)
- 构建目录:可在 package 根下或同级目录使用固定子目录(如
rbnx-build)存放 colcon workspace、build/install 等;具体以实现为准。 - 日志与进程信息:rbnx start 可将日志、PID 等写入约定位置(如
rbnx-deploy/logs或等价路径),便于排查与 stop 时匹配进程。 - 环境:start 时需注入 ROS/colcon 环境(如 source setup)、以及 meta gRPC 地址等,以便 node 能注册与解析 channel。
6. 设计原则
- Manifest 聚焦:版本、权限(预留)、各 node 的启动 entry;不要求声明“实现了哪些 RIDL 接口”。
- 无 deployment 规范:不引入单独的部署描述文件或部署阶段;类似“安装后直接启动”的模型,由 rbnx 按 package 启停即可。
- Node 与通信一致:manifest 中的 node id 即通信时的目标标识,保证“谁启动、谁被调用”可追溯。
- 扩展预留:permissions、launchProfiles、config 等可在不破坏现有 schema 的前提下后续迭代。
7. 与 RFC001 的边界
- RFC001:接口契约(RIDL、channel 概念);不涉及 package、manifest、rbnx。
- RFC002:Package、manifest、node、entry、rbnx build/start/stop;不涉及 RIDL 语法或 channel 分配算法。
两者在运行时的衔接:node 进程使用 manifest 中的 node id 向 robonix-server 注册或解析 channel,调用方通过 (node_id, interface_id) 解析 channel 并通信。