robonix

欢迎使用 Robonix!

犀照世界 灵通万物 为机器筑心 为具身立智

Robonix 目标是探索如何从系统层面构造具身智能“大脑”, 目标是为具身智能大脑提供跨越异构硬件的系统底座,支持具身智能机器人的用户/开发者方便地开发和部署自己的新模型,让自己的机器人能容易地掌握新技能、完成新任务。Robonix 将 AI 模型和具身硬件“软硬解耦”,将模型视为程序,希望支持模型“一次训练,任何机器部署运行”,同时针对"感知–理解-规划-行动"过程的数据处理以及环境交互等共性问题,设计 “感知-互联-认知-控制” 具身智能系统服务框架,方便模型和技能的开发和运行。我们期望 Robonix 能帮助各种机器人容易地构筑智脑,推动具身智能软硬件独立发展生态,让具身智能机器人更好用和易用。

快速开始

本节说明:获取源码与编译、Robonix 术语与通信模型、如何启动 robonix-server、如何使用 ridlc 与生成代码、如何创建 package 并开发业务逻辑、如何使用 rbnx 编译/运行/停止 package。

环境要求

  • Ubuntu 22.04 + ROS 2 Humble
  • Rust (rustup)
  • Python 3.10+
  • ros-humble-example-interfacesros-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 releasemake build,或安装时 BUILD_MODE=release make install
  • 单独编译某一项:make build-ridlcmake build-climake build-server;单独安装:make install-ridlcmake install-climake 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_demostream位姿流:stream_server 发布、stream_client 订阅,两进程通过 robonix/prm/base/pose_cov 通信
query_demoquery语义查询:semantic_server 提供、semantic_client 调用,通过 robonix/system/map/semantic_query 通信
skill_democommandgreet 命令:skill_server 提供、skill_client 调用,通过 package-local skill_demo/skill/greet 通信
python_ping_clientquery 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_cppRUST_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/debugrobonix.system.debug
robonix/prm/baserobonix.prm.base
robonix/system/maprobonix.system.map
robonix/system/skillrobonix.system.skill

规则:robonix/a/b/c -> robonix.a.b.c(斜杠变点号)。

3.3 接口类型 -> 生成代码速查

原语生成文件名Python 主要函数
query{name}_query.pycreate_{name}_client, create_{name}_server
stream{name}_stream.pycreate_{name}_publisher, create_{name}_subscriber
command{name}_command.pycreate_{name}_client, create_{name}_server

示例:query pingrobonix.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 步骤总览

  1. 确定要实现的接口:在 RIDL 里找到对应 query/command/stream(如 robonix/system/debug/pingrobonix/prm/base/pose_cov),得到生成模块与函数名(如 query 的 create_ping_server/create_ping_client,stream 的 create_pose_cov_publisher/create_pose_cov_subscriber)。
  2. 创建目录:在任意位置新建 package 根目录,包含 robonix_manifest.yaml 以及与包名同名的 Python 子包(如 python_ping_client/python_ping_client/)。setup.pysetup.cfgrbnx build 自动生成;package.xml 可选(有则使用,可添加自定义 ROS2 依赖;无则自动生成)。rbnx -p 可传该目录的路径或 package 名(按约定查找,见下文)。
  3. 写 manifest:package(id、name、version、vendor、description、license)+ nodes(每项一个 node:id 建议 com.syswonder.xxxentry模块:函数,如 python_ping_client.call_ping:main)。
  4. 写业务代码:在 entry 指向的模块里连接 meta API(gRPC),用生成代码的 create_*_servercreate_*_client,在 handler/execute 或 call 处实现逻辑。
  5. 用 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_pingmain
  • 业务逻辑:python_ping_client/call_ping.pymain() 中连接 ROBONIX_META_GRPC_ENDPOINTcreate_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_trajectoryserver.execute = fn,fn 内收 trajectory、控机械臂、返回 Result
语义地图(query server)robonix/system/map/semantic_queryserver.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_cppps 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。

使用流程

  1. 启动 robonix-server:./start_server
  2. 调用 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日志级别,如 infodebug

