docs: 新增 .qoder Repowiki 知识库文档
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user