106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
#!/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()
|