确认运行

  • 日志中出现 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_querymap_managermodel_managerskill_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 depthmove.ridl 定义 command move。类似 Java:一个文件一个 public 类,类名与文件名一致。
  • 接口语法command/stream/query 接口名 [注解...] { 字段... };字段为 方向 字段名 类型 [注解...];。注解可多个,如 @desc@frame。完整语法见 RFC001 §3.1

1. 通信实现映射

当前 ridlc 采用 ROS 2 作为后端。各原语与 ROS 2 / gRPC 的映射如下:

原语ROS 2gRPC(可选实现)
streamtopicserver/client streaming
commandaction双向流或 server streaming
queryserviceunary RPC

2. 代码生成规范

2.1 命名规律

原语生成文件Python 工厂Rust 模块
query{name}_query.pycreate_{name}_client, create_{name}_server{name}_query
stream{name}_stream.pycreate_{name}_publisher, create_{name}_subscriber{name}_stream
command{name}_command.pycreate_{name}_client, create_{name}_server{name}_command

2.2 命名空间映射

  • robonix/a/b/c → Python 包 robonix.a.b.c
  • robonix/a/b/c → Rust 模块 robonix_a_b_c
  • ROS 2 类型名:namespace 去掉 robonix/ 后按层级 PascalCase 拼接 + 接口名,如 robonix/system/debug + pingSystemDebugPing

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(请求-响应)

角色补全位置补全方式说明
Serverserver.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)构造消息并发布
Subscribersubscriber.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。

角色补全位置补全方式说明
Serverserver.execute赋值 server.execute = your_execute 或子类重写处理 Goal,可选发 Feedback,返回 Result
Client无回调(简单用法)调用 client.send(request)goal_handleget_result_async()rclpy.spin_until_future_complete
Clientsend_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_idfeedback),不是裸 Feedback。访问自定义 output 字段需:fb_msg.feedback.feedback.<字段名>(第一个 feedback 为 FeedbackMessage 的字段,第二个为 action Feedback 中 output 字段名,如 progress)。


5.4 快速对照表

原语Server/Provider 补全Client/Consumer 补全
Queryserver.start(handler)handler(request, response) -> response构造 Requestclient.call(request) → 处理 Response \| None
Streampublisher.publish(msg)(在定时器或业务逻辑中调用)subscriber.start(callback)callback(msg) 处理消息
Commandserver.execute = fnfn(request, goal_handle) -> result;可选 goal_handle.publish_feedback(fb)构造 Goalclient.send(request)send_goal_async(..., feedback_callback=on_fb)goal_handle.get_result_async()rclpy.spin_until_future_completewrapped.result;feedback 回调内用 fb_msg.feedback.feedback.<字段名> 访问 output

Package 开发指南

本文档详细说明:如何从零创建一个 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/pingrobonix/prm/arm/joint_trajectory)。若不清楚,见下文“从 RIDL 到生成代码的映射”或 抽象硬件原语

1.3 谁可以用这套框架通信

不仅是 skill,运行在系统之上的任意进程(不属于标准服务、原语实现、skill 的“用户级”应用)都可以用同一套 RIDL 通信框架相互通信,只需约定好 RIDL 接口即可。类比 Android:系统服务、HAL、用户 app 均可通过 AIDL 通信;Robonix 中,robonix-server、prm 厂商、skill、以及任意自定义 package 都通过 RIDL 接口通信。

示例原语说明
stream_demostreamstream_server 发布位姿,stream_client 订阅;两进程通过 robonix/prm/base/pose_cov 通信
query_demoquerysemantic_server 提供语义查询,semantic_client 调用;通过 robonix/system/map/semantic_query 通信
skill_democommandskill_server 提供 greet 命令,skill_client 调用;通过 package-local skill_demo/skill/greet 通信
python_ping_clientquery client调用 robonix-server 内置 ping;通过 robonix/system/debug/ping 通信

2. 从 RIDL 到生成代码的映射

2.1 RIDL 命名空间 -> Python 模块路径

RIDL namespacePython 导入路径
robonix/system/debugrobonix.system.debug
robonix/prm/baserobonix.prm.base
robonix/prm/armrobonix.prm.arm
robonix/system/maprobonix.system.map
robonix/system/skillrobonix.system.skill

规则:robonix/a/b/c -> robonix.a.b.c(斜杠变点号)。

2.2 接口类型 -> 文件名与函数名

