Files
DCU_DesktopController/scripts/probe_ecat_vs_wifi.py
T

102 lines
3.6 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""ECAT 与 WiFi 两条链路对同一 seq 的 48B STATUS 做逐字节对照。
两条链路读的是 GD32 里同一张 MotorState 表,但读取上下文不同:
* ECAT 段2 由 buildInput 取表,跑在 PDI_Isr —— 会抢占主循环的写侧(本次修复点);
* WiFi 0xC5/buildPayload 与写侧同在主循环 —— 结构上不可能读到中间态。
所以 WiFi 侧可当参照:同一 seq 在两边必须逐字节相同(seq 由 global_seq 唯一确定,
一次更新只产生一个 seq)。不等 ⇒ ECAT 侧那一份是没写完的半成品。
用法: python scripts/probe_ecat_vs_wifi.py [秒数]
"""
import json
import socket
import struct
import sys
import time
import urllib.request
HOST, PORT = "127.0.0.1", 8090
DUR_S = float(sys.argv[1]) if len(sys.argv) > 1 else 60.0
ESP = "192.168.31.127"
REQ = (f"POST /api/motor/state HTTP/1.0\r\nHost: {HOST}:{PORT}\r\n"
"Content-Type: application/json\r\nContent-Length: 2\r\n"
"Connection: close\r\n\r\n{}").encode("ascii")
def ecat_once():
s = socket.create_connection((HOST, PORT))
try:
s.sendall(REQ)
buf = bytearray()
while True:
c = s.recv(65536)
if not c:
break
buf.extend(c)
finally:
s.close()
_, _, d = bytes(buf).partition(b"\r\n\r\n")
return json.loads(d.decode("utf-8", "replace"))
def wifi_once():
"""返回 {node: (seq, 48B)}。协议 ver=2:entry 92B,data 在 +12。"""
b = urllib.request.urlopen(f"http://{ESP}/motor/state?bin=1", timeout=3).read()
if len(b) < 12 or struct.unpack_from("<H", b, 0)[0] != 0x4D53 or b[2] != 2:
return {}
out, off = {}, 12
for _ in range(b[9]):
if off + 92 > len(b):
break
out[b[off]] = (struct.unpack_from("<I", b, off + 8)[0],
b[off + 12:off + 60])
off += 92
return out
def main():
cache = {} # node -> {seq: payload}(ECAT 侧见过的每一帧)
matched = mism = polls = wpolls = 0
bad = []
t0 = time.time()
while time.time() - t0 < DUR_S:
try:
snap = ecat_once()
polls += 1
for m in snap.get("motors") or []:
if m.get("online") and m.get("data"):
cache.setdefault(m["node"], {})[m["seq"]] = bytes.fromhex(m["data"])
if len(cache[m["node"]]) > 4000:
for k in sorted(cache[m["node"]])[:2000]:
del cache[m["node"]][k]
except Exception:
continue
try:
w = wifi_once()
wpolls += 1
except Exception:
continue
for node, (seq, payload) in w.items():
d = cache.get(node, {}).get(seq)
if d is None:
continue # 该 seq 未被 ECAT 侧采到,跳过
matched += 1
if d != payload:
mism += 1
bad.append((round(time.time() - t0, 2), node, seq, d.hex(), payload.hex()))
print(f"轮询 ECAT {polls} 次 / WiFi {wpolls} 次 / {DUR_S:.0f}s")
print(f"两侧同 seq 可比对 = {matched} 逐字节不一致 = {mism}")
for t, node, seq, e, w in bad[:6]:
j = next((i for i in range(48) if e[i] != w[i]), 48)
print(f" t={t}s node={node} seq={seq} 首字节差={j}")
print(f" ECAT={e}")
print(f" WiFi={w}")
if not matched:
print("功效不足:ECAT 帧率远高于 WiFi 采样,同 seq 交集为 0,本次判定不作数。")
if __name__ == "__main__":
main()