Robonix 包与部署配置规范
Robonix 部署分两层 manifest:
| 文件 | 范围 | 谁读 |
|---|---|---|
部署根目录 robonix_manifest.yaml | 部署 | rbnx boot |
每个包内 package_manifest.yaml | 包 | rbnx start |
命令
# 整个栈一键起(读 deploy manifest,依次启动 system + 所有包)
rbnx boot -f robonix_manifest.yaml
# 只起单个包(开发调试)
rbnx start -p ./service/slam_fastlio2
rbnx boot 的流程:
- 展开
${VAR}环境变量 - 起
system:服务(atlas / executor / pilot / liaison / memory / vlm 等 cargo 二进制) - 对每个
primitive/service/skill条目:把它的config块写到rbnx-boot/instances/<name>.json,然后rbnx start -p <path>,env 里带两个变量:RBNX_CONFIG_FILE=<json-path>和RBNX_INSTANCE_NAME=<name> - 日志落到
rbnx-boot/logs/<component>.log - Ctrl-C 统一 kill
config 用文件传递(不是 env 里直接塞 JSON)——避开 bash 对引号/换行的 escape、
ARG_MAX限制、以及printenv | jq这种不直观的 debug 路径。包里一行jq就能读配置,同一个包的多个 instance(name 不同)各有自己的 json 文件,互不干扰。
Deploy manifest 示例
name: my-robot
env:
ROS_DISTRO: humble
# 每个 system 服务的 config 直接写在它自己下面,不重复。
# pilot 会从 vlm.listen 自己推断该 dial 哪里,不需要再在 pilot 里写一遍。
system:
atlas: { listen: 127.0.0.1:50051 }
executor: { listen: 127.0.0.1:50061 }
pilot: { listen: 127.0.0.1:50071 }
liaison: { listen: 127.0.0.1:50081 }
memory: { backend: sqlite, path: ${HOME}/.robonix/memory.db }
vlm:
listen: 127.0.0.1:50091
upstream: ${VLM_BASE_URL}
api_key: ${VLM_API_KEY}
model: ${VLM_MODEL}
api_format: openai
# 硬件。每个条目是一个硬件实例(设备)
primitive:
- package: com.robonix.primitive.sensor.lidar3d.mid360
path: ./primitive/sensor_lidar3d_mid360
name: lidar3d
config:
ip: 192.168.1.161
mounted_frame: livox_frame
# 场景服务。path 是本地路径;url 是 git 地址(首次 clone 到 rbnx-boot/cache/)。
service:
- package: com.robonix.service.slam.fastlio2
path: ./service/slam_fastlio2
name: slam
config:
mode: mapping
cube_len: 100
map_dir: ${HOME}/.robonix/maps/current
- package: com.robonix.service.nav.nav2
url: https://github.com/syswonder/robonix-nav.git
branch: v0.3
name: nav
config:
planner: SmacPlanner2D
skill:
- package: com.robonix.skill.navigation.navigate_to_landmark
path: ./skill/navigate_to_landmark
name: navigate_to_landmark
config:
vlm_model: claude-opus-4-7
retry_on_ambiguous: true
条目字段
package:包名,要和包 manifest 里的package.name一致path或url:二选一。path相对 manifest 目录;url是 git,首次 clone 到rbnx-boot/cache/<name>/,可加branch:锁分支name:instance 名字 / 日志前缀config:任意 YAML 字典,JSON 化成RBNX_CAP_CONFIG_JSON给包
Package manifest 示例
manifestVersion: 1
package:
name: com.robonix.service.slam.fastlio2
version: 0.1.0
vendor: syswonder
description: FASTLIO2 3D LiDAR-Inertial SLAM + PGO
license: MulanPSL-2.0
build: bash scripts/build.sh # build 入口
start: bash bin/start.sh # start 入口
# 提供的能力。name 是 contract id,可选 path 指向包本地 TOML(当你自定义接口时)。
# 不写 path 就去 $(rbnx path capabilities) 找官方 TOML。
capabilities:
- name: robonix/service/map/lio_odom
- name: robonix/service/map/save_map
- name: robonix/service/map/switch_mode
depends: # 库依赖,即需要用到另一个库的代码/数据(如model)
- name: com.robonix.primitive.sensor.lidar3d.mid360
path: ../primitive/sensor_lidar3d_mid360
- name: com.robonix.system.xx
url: https://github.com/syswonder/robonix-xx.git
branch: v0.1
包里读 config
rbnx 传两个 env 变量:
RBNX_CONFIG_FILE:本 instance 的配置 json 绝对路径RBNX_INSTANCE_NAME:本 instance 名字(同包多实例时用来区分)
# bin/start.sh
set -eo pipefail
: "${RBNX_CONFIG_FILE:=/dev/null}" # 单独 rbnx start 的 fallback
MODE=$(jq -r '.mode // "mapping"' < "$RBNX_CONFIG_FILE")
CUBE=$(jq -r '.cube_len // 100' < "$RBNX_CONFIG_FILE")
echo "[start] instance=$RBNX_INSTANCE_NAME mode=$MODE cube=$CUBE"
exec ros2 launch my_pkg.launch.py mode:="$MODE" cube_len:="$CUBE"
或者 Python:
import json, os
cfg = json.load(open(os.environ["RBNX_CONFIG_FILE"]))
mode = cfg.get("mode", "mapping")
设计说明
为什么 config 是透传,不做 schema
每个包最清楚自己的配置。Robonix 核心不定 schema,包自己 parse RBNX_CAP_CONFIG_JSON,加字段不用改核心代码。
多实例(primitive 一机多件)
一台车两个 MID360,或同一个 camera 驱动挂两个摄像头:deploy manifest 里写两条就行。
primitive:
- package: com.robonix.primitive.sensor.lidar3d.mid360
path: ./primitive/sensor_lidar3d_mid360
name: lidar_front
config: { ip: 192.168.1.161, mounted_frame: livox_front, topic_prefix: /lidar_front }
- package: com.robonix.primitive.sensor.lidar3d.mid360
path: ./primitive/sensor_lidar3d_mid360
name: lidar_rear
config: { ip: 192.168.1.162, mounted_frame: livox_rear, topic_prefix: /lidar_rear }
两条用同一个包、同一个 path,name 和 config 不同。rbnx 会分别 spawn 两个 rbnx start,每个拿到自己的 RBNX_CAP_CONFIG_JSON。包里要根据 config(比如 topic_prefix)决定发什么 topic、以什么 instance id 注册到 atlas(如 robonix/primitive/lidar/lidar3d@front)。
Primitive 的 driver 生命周期
每个抽象硬件类别对应一个 driver contract(如 robonix/primitive/lidar/lidar3d/driver)。driver 是普通 RPC 接口,里面通过 command 字段区分 INIT / RESET / SHUTDOWN / PROBE 四个操作。Robonix 启动 primitive 包后,会先调它的 driver INIT(config_json) 做硬件初始化 / 自检 / 参数下发;失败则不进入数据面。PROBE 可随时被调用查询状态。config_json 是从 manifest 透传下来的字符串,包自己解析。
Driver 的 IDL(共享的)在 rust/crates/robonix-interfaces/lib/robonix_msg/srv/Driver.srv:
uint8 CMD_INIT = 0
uint8 CMD_RESET = 1
uint8 CMD_SHUTDOWN = 2
uint8 CMD_PROBE = 3
uint8 command
string config_json
---
bool ok
string state # uninit | ready | error | shutdown
string error
开发自己的包
接口来源:官方 vs 包内
| 层 | 接口在哪 | 说明 |
|---|---|---|
| primitive | rust/contracts/primitive/ | 官方标准,接入新硬件按已有接口实现;有空缺提 PR 新增 |
| service | rust/contracts/service/(多数)+ 少量包内 | 场景服务大多复用官方接口(SLAM / nav / perception),只有明确私有的才自定义 |
| system | rust/contracts/system/ | 控制面,全官方(pilot / executor / memory / vlm 等) |
| skill | 全部在包内 | skill 是 agent 层,每个包自己定义,不进主仓库 |
Primitive / Service 包:实现官方接口
capabilities: 里只写 name,不写 path:
capabilities:
- name: robonix/primitive/lidar/lidar3d
- name: robonix/primitive/lidar/lidar3d/driver
Robonix 去 rust/contracts/primitive/sensor/lidar3d.v1.toml 查接口形状,你的代码按 TOML 里指向的 ROS IDL / proto 实现。别人看到你的包立刻知道它"提供什么",接口互通。
Skill 包:TOML 写在包里
skill 的接口是 agent 层面的,每个应用都不一样,没有官方标准。TOML 放包里,capabilities: 用 path 指:
capabilities:
- name: robonix/skill/my_stack/weird_thing
path: capabilities/weird_thing.v1.toml
TOML 格式和 primitive/service 的官方结构一致([contract] + [io.srv] / [io.msg] + [mode] + [semantics]),但 srv/msg 的路径解析规则不同:
- 官方 TOML(在 robonix 源码仓库
capabilities/里):[io.srv] srv = "robonix_msg/srv/Foo"→ 去rust/crates/robonix-interfaces/lib/robonix_msg/srv/Foo.srv找 - 包内 TOML(在你本地 package 的
capabilities/里):[io.srv] srv = "srv/Foo"→ 去包的capabilities/srv/Foo.srv找
典型的 skill 包 capabilities/ 布局:
capabilities/
├── weird_thing.v1.toml
├── msg/
│ └── MyStructure.msg
└── srv/
└── MyRequest.srv
weird_thing.v1.toml:
[contract]
id = "robonix/skill/my_stack/weird_thing"
version = "1"
kind = "skill"
[io.srv]
srv = "srv/MyRequest" # 指向 capabilities/srv/MyRequest.srv(包内)
[mode]
type = "rpc"
[semantics]
user_invocable = true
rbnx codegen 会把包内的 capabilities/msg/*.msg 和 capabilities/srv/*.srv 也 codegen 到包的 proto_gen/,和官方接口一样导入使用。(TODO)