docs: 新增 .qoder Repowiki 知识库文档
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""把 ECAT 侧收到的每一拍信号全量落盘,供事后分析(不在线判优劣)。
|
||||
|
||||
去重规则只有一条:同一 (node,seq) 或同一 (id,data) 帧只在第一次出现时写一行 ——
|
||||
重复采样不携带新信息,去掉后可以把时长拉长。其余一律保留。
|
||||
|
||||
行格式(CSV,第 1 列是相对起始的秒):
|
||||
M,t,node,ch,seq,online,dlc,hex36 段2 电机槽(STATUS 前 36B,含 cpu%@31/init@32-35)
|
||||
F,t,kind,ch,dlc,id,hex36 段3 xrx / 段4 rx_batch 的帧
|
||||
H,t,cycles,wkc,gseq,win_base,win_count,active,rx,tx,drop 每 20 拍一条头部与桥计数
|
||||
|
||||
用法: python scripts/rec_ecat_slots.py [秒数=120] [端口=8090] [输出文件]
|
||||
"""
|
||||
import csv
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
DUR_S = float(sys.argv[1]) if len(sys.argv) > 1 else 120.0
|
||||
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 8090
|
||||
OUT = sys.argv[3] if len(sys.argv) > 3 else "ecat_slots_raw.csv"
|
||||
|
||||
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 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 main():
|
||||
seen_m, seen_f = set(), set()
|
||||
polls = rows = 0
|
||||
t0 = time.time()
|
||||
with open(OUT, "w", newline="", encoding="ascii") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["#kind", "t", "a", "b", "c", "d", "e", "f"])
|
||||
while True:
|
||||
dt = time.time() - t0
|
||||
if dt >= DUR_S:
|
||||
break
|
||||
try:
|
||||
snap = once()
|
||||
except Exception:
|
||||
continue
|
||||
polls += 1
|
||||
h = snap.get("head") or {}
|
||||
b = snap.get("bridge") or {}
|
||||
if polls % 20 == 1:
|
||||
w.writerow(["H", f"{dt:.6f}", snap.get("cycles"), snap.get("wkc"),
|
||||
h.get("global_seq"), h.get("win_base"), h.get("win_count"),
|
||||
h.get("active"), b.get("rx_count"), b.get("tx_count"),
|
||||
b.get("drop")])
|
||||
for m in snap.get("motors") or []:
|
||||
if not m.get("data"):
|
||||
continue
|
||||
key = (m["node"], m["ch"], m["seq"])
|
||||
if key in seen_m:
|
||||
continue
|
||||
seen_m.add(key)
|
||||
if len(seen_m) > 400000: # 去重集合本身的内存上限
|
||||
seen_m = set(list(seen_m)[-200000:])
|
||||
d = bytes.fromhex(m["data"])[:36]
|
||||
w.writerow(["M", f"{dt:.6f}", m["node"], m["ch"], m["seq"],
|
||||
int(bool(m.get("online"))), m.get("dlc"), d.hex()])
|
||||
rows += 1
|
||||
def emit_frames(fr, kind):
|
||||
if not fr or not fr.get("data"):
|
||||
return
|
||||
d = bytes.fromhex(fr["data"])[:36]
|
||||
key = (fr.get("id"), d.hex())
|
||||
if key in seen_f:
|
||||
return
|
||||
seen_f.add(key)
|
||||
w.writerow([kind, f"{dt:.6f}", fr.get("ch"), fr.get("dlc"),
|
||||
fr.get("id"), "", "", d.hex()])
|
||||
|
||||
emit_frames(snap.get("xrx"), "X")
|
||||
for x in ((snap.get("rx_batch") or {}).get("frames") or []):
|
||||
emit_frames(x, "B")
|
||||
if len(seen_f) > 400000:
|
||||
seen_f = set(list(seen_f)[-200000:])
|
||||
print(f"落盘 {OUT}: 轮询 {polls} 次 / {DUR_S:.0f}s,写入 {rows} 行 "
|
||||
f"(电机槽 {len(seen_m)} 帧, 接收帧 {len(seen_f)})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user