Files
DCU_ECAT_Master/native_enc_diff.py
cbb a56b702d83 feat!: Python 后端整体切换为原生 C/SOEM 守护进程(ecat_native)
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, 由脚本重建。
2026-09-05 16:01:15 +08:00

73 lines
3.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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())