big-bang 迁移(硬约束: Npcap 网卡独占, 无法双主站并存), 四阶段真机验收的 完整契约与对拍记录见 MIGRATION.md —— Python 已删, 该文件是唯一行为基准。 - 新增 ecat_native.c: 与 Python 同构的全路由 native 守护 —— 总线生命周期 open/close/status/op.start/op.stop, 电机 motor.start/stop/spin/state/ send/rx/period/dbg, can/send|state, 配置维护 sdo/eeprom.read|write| verify/flash/sync_diag, 静态托管 src/web, shutdown/focus/--parent-pid 看门狗/独立启动清场/端口漂移; 独占 8090 即生产链路。PDO 热循环天花板 ~5kHz → ~7kHz。 - 删除 src/ecat_server.py(2826 行)、src/test_motor_backend.py(684 行)、 ecat_app.vbs(桌面窗口随 pywebview 依赖一并移除): Python 后端不留副本。 - 编码回归改金标准: native_enc_diff.py 对照 native_enc_golden.json (阶段 2 与 Python _fill_can_out 逐字节对拍通过后固化, 不碰网卡), 当场抓掉 json_tok 不跳闭合引号会静默丢 motor/send 参数的真 bug。 - build_native.bat: 编译前跑 gen_eeprom.py 生成 eeprom_image.h 供 native #include —— 同一生成器同一套断言, 断言失败构建中止; 改 SII 重跑即可。 - 修 flash 探针字历史隐患(本文件与 ecat_native.c 同步改): SCRATCH_WORD 0x0200 实际落在 TxPDO 类别区内部(类别区已长到 0x0281), 单独跑 eeprom 探针不烧录会把真数据擦成空洞; 移到 SII END 之后的 0x0400。 - 启动器 ecat_web.vbs/bat 改拉 build_native\ecat_native.exe (--substr I226 --web src\web)。 - 收录 soem-src wpcap 链接库(vendored, 构建必需)。 两轮回归修复(细节见 MIGRATION.md): 1) op/start 卡 AL 0x001B(SM 看门狗): 固件"自己写 1s 看门狗"的假设破产, do_op_start 第 5 步改为 ~5ms 节奏喂帧 ≥1s 再请求 OP, 24s 两次失败 变 1.2s 进 OP。 2) OP 态数据冻结(从站 PDI_Isr SM2 分支被旗标挡住, 固件侧根因): 主站加 楔死看门狗(输出窗口轮转 + 回显冻结 128 周期判死)与 /api/motor/state 自动恢复(10s 冷却, 快照新增 wr 字段)。 已知差异: SDO 失败文本无 pysoem abort code 粒度; 250µs 亚毫秒周期从站 吃不下, 修复前勿用。build_native/ 构建产物入 .gitignore, 由脚本重建。
73 lines
3.1 KiB
Python
73 lines
3.1 KiB
Python
"""native_enc_diff.py — CAN 输出编码回归(不碰网卡,无 Python 后端依赖)。
|
||
|
||
阶段 4 切换后 Python 基准已删除;本脚本改为对照 native_enc_golden.json
|
||
(阶段 2 与 Python _fill_can_out 逐字节对拍通过后固化的 4 组用例)。
|
||
byte0 是 seq(守护进程状态相关,每次 enctest 都 +1),不参与比对;
|
||
其余 383 字节必须逐字节一致,另校验 seq 两两 +1(跳 0) 的递增语义。
|
||
|
||
用法:native 在 8090 跑着即可(不需要 open/OP):python native_enc_diff.py
|
||
重新生成金标准(需 legacy 备份里的 ecat_server.py,一般不需要):见 git 历史/zip。
|
||
"""
|
||
import json
|
||
import sys
|
||
import urllib.request
|
||
|
||
GOLDEN = "native_enc_golden.json"
|
||
URL = "http://127.0.0.1:8090/api/native/enctest"
|
||
|
||
|
||
def main():
|
||
g = json.load(open(GOLDEN, encoding="utf-8"))
|
||
cases = g["cases"]
|
||
body = {"cases": CASES}
|
||
req = urllib.request.Request(URL, data=json.dumps(body).encode(),
|
||
headers={"Content-Type": "application/json"})
|
||
with urllib.request.urlopen(req) as r:
|
||
d = json.loads(r.read())
|
||
got = d["hexes"]
|
||
ok = True
|
||
if len(got) != len(cases):
|
||
print(f"用例数不符: golden={len(cases)} native={len(got)}")
|
||
return 1
|
||
for i, (a, b) in enumerate(zip(cases, got)):
|
||
if a[2:] != b[2:]:
|
||
ok = False
|
||
diff = [j for j in range(2, len(a), 2) if a[j:j+2] != b[j:j+2]]
|
||
print(f"用例 {i}: 不一致!差异字节偏移 {diff[:12]}")
|
||
j = diff[0]
|
||
print(f" golden[{j}:{j+24}] = {a[j:j+24]}")
|
||
print(f" native[{j}:{j+24}] = {b[j:j+24]}")
|
||
else:
|
||
print(f"用例 {i}: ✅ 383B 逐字节一致(byte0=seq 除外)")
|
||
seqs = [int(h[:2], 16) for h in got]
|
||
|
||
def nxt(s):
|
||
s = (s + 1) & 0xFF
|
||
return 1 if s == 0 else s
|
||
inc = all(seqs[i + 1] == nxt(seqs[i]) for i in range(len(seqs) - 1))
|
||
print(f"seq 递增: {seqs} {'✅' if inc else '❌'}")
|
||
return 0 if ok and inc else 1
|
||
|
||
|
||
# 与阶段 2 对拍时同一组用例(纯数据)。改动这里 = 改动金标准语义,
|
||
# 必须连同 native_enc_golden.json 一起重新生成。
|
||
CASES = [
|
||
# 基础单帧:DLC32 电机 CMD
|
||
{"win": 0, "ch": 1, "id": 0x30200, "dlc": 32, "data": bytes(range(32)).hex(), "batch": []},
|
||
# 批量边界:20×DLC8(332B 区,每项 16B → 20 项 320B,第 21 项 336>332 越界)
|
||
{"win": 5, "ch": 0, "id": 0x123, "dlc": 8, "data": "AABBCCDDEEFF0011",
|
||
"batch": [{"ch": 1, "id": 0x300 + i, "dlc": 8, "data": "0102030405060708"} for i in range(21)]},
|
||
# dlc=0 只发批量;6×DLC64(每项 72B → 4 项 288B,第 5 项 360 越界)
|
||
{"win": 20, "ch": 2, "id": 0, "dlc": 0, "data": "",
|
||
"batch": [{"ch": 2, "id": 0xFFFFF, "dlc": 64, "data": "EE" * 64} for _ in range(6)]},
|
||
# 混合 DLC + win 保持
|
||
{"win": 255, "ch": 1, "id": 7, "dlc": 32, "data": "11" * 32,
|
||
"batch": [{"ch": 0, "id": 9, "dlc": 32, "data": "22" * 32},
|
||
{"ch": 1, "id": 10, "dlc": 4, "data": "33445566"},
|
||
{"ch": 2, "id": 11, "dlc": 12, "data": "55" * 12}]},
|
||
]
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|