Skip to content

Mod Nodes

konodoki edited this page Jul 30, 2026 · 8 revisions

Mod 子节点声明与运行(mod.yamlnodes 字段)

本文说明 BXI ROS 2 控制框架中,一个 Mod 如何在自己的 mod.yaml 里声明、启动和 管理辅助 ROS 2 节点和普通后台命令。这里的“Mod 子节点”特指顶层 nodes: 字段中的 受管运行单元,例如相机发布器、感知节点、通信桥接程序或设备 SDK 服务。

本文不是整个工作区的 ROS 2 节点清单,也不是 states: 状态清单,更不是 inference 推理类清单。若你的 Mod 只有动作状态,不需要额外的 ROS 2 发布者或订阅者,可以完全 不写 nodes:

本文对应哪里

项目 位置或含义
所属项目 src/bxi_example_py_elf3/ 中的 BXI Mod 框架
配置入口 每个 Mod 自己的 <mod目录>/mod.yaml 顶层 nodes: 字段
公共类型 bxi_example_py_elf3/framework/mod_api/node.py 中的 NodeBuildContext
运行时实现 bxi_example_py_elf3/framework/runtime/mod_nodes.py 中的 ModNodeManager
管理范围 节点创建、按状态启停、独立进程、参数、重映射、异常重启和关闭

一个真实例子是 mods/com.bxi.normal_depth/mod.yaml

com.bxi.normal_depth Mod
├── nodes.depth_camera_publisher     # 发布深度图像的辅助 ROS 2 节点
│   └── auto_depth_node:create_node
└── states.normal_depth              # 消费深度图像并计算机器人动作的控制状态

两者职责不同:depth_camera_publisher 负责产生 ROS 图像,normal_depth 是控制状态; Mod 节点管理器根据 lifecycle 决定前者何时启动和停止。这一页讲的是上图的 nodes 分支。关于 states 请阅读自定义状态,关于完整 Mod 结构请阅读 Mod 系统

支持的节点运行时

Mod API 2.2 支持四种节点运行时,同时复用相同的生命周期、重启和展示字段:

  • python:Mod 内的 Python 工厂,返回一个 rclpy.Node
  • executable:Mod 内随包分发的原生可执行文件,适用于 C++ ROS 2 节点。
  • ros:通过 ament index 查找已安装 ROS 2 包中的可执行文件,行为等价于 ros2 run
  • command:执行 Mod 内的普通脚本或命令,不追加 ROS 参数;支持独立解释器、环境 变量、工作目录、依赖顺序和分阶段关闭。

使用 executableroscommand 的 Mod 应声明当前公共 API 范围 api: ">=2,<3"

公共格式

nodes:
  detector:
    runtime: executable
    entrypoint: detector_node
    execution: process
    lifecycle: state
    states: [detect]
    arguments: [--device, "0"]
    namespace: /vision
    remappings:
      image: /camera/image_raw
    params:
      threshold: 0.5
      enabled: true
    manifest:
      label: C++ 检测节点
    runtime_requirements:
      python: []
      ros:
        - package: sensor_msgs
      system: []
    restart:
      max_attempts: 3
      delay: 1.0
      non_retryable_exit_codes: [78]

字段说明:

  • runtimepythonexecutableroscommand。为兼容旧 Mod,省略时默认为 python
  • entrypoint:其格式由 runtime 决定,具体见下文。
  • execution:Python 节点支持 in_processprocess;原生节点必须是 process。原生节点省略该字段时默认使用 process
  • lifecyclemod 表示随 Mod 常驻,state 表示仅在 states 指定的状态 活跃或预加载时运行。
  • arguments:放在可执行文件之后、--ros-args 之前的普通程序参数。
  • namespace:空字符串或以 / 开头的绝对 ROS namespace。
  • remappings:ROS 名称重映射表。
  • params:节点参数。原生节点会收到自动生成的标准 ROS 2 参数文件。
  • restart:仅适用于进程节点,沿用现有退出监控和重启策略。 non_retryable_exit_codes 可声明确定性配置故障的退出码;命中后节点立即进入 faulted,不消耗重试次数。建议使用 sysexits 的 EX_CONFIG=78 表示配置错误,普通 运行时故障仍应使用其他非零退出码以保留自动恢复能力。
  • depends_on:依赖的节点名列表。框架按依赖拓扑顺序启动、逆序停止;局部名称会自动 加上当前 Mod ID。
  • shutdown:进程关闭策略,可设置初始信号、升级为 SIGTERM 的时间和最终 SIGKILL 时间。

所有原生节点都会收到框架生成的唯一节点名。实际命令形如:

<executable> <arguments...> --ros-args \
  -r __node:=com_example_detector_detector \
  -r __ns:=/vision \
  -r image:=/camera/image_raw \
  --params-file <temporary-file>

临时参数文件由节点管理器持有,在管理器关闭时删除。

Python 节点

nodes:
  camera:
    runtime: python
    entrypoint: camera_node:create_node
    execution: process
    lifecycle: mod
    arguments: []
    namespace: ""
    remappings: {}
    params:
      fps: 30
    manifest:
      label: Python 相机节点

Python 工厂通过 NodeBuildContext 获得 node_nameparamsargumentsnamespaceremappings。Python 工厂负责在构造 rclpy.Node 时使用这些值。

普通命令节点

command 用于不实现 rclpy.Node、也不接受 --ros-args 的长期后台程序:

nodes:
  device_manager:
    runtime: command
    entrypoint: scripts/device_manager.py
    interpreter: "${DEVICE_PYTHON:-python3}"
    lifecycle: state
    states: [teleop]
    arguments: [--port, "${DEVICE_PORT:-5556}"]
    cwd: .
    environment:
      PYTHONUNBUFFERED: "1"
      LD_LIBRARY_PATH:
        prepend:
          - "${DEVICE_SDK_ROOT:-/opt/device}/lib"
        existing_only: true
    manifest:
      label: 设备管理器
    restart:
      max_attempts: 3
      delay: 2.0
      non_retryable_exit_codes: [78]
    shutdown:
      signal: SIGINT
      terminate_after: 3.0
      kill_after: 5.0

  protocol_bridge:
    runtime: command
    entrypoint: scripts/protocol_bridge.py
    interpreter: "${DEVICE_PYTHON:-python3}"
    lifecycle: state
    states: [teleop]
    depends_on: [device_manager]
    manifest:
      label: 协议桥

entrypoint 必须是 Mod 目录内的安全相对路径,不能包含 ..,符号链接解析后也不能 逃出 Mod。设置 interpreter 后脚本不要求可执行位;不设置时 entrypoint 自身必须 可执行。cwd 同样只能指向 Mod 内已有目录,默认是 Mod 根目录。

字符串支持只读环境展开:$NAME${NAME}${NAME:-default}。框架不会经过 shell,因此不会执行命令替换、重定向或管道。子进程还会获得 BXI_MOD_ROOT,可在 argumentsinterpreterenvironment 中引用。

environment 的字符串值表示直接赋值;映射值支持:

  • set:先设置变量。
  • unset:从子进程环境删除变量;不能与 setprependappend 同时使用。
  • prepend / append:按 separator 拼接多个值;默认分隔符是平台路径分隔符。
  • existing_only:只保留当前文件系统上存在的 prepend/append 项,适合可选 SDK 路径。

command 不接收 ROS 参数,因此不能声明非空 paramsnamespaceremappings。 若程序本身需要 ROS 参数,应自行放入 arguments,或者改用其他三种运行时。

shutdown.signal 可为 SIGHUPSIGINTSIGQUITSIGTERM,默认 SIGTERM。到达 terminate_after 后框架发送 SIGTERM,到达 kill_after 后发送 SIGKILL。所有信号都发往该命令独立的进程组,以便清理其子进程。

Mod 内原生节点

一个 Mod 可以同时携带多个平台的同名二进制:

com.example.detector/
├── mod.yaml
├── bin/
│   ├── linux-x86_64/
│   │   └── detector_node
│   └── linux-aarch64/
│       └── detector_node
└── vendor/
    └── lib/
        ├── linux-x86_64/
        └── linux-aarch64/

对应清单只写平台无关的文件名:

runtime: executable
entrypoint: detector_node

运行时使用与 runtime_platform_tag() 相同的平台标签,自动解析 bin/<platform>/<entrypoint>。不会回退到其他平台。文件缺失或没有执行权限时, 节点状态为 unavailable,其他可用节点仍可继续加载。

entrypoint 必须是相对于平台 bin 目录的安全路径;绝对路径、.. 和解析后 逃出该目录的符号链接都会被拒绝。Mod 私有动态库放在 vendor/lib/<platform>/,该目录会被优先加入子进程的 LD_LIBRARY_PATH

已安装 ROS 包节点

nodes:
  detector:
    runtime: ros
    entrypoint: detector_package:detector_node
    execution: process
    lifecycle: mod
    arguments: []
    namespace: ""
    remappings: {}
    params: {}
    manifest:
      label: 系统检测节点

运行时通过 ament index 获取包前缀,并直接执行 <prefix>/lib/detector_package/detector_node。直接执行避免了监管 ros2 run 包装进程,停止和异常重启仍由同一个 Mod 节点管理器负责。

相关文档

Clone this wiki locally