原语RIDL 名示例生成文件名主要函数
querypingping_query.pycreate_ping_client, create_ping_server
streamposepose_stream.pycreate_pose_publisher, create_pose_subscriber
commandmotion_cmdmotion_cmd_command.pycreate_motion_cmd_client, create_motion_cmd_server

2.3 快速查表:常见接口

RIDL 接口 IDPython 导入服务端/发布方客户端/订阅方
robonix/system/debug/pingfrom robonix.system.debug.ping_query import create_ping_servercreate_ping_server(runtime_client, node_id)create_ping_client(runtime_client, requester_id, target)
robonix/system/map/semantic_queryfrom robonix.system.map.semantic_query_query import create_semantic_query_servercreate_semantic_query_server(runtime_client, node_id)create_semantic_query_client(...)
robonix/prm/base/movefrom robonix.prm.base.move_command import create_move_servercreate_move_server(runtime_client, node_id)create_move_client(...)
skill_demo/skill/greet (package-local)from skill_demo.skill.greet_command import create_greet_servercreate_greet_server(runtime_client, node_id)create_greet_client(...)
robonix/prm/base/pose_covfrom robonix.prm.base.pose_cov_stream import create_pose_cov_publishercreate_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_interfacescommon_interfaces 等),不依赖系统 ROS msg 包。

  • 来源robonix-interfaces/lib/rcl_interfacesrobonix-interfaces/lib/common_interfaces(如 builtin_interfacesstd_msgsgeometry_msgsaction_msgsnav_msgssensor_msgstrajectory_msgs 等)
  • 构建流程:ridlc 将上述包复制到 workspace 的 vendor/,colcon 在 vendor 中构建,产物进入 install 空间
  • 使用方式:与系统包完全一致,例如 from std_msgs.msg import Stringfrom geometry_msgs.msg import PoseWithCovarianceStamped,字段定义以本仓库为准

自定义 package.xml:若需使用其他 ROS2 系统包(如 sensor_msgsgeometry_msgs 等 apt 安装的包),可在 package 根目录添加 package.xml,在其中声明 <depend>。rbnx build 会使用该文件,不会覆盖。示例见 rust/examples/prm_camera_vendor/package.xmlrust/examples/skill_demo/package.xml


3. 创建 package 的完整步骤

步骤一:确定要实现的接口

  • 若做 query 的 server:在 RIDL 里找到对应 query(如 robonix/system/map/semantic_query),记下 namespace 与接口名,得到 Python 模块 robonix.system.map.semantic_query_querycreate_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_streamcreate_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.pysetup.cfgresource/rbnx build 根据 manifest 自动生成,无需手写。
  • package.xml:若源码中已有,则直接使用(可添加自定义 ROS2 依赖如 std_msgssensor_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许可证
nodesnode 列表,至少一项
nodes[].idnode 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())必须完成:
    1. 连接 robonix meta API(gRPC),得到 runtime_clientRobonixRuntimeStub)。
    2. 若为 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,详见 厂商接入指南
    3. 若为 client:用 create_*_client(runtime_client, requester_id, target) 创建客户端,构造请求并 client.call(req, timeout_sec=...),打印或处理响应后退出。

目录结构关系示例(以包名 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),响应填 objectsrobonix_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.objectsrobonix_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.messageresult.response.success


小结:生成代码负责向 robonix-server 注册/解析 channel 和 ROS 绑定;你只在 query 的 handler、command 的 execute、或 client 的 call 之后写业务逻辑。node_id 必须与 manifest 中对应 component 的 id 一致。

步骤五:用 rbnx 构建与运行

