-
Notifications
You must be signed in to change notification settings - Fork 12
Mod System
Mod 是当前状态侧的最小部署单元。一个目录同时携带清单、Python 代码和资产,可以被发现、校验、热重载和整体移除。
com.example.wave/
mod.yaml
state.py
plugin.py # 仅资源/自定义扩展需要
assets/
wave.onnx
内置根目录是安装包的 share/bxi_example_py_elf3/mods/。系统配置中的 mod_paths 可追加客户目录,例如 /opt/bxi/mods。根目录本身是 Mod 或其任意子目录包含 mod.yaml 都可被发现。
id: com.example.wave
version: 1.0.0
events:
activate: {slot: btn_10, value: 8}
states:
wave:
factory: state:WaveState
label: 挥手示例
index: 20
group: Customer
icon: waving_hand
routes:
- from: com.bxi.basic_actions/normal
event: activate
to: wave
transition: soft_switch
- from: wave
event: com.bxi.basic_actions/normal
to: com.bxi.basic_actions/normal
transition: dual_running_blendschema/api 缺省为 1。没有 entrypoint 时,每个状态用 factory: module:Class;如果目录已有 plugin.py,则缺省入口为 plugin:create_mod。简写的 label/index/group/icon/confirm/confirm_message 会归一化到内部 manifest,完整旧写法继续兼容。
跨 Mod 使用基础状态和事件时,应声明依赖:
requires:
- id: com.bxi.basic_actions
version: ">=1,<2"只有需要 Resource、自定义 Transition 导入或依赖注入时才需要入口插件:
from bxi_example_py_elf3.utils.mod_system import ModDefinition, ModLoadContext
from .state import WaveState
def create_mod(context: ModLoadContext) -> ModDefinition:
return ModDefinition(
state_factories={
"wave": lambda state: WaveState(state.name, state.state_id),
}
)states 声明和 state_factories 的键必须完全相同。工厂在状态机构建阶段调用,不应自行修改全局状态。
约定类可声明 dataclass Params,框架会自动强类型构造:
from dataclasses import dataclass
from bxi_example_py_elf3.utils.state_library import ProceduralState
@dataclass(frozen=True)
class WaveParams:
amplitude: float = 0.3
loop: bool = False
class WaveState(ProceduralState[WaveParams]):
Params = WaveParams高级显式工厂仍可逐字段读取:
清单:
states:
wave:
params:
amplitude: 0.4
loop: true工厂:
"wave": lambda state: WaveState(
state.name,
state.state_id,
amplitude=state.float_param("amplitude", 0.3),
loop=state.bool_param("loop", False),
)还可使用 int_param()、string_param() 和 param()。工厂返回后框架会调用 finish();未消费或类型错误的参数会阻止加载。
资源键必须是全局命名:
from bxi_example_py_elf3.utils.mod_system import ResourceKey, ResourceLoadContext
POLICY = ResourceKey[WavePolicy]("com.example.wave/policy")
def _load_policy(context: ResourceLoadContext) -> WavePolicy:
return WavePolicy(str(context.asset("assets/wave.onnx")))
def create_mod(context: ModLoadContext) -> ModDefinition:
context.register_resource(POLICY, _load_policy)
policy = context.resource(POLICY)
return ModDefinition(
state_factories={
"wave": lambda state: WaveState(state.name, state.state_id, policy),
}
)context.resource() 返回 ResourceHandle,不会立即加载模型。第一次 handle.get() 时才调用加载函数,之后复用缓存实例。context.asset() 只允许访问当前 Mod 的 assets/ 内存在的文件,并把真实使用路径记录到 ResourceManager.loaded_paths;热重载器另行监控整个 Mod 根目录。
共享资源可以由一个无状态 Mod 提供,使用方通过 requires 依赖它。
一个 Mod 可以贡献:
stateseventsroutesspeed_profilestransition_profilespython_exports
本地状态、事件和 profile 会自动加 id/ 前缀。系统级 transition profile(如 soft_switch)保持全局名称。
python_exports 用于把 Mod 内的顶层 Python 包临时加入导入路径。导出的目录必须包含 __init__.py,不同 Mod 不得导出同名包。热重载关闭旧运行时时会移除对应模块和路径。
requires 支持字符串和带版本约束的对象:
requires:
- com.example.common
- id: com.bxi.basic_actions
version: ">=1,<2"版本必须是数字点格式。加载器进行拓扑排序,并拒绝缺失依赖、版本不匹配和依赖环。
日常使用:
bxi-mod new com.example.wave --state wave --template procedural
bxi-mod validate path/to/com.example.wave
bxi-mod inspect path/to/com.example.waveCLI 的仓库实现位于 tools/bxi-mod,构建时安装到前缀的 bin/;它不是机器人运行时 Python 包的一部分。
编辑器可使用安装到 share/bxi_example_py_elf3/schema/mod.schema.json 的 JSON Schema。
启动或热重载时会检查:
-
schema/api缺省或显式值为1、合法且唯一的 Mod id。 - entrypoint 文件和函数存在,返回
ModDefinition。 - 依赖完整且版本匹配。
- 清单状态与工厂一一对应。
- 本地名称、资源键、事件槽位和字段类型合法。
- route 的源、事件和目标存在,同一状态同一事件没有重复边。
- 同一状态下不会有可同时触发的遥控输入冲突。
- 所有事件至少被一条 route 使用。
- 初始状态存在,speed profile 引用有效。
manifest.index 必须是非负整数。发生重复时,首次声明者保留原值,后续状态从原值加一开始寻找未占用 index,并输出 RuntimeWarning,不会终止加载。分配器会提前保留所有合法显式 index,避免抢占后续声明。
状态内部的 id 与 UI manifest.index 不同:未显式配置 states.<name>.id 时,state id 根据完整状态名计算稳定 CRC32;真正 CRC 冲突时才要求显式指定。
mods/
com.bxi.basic_actions/ # 11 个基础动作状态和共享模型
com.bxi.back_flip/ # protected
com.bxi.forward_flip/ # public
com.bxi.ballet/ # protected
基础包的事件名采用 com.bxi.basic_actions/<action>。三个独立包仍使用自己的 activate 事件。
visibility: protected运行时会正常加载 protected Mod;该字段供公开发布工具使用。发布工具按完整目录删除保护 Mod,并验证公开 Mod 不依赖被删除的功能。详见 发布保护。