refactor: 按角色分 web/ diag/ launch/ 三层, 根目录只留主程序

18 个文件平铺根目录太乱, 按角色归拢:
  web/     ecat.html/css + motor.html/js —— 前端资源
  diag/    ecat_scan / ecat_sniff / ecat_esc_regs —— 独立诊断脚本, 互不依赖
  launch/  5 个 .bat/.vbs 双击入口
根目录只留 ecat_server / ecat_tool / ecat_flash_eeprom / test_motor_backend:
这几个互相 import(tool→flash_eeprom, test→server), 挪进子目录就得改 import
或加包, 收益不抵成本。

配套改动:
  _STATIC_MAP 的值加 web/ 前缀 —— URL 路径刻意保持不变(/、/ecat.css、
  /motor.html), 页面内相对引用、书签、外部链接一律不用改。
  launch/ 里 %~dp0 现在指向 launch\, 五个启动器统一改为先取上一层作 ROOT
  (bat 用 %~dp0.., vbs 用两次 GetParentFolderName), run_scan 顺带指向
  diag\ecat_scan.py。

验证: 7 个 py 全编译通过; test_motor_backend 8 项全通过; 服务实起后 6 条
静态路由全 200 且 MIME 正确(js 是 application/javascript)、404 兜底正常;
Edge headless 渲染主页与 motor 页, CSS 294 规则加载、9 个节点样式生效、
零加载失败零 JS 异常; bat/vbs 的 ROOT 解析实测指到项目根, 五个目标文件
全部命中; ecat_tool adapters 与 diag/ecat_scan.py 实跑正常。
This commit is contained in:
cbb
2026-09-03 22:08:38 +08:00
parent 47ea85fb23
commit beb33ffe50
15 changed files with 62 additions and 31 deletions
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""抓 SOEM 自己发的 EtherCAT 帧,对照裸帧格式。
另开 pcap 句柄抓包,同时在同进程里跑 pysoem eeprom_read,hexdump 收发帧。
用法:ecat_sniff.py [adapter-substr]"""
import ctypes
import struct
import sys
import threading
import time
import pysoem
ECAT = b"\x88\xa4"
class PcapPktHdr(ctypes.Structure):
_fields_ = [("tv_sec", ctypes.c_long), ("tv_usec", ctypes.c_long),
("caplen", ctypes.c_uint32), ("len", ctypes.c_uint32)]
def main():
substr = sys.argv[1] if len(sys.argv) > 1 else "I226"
adapter = None
for a in pysoem.find_adapters():
desc = a.desc.decode() if isinstance(a.desc, bytes) else a.desc
if substr.lower() in desc.lower():
adapter = a
break
if adapter is None:
raise SystemExit("适配器未找到")
name = adapter.name.decode() if isinstance(adapter.name, bytes) else adapter.name
wpcap = ctypes.WinDLL("wpcap.dll")
wpcap.pcap_open_live.restype = ctypes.c_void_p
wpcap.pcap_open_live.argtypes = [ctypes.c_char_p] + [ctypes.c_int] * 3 + [ctypes.c_char_p]
wpcap.pcap_next_ex.restype = ctypes.c_int
wpcap.pcap_next_ex.argtypes = [ctypes.c_void_p,
ctypes.POINTER(ctypes.POINTER(PcapPktHdr)),
ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte))]
wpcap.pcap_close.restype = None
wpcap.pcap_close.argtypes = [ctypes.c_void_p]
errbuf = ctypes.create_string_buffer(256)
handle = wpcap.pcap_open_live(name.encode(), 65535, 1, 50, errbuf)
if not handle:
raise SystemExit(f"pcap_open_live 失败: {errbuf.value.decode(errors='replace')}")
def soem_work():
try:
master = pysoem.Master()
master.open(adapter.name)
master.config_init()
slave = master.slaves[0]
slave.eeprom_read(0)
master.close()
except Exception as e:
print(f"soem_work: {type(e).__name__}: {e}")
t = threading.Thread(target=soem_work, daemon=True)
t.start()
frames = []
deadline = time.time() + 4.0
ph = ctypes.POINTER(PcapPktHdr)()
pd = ctypes.POINTER(ctypes.c_ubyte)()
while time.time() < deadline and len(frames) < 8:
r = wpcap.pcap_next_ex(handle, ctypes.byref(ph), ctypes.byref(pd))
if r != 1:
continue
data = bytes(bytearray(pd[:ph.contents.caplen]))
if len(data) >= 14 and data[12:14] == ECAT:
frames.append(data)
wpcap.pcap_close(handle)
print(f"抓到 {len(frames)} 个 EtherCAT 帧")
for i, f in enumerate(frames):
src = ":".join(f"{b:02X}" for b in f[6:12])
print(f"\n[{i}] len={len(f)} src={src}")
for off in range(14, min(len(f), 14 + 48), 16):
chunk = f[off:off + 16]
print(f" +{off - 14:02d}: " + " ".join(f"{b:02X}" for b in chunk))
# 解析前两个 datagram 头字段,确定头部布局
elen = struct.unpack_from("<H", f, 14)[0] & 0x7FF
print(f" ecat_hdr.len={elen} type={struct.unpack_from('<H', f, 14)[0] >> 12}")
d = f[16:]
if len(d) >= 12:
cmd, idx, adp, ado, ln = struct.unpack_from("<BBHHH", d, 0)
print(f" dgram: cmd=0x{cmd:02X} idx={idx} adp=0x{adp:04X} "
f"ado=0x{ado:04X} len_field={ln} (len={ln & 0x7FF})")
if __name__ == "__main__":
main()