refactor: 入口提到根目录, 代码收进 src/

上一版把入口塞进 launch/、代码摊在根目录, 正好反了: 双击入口该第一眼看到,
实现细节该收起来。现在:
  根目录  5 个 .bat/.vbs 入口 + README + requirements
  src/    ecat_server / ecat_tool / ecat_flash_eeprom / test_motor_backend
  src/web    前端资源      src/diag   独立诊断脚本

启动器改为 cd 进 %~dp0src 再跑(vbs 用 CurrentDirectory = base & "\src"),
工作目录落在 src 才能让 tool→flash_eeprom、test→server 的 import 解析到。
ecat_server.py 的 BASE_DIR 取自 __file__, 与 src/web 仍是同层父子关系,
_STATIC_MAP 和窗口模式的子进程 spawn 都无需改动。

.gitignore 补 sii.json / *.sii / eeprom_*.bin —— eeprom read --out 的导出
产物, 属运行结果不该入库。

验证: 7 个 py 全编译通过; test_motor_backend 8 项全通过; ecat.bat 与
run_scan.bat 实跑列出适配器; 服务起后 4 条静态路由 200 且 MIME 正确;
Edge headless 渲染主页与 motor 页, CSS 294 规则、9 节点样式生效, 零加载
失败零 JS 异常; bat 的 %~dp0 与 vbs 的 GetParentFolderName 实测均落在
项目根, src/ 下五个目标文件全部命中。
This commit is contained in:
cbb
2026-09-03 22:27:23 +08:00
parent beb33ffe50
commit 979da72181
19 changed files with 61 additions and 54 deletions
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env python
"""EtherCAT master bring-up test for the DCU LAN9252 slave (pysoem >= 1.1 API).
Usage:
ecat_scan.py list all Npcap adapters and exit
ecat_scan.py <name-substr> open the first adapter whose description matches
the substring (e.g. "I226"), scan the bus and
print slave identity + SII EEPROM dump
ecat_scan.py <substr> --op additionally request OP and run PDO cycles for 2s
"""
import sys
import time
try:
import pysoem
except ImportError:
print("pysoem 无法加载:请先安装 Npcap(本目录下的 npcap-1.88.exe,"
"安装时保持勾选 'Install WinPcap API-compatible Mode')")
sys.exit(1)
AL_STATE = {1: "INIT", 2: "PREOP", 4: "SAFEOP", 8: "OP"}
def al_state_name(code):
return AL_STATE.get(code, f"0x{code:02x}")
def list_adapters():
for i, a in enumerate(pysoem.find_adapters()):
desc = a.desc.decode() if isinstance(a.desc, bytes) else a.desc
print(f"[{i}] {desc}\n {a.name}")
def find_adapter(substr):
for a in pysoem.find_adapters():
desc = a.desc.decode() if isinstance(a.desc, bytes) else a.desc
if substr.lower() in desc.lower():
return a
return None
def dump_eeprom(slave, count=0x10):
print(" SII EEPROM dump (每行 8 个 word 地址,原始 32bit 返回):")
for base in range(0, count, 8):
words = []
for w in range(base, base + 8):
try:
v = bytes(slave.eeprom_read(w))
words.append(v.hex().upper())
except Exception:
words.append("ERRRRRRRR")
print(f" 0x{base:02X}: {' '.join(words)}")
def main():
if len(sys.argv) < 2 or sys.argv[1] in ("-l", "--list"):
list_adapters()
return
substr = sys.argv[1]
want_op = "--op" in sys.argv[2:]
adapter = find_adapter(substr)
if adapter is None:
print(f"没有找到描述包含 {substr!r} 的适配器,当前列表:")
list_adapters()
sys.exit(1)
desc = adapter.desc.decode() if isinstance(adapter.desc, bytes) else adapter.desc
master = pysoem.Master()
master.open(adapter.name)
try:
n = master.config_init()
except Exception as e:
print(f"总线上没有发现从站({type(e).__name__}: {e}):"
"请检查 DCU 板子是否上电、网线是否接到 I226-V 网口")
sys.exit(2)
if not n:
print("总线上没有发现从站:请检查 DCU 板子是否上电、网线是否接到 I226-V 网口")
sys.exit(2)
print(f"\n适配器: {desc}")
print(f"发现 {n} 个从站:")
for i, s in enumerate(master.slaves):
print(f" [{i}] name={s.name!r}")
print(f" VendorID=0x{s.man:08X} ProductCode=0x{s.id:08X} Revision={s.rev}")
print(f" AL state={al_state_name(s.state)} AL status code=0x{s.al_status:04X}"
f" DC={s.dc_sync}")
dump_eeprom(master.slaves[0])
if not want_op:
return
master.config_map()
if master.state_check(pysoem.SAFEOP_STATE, 2000) != pysoem.SAFEOP_STATE:
print(f"进入 SAFEOP 失败, 当前 AL 状态: {al_state_name(master.state)}")
sys.exit(3)
t_end = time.time() + 2.0
frames = 0
while time.time() < t_end:
master.send_processdata()
master.receive_processdata(2000)
frames += 1
time.sleep(0.001)
master.state = pysoem.OP_STATE
master.write_state()
if master.state_check(pysoem.OP_STATE, 2000) == pysoem.OP_STATE:
print(f"已进入 OP, PDO 循环 {frames} 帧正常")
else:
print(f"进入 OP 失败, 当前 AL 状态: {al_state_name(master.state)}")
if __name__ == "__main__":
main()