Robonix 包与部署配置规范

Robonix 部署分两层 manifest:

文件范围谁读
部署根目录 robonix_manifest.yaml部署rbnx boot
每个包内 package_manifest.yamlrbnx start

命令

# 整个栈一键起(读 deploy manifest,依次启动 system + 所有包)
rbnx boot -f robonix_manifest.yaml

# 只起单个包(开发调试)
rbnx start -p ./service/slam_fastlio2

rbnx boot 的流程:

  1. 展开 ${VAR} 环境变量
  2. system: 服务(atlas / executor / pilot / liaison / memory / vlm 等 cargo 二进制)
  3. 对每个 primitive / service / skill 条目:把它的 config 块写到 rbnx-boot/instances/<name>.json,然后 rbnx start -p <path>,env 里带两个变量:RBNX_CONFIG_FILE=<json-path>RBNX_INSTANCE_NAME=<name>
  4. 日志落到 rbnx-boot/logs/<component>.log
  5. 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 一致
  • pathurl:二选一。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,nameconfig 不同。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 包内

接口在哪说明
primitiverust/contracts/primitive/官方标准,接入新硬件按已有接口实现;有空缺提 PR 新增
servicerust/contracts/service/(多数)+ 少量包内场景服务大多复用官方接口(SLAM / nav / perception),只有明确私有的才自定义
systemrust/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/*.msgcapabilities/srv/*.srv 也 codegen 到包的 proto_gen/,和官方接口一样导入使用。(TODO)