Files
cbb 2c6235d920 feat(ecat): PDO 快发 ESF1 桌面强制直通,堵死慢发回落
桌面 APP 下 ECAT motor 发送只走 pywebview ESF1→native TCP;
删除桥内 HTTP/JSON 回落。附延迟测量与 pack 自测脚本与规格文档。
2026-09-21 02:11:49 +08:00

119 lines
3.7 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""A/B:JSON motor/send vs ESF1 快发(HTTP send_raw / 持久 TCP)软件段延迟。
需要:tools/ecat ecat_native 已运行(总线可不必在 OP,未连接时会报未连接但仍计时)。
用法:
python scripts/check_esf_latency.py
python scripts/check_esf_latency.py --n 40
"""
from __future__ import annotations
import argparse
import json
import os
import socket
import statistics
import struct
import sys
import time
import urllib.request
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from esp32_server import pack_esf, unpack_fsok # noqa: E402
PAGE = os.environ.get("DCU_PAGE_BASE", "http://127.0.0.1:2625")
ECAT = PAGE + "/ecat/api"
def http(url, data: bytes, ctype="application/json", timeout=5.0):
req = urllib.request.Request(
url, data=data,
headers={"Content-Type": ctype, "Accept": "application/json"},
method="POST",
)
t0 = time.perf_counter()
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
body = r.read()
ms = (time.perf_counter() - t0) * 1000.0
try:
return ms, json.loads(body.decode("utf-8", "replace") or "{}")
except Exception:
return ms, {"_raw_len": len(body)}
except Exception as e:
return (time.perf_counter() - t0) * 1000.0, {"ok": False, "error": str(e)}
def pct(xs, p):
if not xs:
return None
xs = sorted(xs)
k = max(0, min(len(xs) - 1, int(round((p / 100.0) * (len(xs) - 1)))))
return xs[k]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--n", type=int, default=30)
args = ap.parse_args()
frame = {"ch": 2, "id": 0x00030200, "dlc": 32,
"data": "02" + "00" * 29 + "000000"} # 静止 CMD
frames = [frame]
esf = pack_esf(frames)
js = json.dumps({"frames": frames}, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
ms_h, health = http(ECAT + "/native/health", b"{}")
print(f"health {ms_h:.2f}ms {json.dumps(health, ensure_ascii=False)[:200]}")
fsp = int(health.get("fast_send_port") or 0)
print(f"fast_send_port={fsp}")
json_ms, raw_ms = [], []
for _ in range(args.n):
ms, _ = http(ECAT + "/motor/send", js)
json_ms.append(ms)
ms, _ = http(ECAT + "/motor/send_raw", esf, "application/octet-stream")
raw_ms.append(ms)
tcp_ms = []
if fsp > 0:
sock = socket.create_connection(("127.0.0.1", fsp), timeout=None)
sock.settimeout(2.0)
try:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except Exception:
pass
for _ in range(args.n):
t0 = time.perf_counter()
sock.sendall(esf)
resp = b""
while len(resp) < 8:
c = sock.recv(8 - len(resp))
if not c:
raise OSError("closed")
resp += c
tcp_ms.append((time.perf_counter() - t0) * 1000.0)
r = unpack_fsok(resp)
if not r.get("ok") and not tcp_ms:
print("TCP 快发 ok=false", r)
sock.close()
def show(name, xs):
if not xs:
print(f"{name}: (skip)")
return
print(f"{name}: n={len(xs)} mean={statistics.mean(xs):.3f}ms "
f"p50={pct(xs,50):.3f} p90={pct(xs,90):.3f} min={min(xs):.3f} max={max(xs):.3f}")
show("JSON motor/send ", json_ms)
show("HTTP send_raw ", raw_ms)
show("TCP fast_send ", tcp_ms)
print("口径:HTTP 客户端侧 POST 往返(含本机代理时);不含浏览器 JS/pywebview hop。")
return 0
if __name__ == "__main__":
sys.exit(main())