rbnx build 会根据 manifest 自动生成 setup.pysetup.cfgresource/package.xml:若源码中已有则使用(可添加自定义 ROS2 依赖,如 std_msgssensor_msgsgeometry_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_trajectorycreate_joint_trajectory_serverexecute:收 trajectory,控机械臂,返回 CommandResult常驻,rbnx start
语义地图(query)robonix/system/map/semantic_querycreate_semantic_query_serverstart(handler):request.filter -> 查地图 -> response.objects(Object[])常驻,rbnx start
Skill 节点(command)skill_demo/skill/greet(package-local)create_greet_serverexecute:typed request/result(GreetRequest/GreetResult)常驻,rbnx start
位姿/传感器流(stream)robonix/prm/base/pose_covcreate_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/ProviderClient/Consumer
queryserver.start(handler),handler 内填 response构造 Request → client.call(request) → 处理 Response \| None
stream定时器/循环中 publish(msg);subscriber 用 start(callback)subscriber.start(callback),callback(msg) 内处理
commandserver.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_completewrapped.result;feedback 内用 fb_msg.feedback.feedback.<字段名> 访问 output

详见 ridlc 开发手册 §5


8. 参考

  • 完整示例:本仓库 rust/examples/python_ping_client(query client)、query_demostream_demoskill_demoprm_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/cameraRGB、深度、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 相机实现 rgbintrinsics;纯深度相机只实现 depth(可选 intrinsics);RGB-D 相机实现 rgbdepthrgbdintrinsics

接口列表

接口原语类型载荷说明
rgbstreamsensor_msgs/msg/ImageRGB 图像流
depthstreamsensor_msgs/msg/Image深度图(16-bit 或 32-bit)
rgbdstreamrobonix_msg/msg/RGBDRGB + 深度同步输出
irstreamsensor_msgs/msg/Image红外图像
intrinsicsstreamsensor_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

概述

底盘原语抽象移动机器人底座,涵盖导航、运动控制、位姿估计、里程计、急停、重定位等。不同机器人可能支持不同子集(如仅差速、仅导航、或带路径跟踪控制器)。

接口列表

接口原语类型载荷说明
navigatecommandgoal: geometry_msgs/msg/PoseStamped导航到目标位姿
movecommandcmd_vel, odom, status速度控制 + 里程计反馈
stopcommandreq, success急停
relocalizecommandreq, success重定位(如 AMCL 重设)
controllercommandpath, success路径跟踪控制器
pose_covstreamgeometry_msgs/msg/PoseWithCovarianceStamped带协方差的位姿估计
odomstreamnav_msgs/msg/Odometry里程计
goal_statusquerygoal_id → NavigationStatus导航目标状态查询
battery_okqueryreq → ok电池是否正常
statusqueryreq → res通用状态字符串
joint_statestreamsensor_msgs/msg/JointState底盘关节状态(如万向轮)

典型组合

场景建议实现
差速底盘move, odom, stop
导航底盘(Nav2)navigate, pose_cov, goal_status, stop
路径跟踪controller, pose_cov, odom
带电池监控battery_ok

通用传感器 (Sensor)

命名空间:robonix/prm/sensor

概述

通用传感器原语涵盖点云、激光雷达、IMU 等,与相机、底盘分离,便于厂商按传感器类型独立实现。

接口列表

接口原语类型载荷说明
pointcloudstreamsensor_msgs/msg/PointCloud23D 点云
lidarstreamsensor_msgs/msg/LaserScan2D 激光扫描
imustreamsensor_msgs/msg/Imu惯性测量单元(加速度、角速度、姿态)

典型组合

硬件建议实现
3D 激光雷达pointcloud
2D 激光雷达lidar
IMUimu
深度相机 + 点云由 camera 提供 depth,或单独实现 pointcloud

机械臂 (Arm)

命名空间:robonix/prm/arm

概述

机械臂原语抽象多自由度机械臂,支持末端执行器位姿控制、关节位置控制、关节轨迹执行及关节状态反馈。

接口列表

接口原语类型载荷说明
move_eecommandpose → status末端执行器位姿控制
move_jointcommandjoint_positions → status关节位置控制
joint_trajectorycommandtrajectory → status关节轨迹执行
state_jointstreamsensor_msgs/msg/JointState关节状态流

典型组合

场景建议实现
仅笛卡尔控制move_ee, state_joint
仅关节控制move_joint, state_joint
轨迹控制joint_trajectory, state_joint
全功能以上全部

夹爪 (Gripper)

命名空间:robonix/prm/gripper

概述

夹爪原语抽象抓取执行器,支持开合命令、宽度设置及宽度状态反馈。常与机械臂配合使用。

接口列表

接口原语类型载荷说明
closecommandreq → status闭合夹爪
opencommandreq → status张开夹爪
set_widthcommandwidth → status设置目标宽度
state_widthstreamstd_msgs/msg/Float64当前宽度

典型组合

硬件建议实现
二指夹爪(开/关)close, open
可调宽度夹爪set_width, state_width
全功能close, open, set_width, state_width

力/力矩传感器 (Force-Torque)

命名空间:robonix/prm/force_torque

概述

力/力矩原语抽象六维力/力矩传感器,常用于机械臂腕部或足式机器人足端,用于力控、碰撞检测等。

接口列表

接口原语类型载荷说明
wrenchstreamgeometry_msgs/msg/WrenchStamped力/力矩(含参考系与时间戳)

典型组合

单接口,实现即提供完整能力。若传感器支持多坐标系输出,可注册多个实例或由上层根据 frame_id 区分。

硬件/服务厂商接入指南

本文档说明相机、机械臂等硬件厂商以及地图等系统服务如何接入 Robonix RIDL 接口。设计参考 Android HAL:厂商实现接口子集,按需注册,无需实现全部。

各硬件类型的接口形态详见 抽象硬件原语


1. 接入流程概览

  1. 查阅 RIDL 接口定义(robonix-interfaces/ridl/prm/*)
  2. 选择本硬件/服务支持的接口子集
  3. 创建 package,编写 manifest 与 entry
  4. 实现所选接口的 server/publisher,向 meta API 注册
  5. rbnx build + rbnx start 构建与运行

代码分工:ridlc 生成 create_*_servercreate_*_publisherRobonixRuntimeStub 等;你只写 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, intrinsicsdepth, rgbd, ir
纯深度相机depth(+ 可选 intrinsics)rgb, rgbd, ir
RGB-D 相机rgb, depth, rgbd, intrinsicsir(若硬件不支持)
机械臂(无夹爪)move_ee, move_joint, state_joint, joint_trajectorygripper 全部
机械臂 + 夹爪上述 + 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_*_serverRobonixRuntimeStub 等由 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 的类比

AndroidRobonix
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. 参考

RFC 001: Robonix Interface Definition Language (RIDL)

版本日期作者
0.12026-03-11韩喻泷

1. 目标与范围

RIDL 定义接口契约:接口身份(命名空间 + 名称)、通信语义(stream/command/query)、载荷类型、接口版本。RIDL 仅描述"有什么接口、长什么样";不定义 package 构建、channel 名或部署拓扑,channel 由运行时分配(§7)。

不在本 RFC 内:Package 目录与 manifest(RFC002)、channel 分配实现细节、ridlc 代码生成与通信实现(见开发手册)。


2. 命名空间

格式:robonix/{domain}/{subdomain}/...,层级用 / 分隔,小写。

示例:robonix/prm/baserobonix/system/debug/pingrobonix/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 规则摘要

  • 关键词namespaceimportstreamcommandqueryinputoutputresultrequestresponseversion。注解 @desc 等。
  • 文件规则:一文件一接口;接口名必须与文件名一致(如 depth.ridl 定义 stream depth)。
  • 命名:namespace 小写、用 / 分隔;接口名、字段名小写或 snake_case;类型用 package/msg/Name 或 import 后短名。
  • 字段:以 ; 结尾。

4. 通信原语

RIDL 四类原语均为传输无关的语义抽象,具体实现由 codegen/运行时选择。

原语语义典型用法
stream单向数据流位姿/传感器、控制指令
command长任务:输入 + 进度 + 结果运动、skill 执行
query同步请求-响应状态查询、ping

4.1 query

一对一、同步请求-响应。RIDL 结构:requestresponse 各声明字段,类型引用 message。

4.2 command

带输入、可选进度反馈、最终结果的长任务。RIDL 结构:inputoutput(进度)、result

4.3 stream

单向:每个 stream 仅 output 或仅 input。以 provider 为视角:output = provider 发布;input = provider 订阅。


5. 类型系统

  • RIDL 不定义独立 IDL,引用兼容 ROS IDL 规范的类型(package/msg/Namepackage/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 IDLRIDL
消息/服务类型定义引用 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.12026-03-11韩喻泷

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 列表)字段说明

字段必填说明
nodesNode 列表;至少一项。每项对应一个进程(node)。rbnx start -p <package> -n <node_id> 每次只启动一个 node。
nodes[].idNode 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_armcom.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 的 typeentry 构造启动命令(如 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 并通信。