docs: 补充 actpred 注意力改造测试报告与 bot4cj Python 迁移开发文档
This commit is contained in:
1 parent
4dd25486f8
commit
6089e7427d
2 files changed
+612
No files matched your search
@@ -0,0 +1,212 @@
|
||||
# actpred 多帧注意力架构改造测试报告
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 日期 | 2026-08-09 |
|
||||
| 模块 | actpred |
|
||||
| 测试人 | qing |
|
||||
| 环境 | Intel Xeon E3-1200 v2 (Ivy Bridge), 4 核, Linux 24.04, Python 3.x, PyTorch (CPU-only) |
|
||||
| 关联文件 | `config.py`, `model.py`, `dataset.py`, `train.py`, `pretrain_mt.py`, `predict.py`, `export_onnx.py`, `test_actpred.py` |
|
||||
| 前置报告 | [actpred_pretrain_test_20260808.md](actpred_pretrain_test_20260808.md) |
|
||||
|
||||
---
|
||||
|
||||
## 一、改造背景与目标
|
||||
|
||||
原架构为"单帧 CNN + 历史动作 MLP/GRU + concat 融合",仅支持单帧图像输入。
|
||||
本次改造目标:
|
||||
|
||||
1. 引入注意力机制,支持**多帧图像 + 历史动作序列**联合建模,预测下一时间步动作
|
||||
2. 保持 checkpoint 格式(`model_state_dict` + `config`)、CLI 工作流与 ONNX 导出链路不变
|
||||
3. 保持除 batch 维外全静态 shape,面向树莓派 AI 扩展板(NPU)部署友好
|
||||
4. 直接替换旧架构(用户决策):删除 MLP/GRU 编码器与 concat 路径,旧 checkpoint 作废
|
||||
|
||||
## 二、新架构
|
||||
|
||||
```
|
||||
F 帧图像 (F×3×64×64) ─► 共享 CNN 编码器 ─► F 个 token ─► proj → d_model ┐
|
||||
├─► [img×F, act×H, PRED] + 位置编码
|
||||
动作历史 (H×D) ─► Linear proj → d_model ─► H 个 token ──────────┤ │
|
||||
可学习 [PRED] token ─────────────────────────────────────────────────┘ ▼
|
||||
TransformerEncoder (pre-LN, 2 层, 4 头, d_model=128)
|
||||
│
|
||||
取 PRED 位置输出 ─► MLP 头 ─► 预测动作 (action_dim)
|
||||
```
|
||||
|
||||
| 设计点 | 取值/说明 |
|
||||
|--------|----------|
|
||||
| token 序列 | `[img×F, act×H, PRED]`,长度固定 F+H+1(默认 3+5+1=9) |
|
||||
| 帧序约定 | 旧→新,末帧为当前帧;冷启动可重复传同一帧 |
|
||||
| 注意力 | `nn.TransformerEncoder`,pre-LN(norm_first),dim_feedforward=2×d_model |
|
||||
| 位置编码 | 可学习 `pos_embed (1, F+H+1, d)`,trunc_normal 初始化 |
|
||||
| 输出 | 取末位 PRED token 过 MLP 头(d → 128 → 64 → action_dim) |
|
||||
| 静态性 | forward 中 reshape 仅 batch 维动态,其余编译期固定 |
|
||||
| 参数量 | 409,042(旧架构 142,034,约 2.9 倍) |
|
||||
|
||||
### 新增配置参数
|
||||
|
||||
| 参数 | 默认值 | 校验范围 |
|
||||
|------|--------|---------|
|
||||
| `--frame-len` | 3 | [1, 1024] |
|
||||
| `--attn-dim` | 128 | [1, 4096],且须能被 attn-heads 整除 |
|
||||
| `--attn-heads` | 4 | [1, 64] |
|
||||
| `--attn-layers` | 2 | [1, 64] |
|
||||
|
||||
删除参数:`--history-encoder`、`--action-hidden-dim`。
|
||||
旧 checkpoint 因 config 含未知键(`history_encoder`/`action_hidden_dim`)在加载时被
|
||||
`parse_checkpoint_config` 以明确 ValueError 拒载,符合"作废"预期(有专项测试覆盖)。
|
||||
|
||||
## 三、单元测试
|
||||
|
||||
```
|
||||
$ python test_actpred.py
|
||||
Ran 56 tests in 5.959s
|
||||
OK
|
||||
```
|
||||
|
||||
**56 项测试全部通过**(安装 onnxscript/onnxruntime 后无跳过项)。本次新增/改写用例:
|
||||
|
||||
| 用例 | 验证点 |
|
||||
|------|--------|
|
||||
| `test_forward_multi_frame` | (2,3,3,64,64) + (2,5,18) → (2,18) |
|
||||
| `test_forward_single_frame_degenerate` | frame_len=1 退化单帧仍正常前向 |
|
||||
| `test_forward_dynamic_batch` | batch 1/4/8 动态(frame_len=2) |
|
||||
| `test_custom_attn_config` | attn_dim=64/heads=2/layers=1 可构建前向 |
|
||||
| `test_attn_dim_not_divisible_by_heads_raises` | 130 % 4 ≠ 0 → ValueError |
|
||||
| `test_dataset_loading` | 多帧样本 shape (3,3,32,32),batch key 改为 `images` |
|
||||
| `test_frame_len_gt_history_shifts_start` | 采样起点 max(history_len, frame_len-1) 正确 |
|
||||
| `test_legacy_checkpoint_config_rejected` | 旧架构 checkpoint 拒载 |
|
||||
| `test_wrong_frame_count_raises` | 推理帧数不符 → ValueError |
|
||||
| `test_end_to_end_predict` 等推理用例 | 多帧输入端到端、归一化等价性、确定性 |
|
||||
|
||||
## 四、预训练测试
|
||||
|
||||
### 4.1 训练配置
|
||||
|
||||
| 参数 | 值 |
|
||||
|------|-----|
|
||||
| 数据来源 | `../bot4cj/scripts/*.mt`(13 文件) |
|
||||
| train/val 划分 | **文件级**(11 训练文件 / 2 验证文件,杜绝滑动窗口泄漏) |
|
||||
| 输入 | 全零多帧图像 (B, 3, 3, 64, 64) + action_history (B, 5, 18) |
|
||||
| 优化器/调度 | AdamW (lr=0.001, wd=1e-4) + CosineAnnealingLR |
|
||||
| 数据增强 | Gaussian noise (std=1.0) |
|
||||
| batch_size / epochs / patience | 8 / 200 / 30 |
|
||||
| 随机种子 | 42 |
|
||||
|
||||
### 4.2 训练结果
|
||||
|
||||
| 指标 | 值 |
|
||||
|------|-----|
|
||||
| 最终轮数 | 65(早停,patience=30) |
|
||||
| 最优 val_loss | **2476.75** |
|
||||
| 产出 | `checkpoints/pretrain_best.pt`(含 optimizer)、`checkpoints/pretrain_best_inference.pt` |
|
||||
|
||||
> **关于 val_loss 数值的说明**:本次 2476.75 与前次报告的 190.58 不可直接对比——
|
||||
> 前次数值系样本级滑动窗口划分导致的数据泄漏虚低值(已在 0808 审计中定性);
|
||||
> 本次为文件级划分(验证集是训练中完全未见过的动作脚本),且模型更大(409K vs 142K)
|
||||
> 而数据仅 13 个脚本,属"诚实的困难任务"数值。注意力架构的真实收益需在真实图像数据
|
||||
> 微调后评估。
|
||||
|
||||
## 五、推理链路验证
|
||||
|
||||
### 5.1 CLI 全链路
|
||||
|
||||
```
|
||||
$ bash _run.sh --model checkpoints/pretrain_best_inference.pt \
|
||||
--images /tmp/f0.jpg /tmp/f1.jpg /tmp/f2.jpg \
|
||||
--action-history "45,0,...(5 步 × 18 值)"
|
||||
18.3430 10.1614 17.3654 ... (18 维输出,数值有限)
|
||||
```
|
||||
|
||||
✅ 3 帧 + 5 步历史全链路输出正常。
|
||||
|
||||
### 5.2 错误场景
|
||||
|
||||
```
|
||||
$ ... --images /tmp/f0.jpg ...
|
||||
--images 需传入 3 个路径(旧→新),实际为 1 个。冷启动时可重复传同一帧路径。
|
||||
```
|
||||
|
||||
✅ 帧数不符时报错信息明确,含冷启动指引。
|
||||
|
||||
## 六、ONNX 导出验证
|
||||
|
||||
```
|
||||
$ python export_onnx.py --model checkpoints/pretrain_best_inference.pt --output /tmp/actpred_attn.onnx
|
||||
ONNX model exported to: /tmp/actpred_attn.onnx
|
||||
ONNX 一致性验证通过(最大差异: 7.629395e-06)
|
||||
```
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 导出 | ✅ 成功(opset 17,输入 `images (batch,3,3,64,64)` + `action_history (batch,5,18)`) |
|
||||
| onnxruntime 一致性 | ✅ 最大差异 7.63e-06(< 1e-4 阈值) |
|
||||
| 动态维 | 仅 batch,NPU 编译友好 |
|
||||
|
||||
注:本次为环境补装了 `onnxscript` 与 `onnxruntime`(torch ≥ 2.12 ONNX 导出依赖)。
|
||||
|
||||
## 七、CPU 推理性能基准
|
||||
|
||||
测试方法:warmup 20 次后测 200 次取平均,输入随机 3 帧 (64,64,3) uint8 + (5,18) float32。
|
||||
|
||||
| 环节 | 本次(注意力,3 帧) | 前次(concat,单帧) |
|
||||
|------|---------------------|---------------------|
|
||||
| 参数量 | 409,042 | 142,034 |
|
||||
| 端到端推理(含预处理) | **4.25 ms (235 FPS)** | 1.61 ms (622 FPS) |
|
||||
| 纯前向 | 2.77 ms (360 FPS) | 1.09 ms (916 FPS) |
|
||||
| image_encoder 占比 | 1.30 ms(47%,3 帧) | 0.82 ms(75%,1 帧) |
|
||||
|
||||
评估:
|
||||
|
||||
- 235 FPS 对 bot4cj 30 FPS 控制循环仍有 **7.8 倍余量**,实时性满足
|
||||
- 延迟增长主因:CNN 由 1 帧变 3 帧(约 3×0.43ms)+ Transformer 编码器(约 1.5ms)
|
||||
- image_encoder 占比由 75% 降至 47%,注意力部分成为新的次要热点;
|
||||
部署到树莓派 AI 扩展板后两者均可被 NPU 加速(静态 shape 已保证)
|
||||
|
||||
## 八、结论与下一步
|
||||
|
||||
### 8.1 测试结论
|
||||
|
||||
| 测试项 | 结果 |
|
||||
|--------|------|
|
||||
| 多帧注意力模型前向 | ✅ 通过(多帧/单帧退化/动态 batch/自定义 attn 配置) |
|
||||
| 配置校验 | ✅ 通过(frame_len/attn_* 值域 + 整除校验 + 旧 checkpoint 拒载) |
|
||||
| 多帧数据集采样 | ✅ 通过(采样起点与样本数断言) |
|
||||
| 单元测试 | ✅ 56/56 通过 |
|
||||
| 预训练 | ✅ 通过(65 epoch 早停,文件级划分无泄漏) |
|
||||
| 推理 CLI | ✅ 通过(正常 + 错误场景) |
|
||||
| ONNX 导出 | ✅ 通过(一致性 7.63e-06) |
|
||||
| CPU 性能 | ✅ 通过(235 FPS,7.8 倍实时余量) |
|
||||
|
||||
### 8.2 已知局限
|
||||
|
||||
1. **预训练图像分支未习得**:全零多帧占位下 image_encoder 与图像 token 的注意力权重
|
||||
无有效梯度信号,真实收益依赖后续真实图像微调
|
||||
2. **数据量极少**:13 个 .mt 脚本对 409K 参数模型明显不足,val_loss 绝对值偏高属预期
|
||||
3. **旧 checkpoint 全部作废**:已删除并重训预训练 checkpoint;如有外部持有旧
|
||||
`best_inference.pt` 需同步告知重训
|
||||
|
||||
### 8.3 下一步建议
|
||||
|
||||
1. **采集真实多帧数据**:用 `DataRecorder` 录制 bot4cj 摄像头 episode,以本次预训练
|
||||
checkpoint 为起点微调(勿开 `--normalize-actions`)
|
||||
2. **树莓派 AI 扩展板部署验证**:将 ONNX 模型走扩展板厂商工具链编译,实测 NPU 延迟
|
||||
3. **帧数消融**:真实数据到位后对比 frame_len=1/3/5 的验证误差,确认多帧收益
|
||||
4. **扩充 .mt 脚本**:增加动作脚本数量,缓解预训练数据不足
|
||||
|
||||
---
|
||||
|
||||
## 附录:文件变更清单
|
||||
|
||||
| 文件 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `actpred/config.py` | 修改 | 新增 frame_len/attn_*;删除 history_encoder/action_hidden_dim;整除校验 |
|
||||
| `actpred/model.py` | 重写 | 多帧 Transformer 注意力架构;删除 MLP/GRU 编码器与 concat 融合 |
|
||||
| `actpred/dataset.py` | 修改 | 多帧窗口采样;batch key `image` → `images` |
|
||||
| `actpred/train.py` | 修改 | 多帧适配(frame_len 透传、batch key) |
|
||||
| `actpred/pretrain_mt.py` | 修改 | 全零多帧占位;打印信息更新 |
|
||||
| `actpred/predict.py` | 修改 | `predict(images, history)` 多帧 API;CLI `--image` → `--images` |
|
||||
| `actpred/export_onnx.py` | 修改 | 多帧 dummy 输入;input_names 更新 |
|
||||
| `actpred/test_actpred.py` | 修改 | 56 项用例(新增多帧/校验/拒载/帧数错误等) |
|
||||
| `actpred/README.md` | 修改 | 架构图、CLI 示例、配置表、API 文档同步 |
|
||||
| `actpred/checkpoints/pretrain_best*.pt` | 重新生成 | 旧架构 checkpoint 已删除并重训 |
|
||||
@@ -0,0 +1,400 @@
|
||||
# bot4cj 模块 Python 迁移开发方案
|
||||
|
||||
> 文档类型:功能开发计划
|
||||
> 模块:bot4cj → pybot(Python 迁移)
|
||||
> 作者:自动生成
|
||||
> 日期:2026-08-09
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 背景与动机
|
||||
|
||||
`bot4cj` 是基于仓颉(Cangjie)编程语言和 Linux I2C 接口的机器人硬件脚本解析器,通过 `.mt` 矩阵脚本描述机器人硬件构成与动作序列,驱动 PCA9685 舵机控制板、MPU6050 传感器等真实硬件(宿主机为树莓派 aarch64)。
|
||||
|
||||
将其迁移到 Python 的动机:
|
||||
|
||||
| 收益 | 说明 |
|
||||
|------|------|
|
||||
| 工具链门槛消除 | 仓颉编译器(cjc 1.1.3)+ cjpm + 4 个仓颉扩展包(ctype4cj/pthread4cj/spi4cj/lua4cj)→ 树莓派系统自带 Python3,零编译 |
|
||||
| 与 actpred 生态打通 | actpred(动作预测,PyTorch)已有 Python 版 `.mt` 解析器(`mt_parser.py`),迁移后"大脑(actpred)↔ 身体(bot4cj)"共享同一语言与数据结构,DRL 闭环开发成本大幅降低 |
|
||||
| Lua 扩展层可消除 | 仓颉版引入 Lua 是为了运行时可扩展硬件驱动;Python 本身即脚本语言,硬件驱动直接写成 Python 插件模块,砍掉 lua4cj + liblua5.4 + C 函数注册整层 |
|
||||
| C/C++ 共享库大部分可消除 | libi2c4cj.so 的功能可用 `fcntl.ioctl` + `os.read/write` 纯 Python 实现;矩阵/FFT/CNN 由 numpy/PyTorch(actpred 已有)承担 |
|
||||
| 审计遗留一并修复 | [bot4cj 代码审计](../reviews/bot4cj_CODE_AUDIT_20260808.md) 15 项发现(3 P0 + 5 P1 + 7 P2)全部待修复,迁移时逐项闭环 |
|
||||
| 与项目主流技术栈对齐 | micro_servers 业务服务以 Python/C++20 为主,仓颉是唯一的孤立技术栈,维护人手受限 |
|
||||
|
||||
**性能评估**:bot4cj 的执行节奏由硬件决定(舵机 50Hz PWM、I2C 100kHz~400kHz、动作行间 20~500ms 延时),解析与调度不是瓶颈,Python 的性能完全够用;且 Python 插件替代 Lua 桥接后,单次硬件调用的框架开销反而下降约两个数量级(详见附录 A 的量化分析)。这与 db2/captcha2 等"Python→C++ 求性能"的方向相反,是"仓颉→Python 求生态与可维护性"。
|
||||
|
||||
### 1.2 设计目标
|
||||
|
||||
1. **`.mt` 脚本零改动**:现有 `scripts/*.mt` 全部原样可用,块语法(`@Alias`/`@I2C`/`@PCA9685`/`@MPU6050_BIND`/`@Speaker`/`@Servo`/`@Action`)、`-` 占位符语义、`#` 注释、`;` 行结束符完全兼容。
|
||||
2. **CLI 兼容**:保持 `cat scripts/std.mt | ./bot` 的 stdin 管道用法与 `--help`/`--test` 分支。
|
||||
3. **零第三方依赖的核心**:解析器 + 硬件层仅用标准库(`fcntl`/`os`/`struct`/`threading`/`json`/`argparse`),树莓派上无需 venv 即可运行;numpy 仅测试/工具链可选。
|
||||
4. **硬件插件化(Python 替代 Lua)**:未知 `@Block` 路由到 `modules/<Name>.py` 插件,保留 build/call/destroy 三段生命周期协议。
|
||||
5. **审计 15 项发现全部修复**:见 §5 修复映射表。
|
||||
6. **可测试**:pytest 单元测试覆盖解析器、占位符语义、Servo 校准、插件加载;硬件层可用 Mock I2C 在无硬件的 x86 上跑通全流程。
|
||||
|
||||
### 1.3 不做什么
|
||||
|
||||
- **不迁移 math/NN/CNN/FFT/CUDA 层**(`src/math/*.cj`、`src/cpp/cnn/`、`src/cpp/fft*`、`src/cuda/`):这部分是为 DRL 预留的自研神经网络库,功能已被 actpred(PyTorch)完全覆盖且质量更高。DRL 闭环未来直接调 actpred,不重复造轮子。相应地 `--test nn/cnn/cnn1` 分支不迁移。
|
||||
- **不迁移 `.bot` 旧版脚本支持**:`scripts/*.bot` 是历史遗留,仓颉版本身也未解析它。
|
||||
- **不改 `.mt` 语法**:`@Action_Auto` 等语法演进(旧分析 D11)留待后续版本。
|
||||
- **不引入 smbus2/RPi.GPIO 等三方库**:I2C 用 `fcntl.ioctl(fd, I2C_SLAVE=0x0703, addr)` 直接实现,与仓颉版 `I2C.cj`/`I2C.c` 行为逐字节对齐。
|
||||
- **不删除 bot4cj 目录**:遵循项目"原版与重写版并存 → 验证通过后切换"的渐进策略(同 db→db2、captcha→captcha2、smtp→smtp3)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 现状分析
|
||||
|
||||
### 2.1 仓颉版架构分层
|
||||
|
||||
```
|
||||
.mt 矩阵脚本(硬件定义 + 动作序列)
|
||||
↓
|
||||
MatrixParser(P.cj,FSM 逐字符解析)→ MatrixObject 链表
|
||||
↓
|
||||
RobotStruct(r.cj)createHardVars():按 @块名分发到硬件变量工厂
|
||||
MatrixVars_Hardware 基类 + 三闭包:rise/things/free
|
||||
↓
|
||||
硬件实现 hd/*.cj:I2C(20 槽锁数组)、Servo、PCA9685、MPU6050、Speaker
|
||||
+ Lua 扩展 modules/*.lua(未知 @Block 的通用驱动,经 C 函数注册调 I2C/SPI)
|
||||
↓
|
||||
系统库 .so:libi2c4cj(I2C glibc 封装)、libsnd4cj(音频)、libmt4cj/libfft4cj/libcnn4cj(数学)
|
||||
```
|
||||
|
||||
### 2.2 迁移范围源码清单
|
||||
|
||||
| 仓颉源文件 | 行数 | 职责 | 迁移去向 |
|
||||
|-----------|------|------|---------|
|
||||
| `src/main.cj` | 178 | CLI 入口、stdin 管道、--test 分支 | `pybot/main.py` |
|
||||
| `src/P.cj` | 264 | MatrixParser(FSM)+ MatrixObject + MatrixSaver + 解析异常 | `pybot/parser.py` |
|
||||
| `src/r.cj` | 1072 | RobotStruct + 硬件变量基类 + 6 个工厂 + Lua 状态机桥接 | `pybot/robot.py` + `pybot/hardvars.py` |
|
||||
| `src/hd/I2C.cj` + `src/cpp/I2C.c` | 340+ | I2C glibc 封装 + 每 fd 互斥锁 | `pybot/hd/i2c.py` |
|
||||
| `src/hd/Servo.cj` | 31 | max/min/offset 校准 + 负 max 角度翻转 | `pybot/hd/servo.py` |
|
||||
| `src/hd/MPU6050.cj` | 244 | 6 轴传感器 + 后台采样线程 + 卡尔曼 | `pybot/hd/mpu6050.py` |
|
||||
| `src/hd/snd.cj` + `src/cpp/snd.cpp` | 23+ | 扬声器(仓颉侧仅 sleep 占位) | `pybot/hd/speaker.py` |
|
||||
| `src/hd/PCA9685.cj` | — | 已标记"由 Lua 取代" | 不迁移(走插件) |
|
||||
| `modules/*.lua`(PCA9685/ADS1115/ADS1256/SPI/TEST) | — | Lua 硬件驱动 | `pybot/modules/*.py` 插件 |
|
||||
| `src/math/*.cj`、`src/cpp/cnn|fft|Matrix|kalman`、`src/cuda/` | ~2000 | 自研 NN/数学库 | **不迁移**(actpred 承担);卡尔曼滤波例外,MPU6050 需要,~40 行纯 Python |
|
||||
|
||||
### 2.3 关键语义(必须逐项对齐)
|
||||
|
||||
1. **占位符 `-`**:递归沿用上一行同列值;首行为 `-` 时,`parseMt` 解析为 `0`(数值列),`getMt` 返回字面 `-`。
|
||||
⚠️ 注意与 actpred `mt_parser.py` 的差异:后者首行 `-` 用 `@Servo` 初始角度填充(这是 ML 数据管线的语义)。**pybot 运行时语义以仓颉版为准(填 0)**,因为运行时 Servo 有 max/min 钳位保护;数据管线语义留在 actpred 不变。
|
||||
2. **@Action 列名 `dev.param`**:点号拆分为硬件名 + 参数名;参数名经 `@Alias` 表转换(`A`→`angle`、`F`→`frequency`)后传入硬件。
|
||||
3. **每行执行**:同一行按硬件聚合参数字典,逐硬件调用一次 `play()`;行间时延由 `spk.delay` 参数承担。
|
||||
4. **硬件生命周期**:`active()` 按创建顺序 build,`deactive()` 倒序 destroy,且必须在 `try/finally` 中保证执行。
|
||||
5. **Servo 角度算法**:`a = angle + offset; a = clamp(a, |min|, |max|); if max < 0: a = 180 - a`。
|
||||
6. **PCA9685 脉宽映射**:50Hz、0°→0.5ms、180°→2.5ms、`offval = width * 4096 / 20`、寄存器 `0x06 + 4*channel` 写 4 字节。
|
||||
7. **MPU6050**:±500°/s、±8g、10ms 采样周期、卡尔曼滤波(Q=20, R=200)、roll/pitch 由加速度计反正切求得。
|
||||
8. **I2C 互斥**:同一 fd 上"设地址 + 读/写寄存器"必须是原子序列(多设备共享总线)。
|
||||
|
||||
### 2.4 审计遗留(迁移时修复的输入清单)
|
||||
|
||||
来自 [bot4cj_CODE_AUDIT_20260808.md](../reviews/bot4cj_CODE_AUDIT_20260808.md):3 项 P0(S01 列数校验恒假 / S02 调试 print 污染 stdout / S03 扬声器无实声)、5 项 P1(S04 转义不一致 / S05 MPU6050 地址重复 / S06 有符号读寄存器 / S07 Lua 路径硬编码 / S08 文档滞后)、7 项 P2(无测试、注释代码、锁扫描 O(n)、无编译告警、消费端报错不可读、线程退出、GC 析构不确定)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 目标架构
|
||||
|
||||
### 3.1 目录结构
|
||||
|
||||
```
|
||||
pybot/
|
||||
├── pybot/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # CLI 入口(argparse:--help / --test / -i / -o 预留)
|
||||
│ ├── parser.py # MatrixParser + MatrixObject + MatrixSaver + 解析异常
|
||||
│ ├── robot.py # RobotStruct(createHardVars / active / run / deactive / save)
|
||||
│ ├── hardvars.py # HardwareVar 基类 + Alias/I2C/Servo/Speaker/MPU6050_BIND 工厂
|
||||
│ ├── plugin.py # Python 插件加载器(替代 Lua 状态机桥接)
|
||||
│ └── hd/
|
||||
│ ├── __init__.py
|
||||
│ ├── i2c.py # fcntl.ioctl I2C + 每 fd 锁(dict + threading.Lock)
|
||||
│ ├── servo.py # 舵机校准与翻转
|
||||
│ ├── mpu6050.py # 传感器 + threading.Thread 采样 + kalman
|
||||
│ ├── kalman.py # 一维卡尔曼滤波(~40 行)
|
||||
│ └── speaker.py # 正弦波发声(可选 alsaaudio,默认 aplay 子进程 / 静默降级)
|
||||
├── modules/ # 硬件驱动插件(Python 替代 Lua)
|
||||
│ ├── PCA9685.py
|
||||
│ ├── ADS1115.py
|
||||
│ ├── ADS1256.py
|
||||
│ └── SPI.py
|
||||
├── scripts -> ../bot4cj/scripts # 软链或复制,.mt 脚本共享
|
||||
├── tests/
|
||||
│ ├── test_parser.py
|
||||
│ ├── test_hardvars.py
|
||||
│ ├── test_servo.py
|
||||
│ └── mock_i2c.py # Mock I2C 总线(记录寄存器写序列,供断言)
|
||||
├── _run.sh # cat x.mt | bash _run.sh(exec python3 -m pybot)
|
||||
├── _set.sh # 无编译;可选建 venv 装 pytest
|
||||
├── requirements.txt # 核心为空;[dev] pytest
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### 3.2 数据流(与仓颉版一致)
|
||||
|
||||
```
|
||||
stdin → MatrixParser.parse()(逐块产出 MatrixObject)
|
||||
→ RobotStruct.create_hardvars()(按块名分发工厂;未知块 → plugin.load("modules/<Name>.py"))
|
||||
→ robot.active()(按序 build)
|
||||
→ robot.run()(逐行 @Action:聚合参数 → 逐硬件 play(dict))
|
||||
→ robot.deactive()(倒序 destroy,try/finally 保证)
|
||||
```
|
||||
|
||||
### 3.3 关键映射决策
|
||||
|
||||
| 仓颉版机制 | Python 版方案 | 理由 |
|
||||
|-----------|--------------|------|
|
||||
| `JsonValue` 统一通信协议 | 原生 `dict`(进程内直接传递) | 无跨语言边界后无需序列化;插件协议文档化 dict 键约定 |
|
||||
| 三闭包 rise/things/free | `HardwareVar` 基类三方法 `build()/play(params)/destroy()`,子类覆写 | Python 惯用法;闭包捕获改为实例属性 |
|
||||
| Lua 状态机 + C 函数注册(i2c_open 等 11 个) | `importlib` 动态加载 `modules/<Name>.py`,插件直接 `from pybot.hd import i2c` | 消除整个 FFI 层;插件与核心同语言 |
|
||||
| 20 槽 I2C 锁数组(O(n) 线性扫描) | `dict[fd → threading.Lock]` + 全局注册锁 | 修复审计 Q03;容量不再受限 |
|
||||
| pthread_create/pthread_cancel(MPU6050 采样) | `threading.Thread(daemon=True)` + `threading.Event` 停止标志 | 修复审计 Q06/D3,安全退出 |
|
||||
| 仓颉 GC 终结器不确定 | 上下文管理器 `with RobotStruct(...) as robot:` + `atexit` 兜底 | 修复审计 Q07/D1,确定性释放 fd |
|
||||
| cjpm build + Makefile .so | 无构建步骤 | 树莓派上 `git pull` 即部署 |
|
||||
|
||||
### 3.4 插件协议(modules/*.py,替代 Lua build/call/destroy)
|
||||
|
||||
```python
|
||||
# modules/PCA9685.py —— 与 PCA9685.lua 逐语义对齐
|
||||
def build(ctx: dict) -> dict:
|
||||
"""ctx: 元数据(fd/address/... 来自 @PCA9685 块行 + 引用硬件的 play() 展开)
|
||||
初始化:重启 → SLEEP → 设 prescale(50Hz) → 唤醒"""
|
||||
def call(ctx: dict) -> dict:
|
||||
"""ctx 额外含实时参数(channel/angle);写 4 个 PWM 寄存器"""
|
||||
def destroy(ctx: dict) -> dict:
|
||||
"""复位 PCA9685"""
|
||||
```
|
||||
|
||||
- 插件查找顺序(修复审计 S07):`$PYBOT_MODULES_PATH`(冒号分隔多路径)→ 包内置 `modules/` → 当前工作目录 `./modules/`。
|
||||
- 加载失败抛出含插件名与搜索路径的 `PluginNotFound` 异常。
|
||||
|
||||
---
|
||||
|
||||
## 4. 详细设计
|
||||
|
||||
### 4.1 parser.py
|
||||
|
||||
- **MatrixObject**:`name: str` + `rows: list[list[str]]`;提供 `__getitem__((row, col))`、`index_of_col(row, key)`、`save(writer)`。越界访问抛 `MatrixIndexError(块名, 行, 列)`(修复审计 Q05 的报错可读性)。
|
||||
- **MatrixParser**:保持 FSM 逐字符解析(非正则),状态与仓颉版一致(nl/cm/nm 三标志),原因:`@` 块嵌套注释、行内注释、任意空白分隔的兼容性已被现有脚本验证。
|
||||
- 修复 S01:`submit_line` 用 `len(grp) != len(rows[-1])` 校验列数,不一致抛 `DoesNotSync(行号, 期望列数, 实际列数)`。
|
||||
- 修复 S02:删除逐字符 debug 打印;提供 `--verbose` 时输出到 **stderr**(stdout 保留给未来管道通信)。
|
||||
- 修复 S04:Python 无 Rune/String 混淆问题,换行统一 `"\n"` 字符比较;补充单测覆盖"漏写分号跨行"场景。
|
||||
- **占位符语义**(§2.3-1):`get_mt(obj, row, col)` 递归向上;`parse_mt(obj, row, col, type)` 首行 `-` 返回 `type("0")`。
|
||||
- **MatrixSaver**:保持与仓颉版相同的输出格式(`@Name` / 空格分隔 / `;` 结尾 / `@` 收尾),保证 `--test matrix_o/matrix_2` 的解析↔序列化往返可用。
|
||||
|
||||
### 4.2 hd/i2c.py
|
||||
|
||||
```python
|
||||
I2C_SLAVE = 0x0703
|
||||
class I2CBus:
|
||||
def __init__(self, path): self.fd = os.open(path, os.O_RDWR); _register_lock(self.fd)
|
||||
def set_addr(self, addr): fcntl.ioctl(self.fd, I2C_SLAVE, addr)
|
||||
def write_reg(self, reg, val): os.write(self.fd, bytes([reg, val]))
|
||||
def read_reg(self, reg) -> int: # 返回 0..255 无符号(修复 S06)
|
||||
os.write(self.fd, bytes([reg])); return os.read(self.fd, 1)[0]
|
||||
def transaction(self): # with bus.transaction(): 原子序列(设地址+读写)
|
||||
```
|
||||
|
||||
- 锁注册表:`_LOCKS: dict[int, threading.Lock]` + `_MAIN_LOCK`;`transaction()` 上下文管理器持锁,替代仓颉版 `I2cLock_lock/unlock` 手动配对。
|
||||
- `read_reg` 天然返回无符号 int(`bytes[0]` ∈ 0..255),16 位拼接 `(hh << 8) | ll` 后用 `struct.unpack('>h')` 转有符号——修复 S06 的符号扩展错误且保留传感器数据的正确符号。
|
||||
|
||||
### 4.3 hd/servo.py
|
||||
|
||||
与 `Servo.cj` 逐行对齐(§2.3-5),构造参数 `max/min/offset/set_angle_callback`。补单测:正装(max=90)、反装(max=-180 翻转)、offset 漂移、越界钳位。
|
||||
|
||||
### 4.4 hd/mpu6050.py
|
||||
|
||||
- 寄存器常量与 `MPU6050.cj` 一致;**修复 S05**:`ADDR_0 = 0x68`、`ADDR_1 = 0x69`。
|
||||
- 采样线程:`threading.Thread` + `self._stop = threading.Event()`,循环 `while not self._stop.wait(0.01)`(10ms 周期且可即时打断)——修复 Q06。
|
||||
- 读传感器整体包在 `bus.transaction()` 中(设地址 + 14 字节连读),比仓颉版逐寄存器 12 次 I2C 往返更高效且原子。
|
||||
- 卡尔曼滤波参数保持 Q=20、R=200;roll/pitch 公式不变。
|
||||
- 数据出口:`latest()` 返回带 `threading.Lock` 保护的最近一帧滤波值(仓颉版 mutex 字段从未真正使用,此处补全)。
|
||||
|
||||
### 4.5 hd/speaker.py(决策 S03)
|
||||
|
||||
仓颉版 `play()` 仅 sleep(实声 C++ 函数被注释)。Python 版决策:**实现真实发声,带静默降级**。
|
||||
|
||||
- 首选:标准库 `wave` + `subprocess.run(["aplay", "-q", tmp.wav])`——树莓派自带 alsa-utils,零 pip 依赖;正弦波生成用 `math.sin`(每次 ≤ 数秒 @48kHz,纯 Python 可接受),保留 5ms 淡入淡出。
|
||||
- `aplay` 不存在或播放失败:log 一次警告后仅 sleep(delay),行为与仓颉版一致(延时职责不丢失)。
|
||||
- `delay` 语义不变:`play()` 阻塞 `delay` 毫秒(发声与延时并行时取延时为准)。
|
||||
|
||||
### 4.6 robot.py / hardvars.py
|
||||
|
||||
- `RobotStruct` 保持仓颉版结构:`C: list[MatrixObject]` + `M: dict[str, HardwareVar]`(Python dict 有序,天然满足"倒序 destroy"需求)。
|
||||
- `create_hardvars()` 分发表:`{"I2C": I2CVar, "Alias": AliasVar, "Servo": ServoVar, "Speaker": SpeakerVar, "MPU6050_BIND": MPU6050Var}`;`Action` 跳过;未知块 → `plugin.PluginVar`。
|
||||
- 重名键检查(仓颉版 FIXME r.cj:911):同名硬件变量重复定义时,先 `destroy()` 旧值再覆盖,并输出 stderr 警告。
|
||||
- `run()` 逐行执行 `@Action`;列名合法性(必须含一个 `.`)在**执行前**整表校验一次,报错含列号与列名(配合 Q05 修复)。
|
||||
- 上下文管理器:`__enter__`→`active()`,`__exit__`→`deactive()`;`main.py` 用 `with` 替代仓颉版 try/finally。
|
||||
|
||||
### 4.7 main.py
|
||||
|
||||
| 仓颉版 CLI | Python 版 | 说明 |
|
||||
|-----------|-----------|------|
|
||||
| (无参)stdin 管道 | 同 | `cat std.mt | ./bot` 不变;另支持 `pybot path/to/x.mt` 位置参数(增强,非破坏) |
|
||||
| `--help` | argparse 自动生成 | 文案更新 |
|
||||
| `--test nn/cnn/cnn1` | **移除**(见 §1.3) | NN 由 actpred 承担 |
|
||||
| `--test matrix_o/matrix_2` | `--test roundtrip <in.mt> <out.mt>` | 解析↔序列化往返测试,路径改为参数(仓颉版硬编码 `ss/test.mt`) |
|
||||
| `-i` / `-o`(DRL 预留) | 保留参数位,标注 reserved | 未来对接 actpred |
|
||||
| (新增)`--dry-run` | 解析 + 构建硬件变量但不开 I2C,Mock 总线打印动作序列 | 无硬件调试 .mt 脚本 |
|
||||
| (新增)`--verbose` | FSM 与硬件调用日志到 stderr | 替代 S02 删掉的调试打印 |
|
||||
|
||||
### 4.8 日志规范
|
||||
|
||||
- 所有诊断输出走 `logging`(stderr),stdout 保留纯净——为未来 actpred 管道集成预留(审计 §六 的阻塞项 S02 根治)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 审计发现修复映射表
|
||||
|
||||
| 审计 ID | 级别 | 仓颉版问题 | Python 版修复方式 | 落点 |
|
||||
|---------|------|-----------|------------------|------|
|
||||
| S01 | P0 | submitLine 列数校验恒假 | `len(grp) != len(rows[-1])` 显式校验 + DoesNotSync 带期望/实际列数 | parser.py |
|
||||
| S02 | P0 | parse() 逐字符 print 污染 stdout | 移除;--verbose 走 stderr logging | parser.py / main.py |
|
||||
| S03 | P0 | 扬声器仅 sleep 无实声 | wave + aplay 实现发声,缺 aplay 静默降级为 sleep | hd/speaker.py |
|
||||
| S04 | P1 | r"\n" 与 r'\n' 转义不一致 | Python 无此坑;补"漏分号跨行"单测防回归 | tests/test_parser.py |
|
||||
| S05 | P1 | MPU6050 ADDR_1 应为 0x69 | `ADDR_1 = 0x69` | hd/mpu6050.py |
|
||||
| S06 | P1 | readI2cReg 返回有符号 Int8 | read_reg 返回 0..255;16 位拼接后 struct 显式转有符号 | hd/i2c.py |
|
||||
| S07 | P1 | Lua 路径硬编码 ./modules/ | `$PYBOT_MODULES_PATH` → 包内置 → cwd 三级查找 | plugin.py |
|
||||
| S08 | P1 | README 已知问题表滞后 | pybot README 重写;bot4cj README 标注维护状态 | README.md |
|
||||
| Q01 | P2 | 无单元测试 | pytest:解析器/占位符/Servo/插件/Mock I2C 全流程 | tests/ |
|
||||
| Q02 | P2 | 大量注释旧代码 | 新代码库不携带 | — |
|
||||
| Q03 | P2 | I2C 锁 O(n) 线性扫描 | dict[fd → Lock] | hd/i2c.py |
|
||||
| Q04 | P2 | Makefile 告警关闭 | 无 C 编译;CI 跑 `python -m pyflakes`(可选) | _set.sh |
|
||||
| Q05 | P2 | 列数不匹配报错不可读 | MatrixIndexError 带块名/行/列 + run() 前整表校验 | parser.py / robot.py |
|
||||
| Q06 | P2 | pthread_cancel 非安全点退出 | threading.Event 优雅停止 | hd/mpu6050.py |
|
||||
| Q07 | P2 | GC 析构不确定,fd 泄漏 | with 上下文管理器 + atexit 兜底 | robot.py / main.py |
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试计划
|
||||
|
||||
### 6.1 单元测试(无硬件,x86 可跑)
|
||||
|
||||
| 用例组 | 覆盖 |
|
||||
|--------|------|
|
||||
| test_parser | 全部 `scripts/*.mt` 解析成功;列数不一致抛 DoesNotSync;漏分号跨行;注释/空行/CRLF;roundtrip(解析→save→再解析等价) |
|
||||
| test_placeholder | `-` 递归沿用;首行 `-` 数值列=0;与 actpred mt_parser 语义差异的显式断言(防止两边被误"统一") |
|
||||
| test_servo | 正装/反装翻转/offset/钳位边界 |
|
||||
| test_hardvars | @Alias 转换;@Action 列名拆分;同行参数聚合;重名覆盖警告 |
|
||||
| test_plugin | 插件三级路径查找;PluginNotFound 报错内容;PCA9685.py 用 Mock I2C 断言寄存器写序列(prescale=121、reg=0x06+4*ch、offval 字节序) |
|
||||
| test_mpu6050 | Mock 总线注入 14 字节样本 → 有符号拼接正确(高字节 ≥ 0x80 场景,回归 S06);Event 停止 1s 内退出 |
|
||||
|
||||
### 6.2 实机验证(树莓派 + Dashun 蜘蛛机器人)
|
||||
|
||||
1. `cat scripts/std.mt | bash _run.sh`:站立姿态与仓颉版目测一致(10 舵机角度逐一对照 --verbose 日志)。
|
||||
2. `jump.mt` / `lie-down.mt` / `tilt-*.mt` 逐个回放,动作时序无可感知差异。
|
||||
3. 双 MPU6050(0x68 + 0x69)同总线并行采样(回归 S05),roll/pitch 数值与仓颉版单设备输出同量级。
|
||||
4. 扬声器 `spk.F` 发声可闻(回归 S03),无爆音(淡入淡出生效)。
|
||||
5. Ctrl-C 中断:舵机停止、I2C fd 全部释放(`ls /proc/<pid>/fd` 验证)、MPU6050 线程退出。
|
||||
|
||||
### 6.3 交叉验证
|
||||
|
||||
- `--dry-run` 模式下用 Mock 总线记录全部 I2C 写序列,与仓颉版(打补丁记录)同脚本输出逐字节 diff——作为迁移等价性的最强证据(可参考 captcha2 迁移的像素级验证经验)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 里程碑
|
||||
|
||||
| 阶段 | 内容 | 产出 |
|
||||
|------|------|------|
|
||||
| M1 解析器 | parser.py + 全量单测 + roundtrip | 全部 scripts/*.mt 解析通过 |
|
||||
| M2 硬件抽象 | hardvars.py + robot.py + plugin.py + Mock I2C | --dry-run 全流程可跑(x86) |
|
||||
| M3 硬件落地 | i2c.py / servo.py / mpu6050.py / speaker.py + modules/*.py | 树莓派实机 std.mt 站立 |
|
||||
| M4 等价性验证 | §6.2 实机回放 + §6.3 写序列 diff | 验证报告 |
|
||||
| M5 切换 | README/INDEX 更新、bot4cj 标注 legacy、aibox/BotOps 若有引用改指 pybot | 并存期结束评估 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 风险与对策
|
||||
|
||||
| 风险 | 影响 | 对策 |
|
||||
|------|------|------|
|
||||
| Python GIL + 解释器抖动影响动作时序 | 舵机动作节奏轻微不均 | 动作行间延时 ≥ 20ms,抖动 <1ms 不可感知;MPU6050 采样用 Event.wait 定时,不忙等;量化依据见附录 A |
|
||||
| aplay 子进程首次启动延迟 | 首个音符滞后 | build() 时预热(播放 0 长度静音);或后续可选接 alsaaudio |
|
||||
| 仓颉版行为的未记录细节 | 等价性缺口 | §6.3 写序列逐字节 diff 兜底 |
|
||||
| .mt 脚本方言分裂(pybot vs actpred 解析器) | 语义漂移 | test_placeholder 显式断言两者差异;长期可评估 actpred 复用 pybot parser 的只读模式 |
|
||||
| 树莓派 Python 版本参差 | 语法兼容 | 目标 Python ≥ 3.9(树莓派 OS Bullseye 起自带 3.9) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 与 actpred 的集成展望(非本期范围)
|
||||
|
||||
迁移完成后,"大脑-身体"闭环的最短路径:
|
||||
|
||||
```
|
||||
actpred predict.py(图像+历史 → 动作向量 action_dim=18)
|
||||
→ 向量转 @Action 单行(pybot 提供 action_row_from_vector() 工具函数)
|
||||
→ pybot RobotStruct 增量执行(对应仓颉版 TODO:@Action 位置指示器)
|
||||
→ MPU6050 姿态反馈回流 actpred(DRL 奖励信号)
|
||||
```
|
||||
|
||||
同语言后,上述管线可在单进程内完成,无需序列化协议——这正是本次迁移最大的战略收益。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:性能评估依据
|
||||
|
||||
迁移评审中最常见的两个疑虑是"Python 做驱动会不会慢"与"Python 替代 Lua 会不会引入开销"。本附录给出量化分析,结论:**两者均不构成风险,后者反而是性能收益**。
|
||||
|
||||
### A.1 Python 做硬件驱动的时间尺度分析
|
||||
|
||||
bot4cj 架构中所有微秒级时序都已下沉到专用硬件芯片(PCA9685 自主生成 PWM 波形),主机侧只发送设定值,该路径的瓶颈是 I2C 总线物理速率,与宿主语言无关:
|
||||
|
||||
| 环节 | 耗时量级 | 由谁承担 |
|
||||
|------|---------|---------|
|
||||
| 舵机 PWM 波形(50Hz、0.5~2.5ms 脉宽) | µs 级精度 | PCA9685 硬件芯片,主机不参与 |
|
||||
| I2C 写一个寄存器(100kHz 标准模式) | ~200–300 µs/次 | 总线物理速率,语言无关 |
|
||||
| Python 一次 `os.write`/`ioctl` 调用开销 | ~2–5 µs | 解释器 + 系统调用 |
|
||||
| 设一个舵机角度(4 次寄存器写) | ~1 ms | 总线 ≈ 99%,Python ≈ 1% |
|
||||
| 舵机机械转动 60° | ~100–200 ms | 物理惯性 |
|
||||
| `.mt` 动作行间延时 | 20–500 ms | 脚本显式指定 |
|
||||
|
||||
Python 解释开销比总线时间低约两个数量级,比机械动作低四五个数量级。逐项检查潜在慢点:
|
||||
|
||||
- **MPU6050 10ms 采样循环**:14 字节连读 ≈ 1.5–2ms 总线时间 + 卡尔曼滤波 6 次浮点(纯 Python 数 µs),周期内富余充足;且连读比仓颉版逐寄存器 12 次往返(每次重发从机地址)更快。
|
||||
- **正弦波生成**:48kHz 下每秒音频约 4.8 万次 `math.sin`,纯 Python 10–30ms 算完,发声前一次性生成,可接受。
|
||||
- **GIL/GC**:仅主线程 + 1 个采样线程,GIL 无竞争;GC 停顿亚毫秒级,低于 `sleep()` 本身的 ms 级精度。
|
||||
|
||||
Python 不适用的边界(bot4cj 均不涉及):主机 GPIO bit-bang 生成 PWM/WS2812 单线协议、>1kHz 闭环控制、硬实时保证(Linux 非 RTOS,仓颉版同样做不到)。若未来 DRL 需要高频控制环,正确做法是下沉到协处理器(如 RP2040),属架构问题而非语言问题。
|
||||
|
||||
### A.2 Python 插件替代 Lua 桥接的开销对比
|
||||
|
||||
单论解释器速度 Lua 5.4 快于 CPython,但现有 Lua 路径的开销主要在跨语言桥接而非驱动代码本身。仓颉版每次硬件调用([r.cj MtVars_Lua.things](../../bot4cj/src/r.cj#L311-L331))的实际成本:
|
||||
|
||||
| 步骤 | 实现 | 开销量级 |
|
||||
|------|------|---------|
|
||||
| 元数据深拷贝 | `JsonValue.fromStr(metadata.toJsonString())`——JSON 序列化+反序列化一整轮 | ~10–50 µs |
|
||||
| 参数推入 Lua 表 | 每键 `mallocCString` + `Lua_pushstring/setfield` + `free`,跨仓颉→C→Lua 三层 FFI | ~几 µs/键 |
|
||||
| `Lua_pcallk` + 返回表遍历 | `Lua_next` 循环 + 每键值 `Lua_tostring` 转仓颉 String | ~几 µs |
|
||||
| 强制调试打印 | 仓颉侧 2 次 `println` + Lua 侧 `print_table` 全表打印 | ~几十 µs 至 ms 级(终端 I/O) |
|
||||
| Lua 内 4 次 `i2c_write_reg` | 每次跨 Lua→C→仓颉 @C 三层 | 桥接 ~1 µs + 总线 ~250 µs |
|
||||
|
||||
合计框架开销 ~0.1–1 ms/次,与 I2C 总线时间同量级。Python 插件路径的对应成本:
|
||||
|
||||
| 步骤 | 实现 | 开销量级 |
|
||||
|------|------|---------|
|
||||
| 上下文构造 | `{**metadata, **params}` dict 合并(同进程传引用,无序列化) | ~0.5 µs |
|
||||
| 插件调用 | 同解释器直接函数调用 | ~0.1 µs |
|
||||
| I2C 写 | 插件直接 `i2c.write_reg()`,一层 `os.write` 到底 | 系统调用 ~2 µs + 总线 ~250 µs |
|
||||
| 诊断输出 | 默认关闭,`--verbose` 时走 stderr logging(即审计 S02 修复) | 0 |
|
||||
|
||||
框架开销 ~1–2 µs/次,比 Lua 桥接低约两个数量级。插件内部计算(脉宽换算、移位掩码)在两种解释器上的差距为百纳秒级,相对 1ms 总线时间可忽略。
|
||||
|
||||
**结论**:Lua 层在仓颉版存在的唯一理由是"编译型宿主做不了运行时扩展",为此付出 JSON 往返 + 五层 FFI(仓颉→C→Lua→C→仓颉→glibc)的税;宿主换成 Python 后调用链缩短为 Python→glibc 一层,此项迁移是删除一整层间接性,而非语言互换。
|
||||
|
||||
---
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [bot4cj 代码审计报告](../reviews/bot4cj_CODE_AUDIT_20260808.md)(15 项发现)
|
||||
- [bot4cj 深度分析报告](bot4cj_analysis_20260525_00.md)(D1-D29)
|
||||
- [actpred 与 bot4cj 研究报告](actpred_bot4cj_research_20260809.md)
|
||||
- [bot4cj README.robot.md](../../bot4cj/README.robot.md)
|
||||
- [actpred mt_parser.py](../../actpred/mt_parser.py)(Python 解析器先例)
|
||||
|
||||
---
|
||||
|
||||
*本文档为开发计划,实施前需结合实际硬件环境评估。*
|
||||
Reference in new issue
Block a user