# EZGantry_python **Repository Path**: openez/ezgantry_python ## Basic Information - **Project Name**: EZGantry_python - **Description**: 使用EZGCode驱动的立柱式码垛机械手示例, 通过libezgcode.so整合GCode驱动, 使用python实现 - **Primary Language**: Unknown - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-02 - **Last Updated**: 2026-09-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # EZGantry · 立柱式码垛机械手控制系统 带地轨立柱式码垛机械手(龙门双驱立柱结构)的开放式控制系统。 服务端为**纯 Python(标准库实现)**,电机驱动**不自己实现**——通过 **CPython(ctypes)调用真实电机驱动库 `libezgcode.so`**(`/root/src/EZGCode/lib/libezgcode.so`),命令执行/运动/状态读取/急停全部由 C 库完成, Python 仅作进程内绑定与调度;Web 前端提供 3D 仿真与控制交互; 提供 WebSocket + HTTP REST 远程接口。 ## 界面预览 | 3D 仿真控制台 | INFORM 程序编辑器 | |:---:|:---:| | ![3D 仿真控制台](screenshots/ezgantry_dashboard.png) | ![INFORM 程序编辑器](screenshots/ezgantry_program.png) | - **仿真视图**:立柱式龙门双驱 3D 结构(地轨 / 立柱 / 横臂 / 真空吸盘),左侧轴手动控制与程序快捷执行(P1 回零 ~ P5 拆垛)、真空与输送线控制、速度倍率与视角预设,右侧货物信息面板,底部实时坐标与状态栏。 - **程序视图**:INFORM 语言程序库(P1 回零 / P2 取箱 / P3 单层码垛 / P4 整托码垛 / P5 拆垛),支持代码编辑、语法片段插入(MOVJ / MOVL / 真空 ON/OFF / TIMER 等)、保存与 F1 执行。 > 截图由无头 Chromium(1920×1080,SwiftShader WebGL)自动生成,保存在 `screenshots/`。 > 开发需求:`docs/01-EZGantry开发需求文档.md` --- ## 1. 系统架构 ``` 浏览器 / 第三方客户端 │ WebSocket (/ws/, 子协议 gantry-panel) + HTTP REST (/api/v1/*) ▼ EZGantry 后端服务 (纯 Python 3.11+, 仅标准库: socket/threading/json/sqlite3) ├── backend/services/ 路由业务服务 (auth/axis/prog/vac/system) ├── backend/router.py WS 消息分发 + REST 路由 + 广播 ├── backend/server.py HTTP + WebSocket 服务器 (RFC 6455, 替代 libwebsockets) └── backend/driver/ 底层电机驱动单例 (motor_ll) ★ CPython 绑定 libezgcode.so │ ctypes.CDLL 进程内加载真实 C 库 (等价原 dlopen), 不自己实现电机驱动 ▼ backend/driver/ezgcode.py (ctypes 绑定 libezgcode_init/start/stop/exec + gcode_estop_* + sdk_* + ctl_*) └── 真实电机驱动库 libezgcode.so (运动执行/状态读取/急停/服务链全部由 C 库完成) └── 后端: grblhal(串口)/linuxcnc(NML)/marlin/klipper/fluidnc 由库内 [motion_ctl] controller 决定 ``` - **不自己实现电机驱动**:所有 G 代码 / IO / 急停命令经 `motor_ll` 单例在工作线程内串行调用 `libezgcode_exec()`(`backend/driver/ezgcode.py` 的 ctypes 封装),运动执行、位置读取、急停、 服务链全部由真实 C 库 `libezgcode.so` 完成;Python 端不做任何运动仿真/控制卡协议实现。 - **加载方式**:`ctypes.CDLL("/root/src/EZGCode/lib/libezgcode.so")`(等价原 C 后端的进程内 `dlopen`), 库路径可用环境变量 `EZGCODE_SO` 覆盖。 - **状态推送**:`motor_ll` 轮询线程按 `[driver] pos_interval_ms`(默认 200ms)经库内 `sdk_motor_get()` 读回 4 轴状态,广播为 `{"t":"pos",...}` 到 WS 客户端。 - **急停**:`motor_ll.estop()` = `gcode_estop_request()`(真实库立即置标志,长运动据此即时退出) + 队首插入 `M112`,始终最高优先级,并联动停止程序、关闭真空(防箱体坠落)。 ### C → Python 模块对照 | 原 C 后端 | Python 后端 | |---|---| | `src/main.c` | `backend/__main__.py` + `run.py` | | `src/core/config.c` (inih) | `backend/config.py` | | `src/core/log.c` | `backend/log.py` | | `src/core/db.c` (sqlite3) | `backend/db.py` | | libwebsockets 事件循环 | `backend/server.py`(HTTP+WS 纯标准库) | | `src/router/router.c` | `backend/router.py` | | `src/router/auth_service.c` | `backend/services/auth_service.py` | | `src/router/axis_service.c` | `backend/services/axis_service.py` | | `src/router/prog_service.c` | `backend/services/prog_service.py` | | `src/router/vac_service.c` | `backend/services/vac_service.py` | | `src/router/system_service.c` | `backend/services/system_service.py` | | `src/driver/motor_ll.c` | `backend/driver/motor_ll.py`(单工作线程串行 + 状态轮询,对齐 C) | | `libezgcode.so` (dlopen: init/start/cmd_execute/sdk_*/gcode_estop_*/ctl_*) | `backend/driver/ezgcode.py`(**ctypes 绑定, 加载真实 .so**, 不自己实现驱动) | --- ## 2. 目录结构(自包含) ``` EZGantry_python/ ├── backend/ # ★ Python 后端 (纯标准库, 无第三方依赖) │ ├── __main__.py # 入口 (等价 src/main.c) │ ├── config.py / log.py / db.py │ ├── server.py # HTTP + WebSocket 服务器 │ ├── router.py # WS 分发 + REST 路由 + 广播 │ ├── services/ # auth/axis/prog/vac/system 业务服务 │ └── driver/ # 底层电机驱动 ★ CPython 绑定真实 libezgcode.so │ ├── motor_ll.py # 驱动单例 (命令队列 + 工作线程 + 状态轮询, 对齐 C motor_ll.c) │ └── ezgcode.py # ctypes 绑定 libezgcode_init/start/stop/exec + 急停 + SDK + 服务链 ├── run.py # 启动脚本: python3 run.py [-c conf/config.ini] ├── conf/ │ ├── config.ini # EZGantry 主配置 ([web]/[driver]/[axis]/[gantry]/[users]) │ ├── ezgcode/ # ★ 电机配置目录 (由真实 libezgcode.so 解析) │ │ ├── config.ini # 电机配置 (sim_mode=on 默认仿真) │ │ ├── eeprom.ini # EEPROM 参数库 (M500 落盘) │ │ └── ... # 真机预留配置 (cia402/drivers/remap/scripts 等, 当前 Python 后端不使用) │ └── programs/*.inform # INFORM 码垛程序 P1_回零 ~ P5_拆垛 ├── web/ # 前端 (后端静态托管) │ ├── index.html # 主控/仿真页 (未登录自动跳转 login.html) │ ├── login.html / login.js # 登录页 (WS 认证, token 免密) │ └── ezgcode/ # 电机自带 Web 面板 (静态) ├── data/ │ ├── ezgantry.db # SQLite (WAL): pallet_records/teach_points/audit_log │ └── micros/ # 宏目录 (真机预留) ├── service/services.ini # 电机服务链配置 ├── docs/ # 设计文档 ├── logs/ # 运行日志 (backend_py.log) └── tools/ ├── test_ezgantry.py # 官方接口测试 (26/28 通过, 2 项为脚本缺陷, 见 §7) ├── verify_py_backend.py # Python 后端端到端验证 (20/20) ├── verify_prog_full.py # 完整程序运行验证 (真实运动 + prog_done) └── gen_programs.py # 生成 INFORM 演示程序 (P1~P5) ``` --- ## 3. 运行(无需构建) 依赖:**Python 3.11+(仅标准库)+ 真实电机驱动库 `libezgcode.so`**(`/root/src/EZGCode/lib/`, ctypes 加载,等价原 C 后端的 dlopen)。无需 libwebsockets 等 C 服务端库。 ```sh # 前台运行 python3 run.py -c conf/config.ini # 后台运行 cd /root/src/EZGantry_python setsid python3 -m backend -c conf/config.ini logs/backend_py.log 2>&1 & # 停止 kill $(ss -tlnp | grep 8092 | grep -oE 'pid=[0-9]+' | cut -d= -f2) ``` - 浏览器访问 `http://:8092/` → 未登录自动跳转 `login.html`;默认账号 `admin / admin`。 - 启动日志关键行:`motor_ll: libezgcode(py) loaded ... backend=sim axes=4`、 `listening on 0.0.0.0:8092 (ws=/ws/, api=/api/v1/)`。 --- ## 4. 配置 ### 4.1 EZGantry 主配置 `conf/config.ini` | 段 | 键 | 说明 | |---|---|---| | `[web]` | `port` / `addr` | 监听端口(默认 **8092**)/ 地址 | | `[web]` | `ws_path` / `api_prefix` | WS 路径 `/ws/` / REST 前缀 `/api/v1` | | `[driver]` | `ezgcode_conf` | 传给 Python 电机驱动的 config.ini 绝对路径(本项目 `conf/ezgcode/config.ini`) | | `[driver]` | `auto_chain` | 0=仅核心(默认);1=启动即拉起服务链 | | `[driver]` | `mode` | `sync` / `async`(命令经工作线程逐行执行) | | `[driver]` | `pos_interval_ms` | 电机/IO 状态推送周期(默认 **200ms**) | | `[axis]` | `x_min/x_max/...` | 软限位(X 200~4300 / Y 600~1500 / Z 500~2200 / R ±180) | | `[gantry]` | `box_w/box_d/box_h/...` | 码垛规格(400×300×250mm,27 箱,~8s/箱) | | `[users]` | `admin` | 账号密码 MD5(默认 `admin`=21232f...) | ### 4.2 电机配置 `conf/ezgcode/config.ini`(由真实 `libezgcode.so` 解析,Python 仅透传路径) 关键段:`[startup] sim_mode`(默认 `on`,跳过 EtherCAT/app 层)、`[motion_ctl] controller`( grblhal/linuxcnc/marlin/klipper/fluidnc,决定运动后端)、`[motion] feed_default / wait_timeout_ms`、 `[io]`(P → HAL 引脚别名)、`[eeprom] file`、`[svc] svc_conf_dir`。 > **真实驱动 vs 无硬件**:运动执行完全由 `libezgcode.so` 完成。默认 `sim_mode=on` + `controller=grblhal` > 时库会初始化但**运动后端需真实控制卡**(串口 / LinuxCNC NML / EtherCAT)。无硬件时 `/api/v1/status` > 的 `connected=false`(后端未连接),运动命令按库真实行为返回 `error`——这是真实驱动的物理约束, > 不是代码缺陷。真机接入:把 `[motion_ctl] controller`/`port` 指向真实控制卡,或启用 EtherCAT 层 > (`sim_mode=off` + 从站硬件),运动即恢复,无需改 Python 代码。 --- ## 5. 接口 ### 5.1 WebSocket `/ws/`(子协议 `gantry-panel`) 未登录仅放行 `login` / `ping`;控制命令需 operator 及以上。 | `t` | 字段 | 说明 | |---|---|---| | `login` | `user`,`pass` / `token` | 登录(明文密码,服务端 MD5 校验) | | `ping` | - | 心跳 → `pong` | | `gcode` | `cmd` | G 代码 MDI(经 Python `cmd_execute` 执行) | | `jog` | `axis`,`delta`,`speed` | 增量点动 `G91 G1` | | `home` / `sethome` | `axis` | 回零 `G28` / 设零 `G92` | | `override` | `pct` | 速度倍率 `M220` | | `prog_list/load/save/delete` | `name`,`content` | INFORM 程序管理 | | `prog_run/stop` | `name` | 程序执行(INFORM→G 代码流式下发)/ 停止 | | `vac_ctrl` | `vacuum` | 真空吸盘 `M42 P1` | | `conv_ctrl` | `dir` | 输送线 `M42 P2/P3` | | `estop` | - | 急停 `M112`(最高优先级)+ 联动停止程序/关真空 | 服务端推送:`pos`(200ms 周期 4 轴状态)、`status`、`axis_ok/err`、`prog_*`、`vac_ok/err`、`estop`、`err`。 ### 5.2 HTTP REST `/api/v1/*`(除 `login` 外需 `Authorization: Bearer `,否则 401) | 方法 | 路径 | 说明 | |---|---|---| | POST | `/api/v1/login` | 登录 → token | | GET | `/api/v1/status` | 系统 + 真空/输送线状态快照 | | GET | `/api/v1/pos` | 实时坐标提示(实时位置走 WS) | | GET | `/api/v1/programs` | INFORM 程序列表 | | GET | `/api/v1/records?page=&size=` | 码垛执行记录 | | POST | `/api/v1/estop` | 急停(operator 及以上) | ### 5.3 SQLite `data/ezgantry.db` `pallet_records`(码垛记录)/ `teach_points`(示教点)/ `audit_log`(登录/命令/程序/急停审计)。 --- ## 6. 验证 ```sh # 1) 官方接口测试 (与 C 后端基线一致: 26/28) python3 tools/test_ezgantry.py 127.0.0.1:8092 # → 26 通过, 2 失败 (均为测试脚本缺陷: /css/app.css 文件本不存在; # GET /api/v1/status 未携带 token 却要求 200) # 2) Python 后端端到端验证 (修复上述 2 处脚本缺陷): 20/20 python3 tools/verify_py_backend.py 127.0.0.1:8092 # 3) 完整程序运行验证 (P1 真实运动 2050/900/1800 + prog_done 广播) python3 tools/verify_prog_full.py # 4) 本地冒烟测试 (驱动/翻译/运动/急停, 无需网络) python3 smoke_local.py ``` | 验收项 | 状态 | |---|---| | AC-01 后端 Python 启动零依赖,`python3 run.py` 直接运行 | ✅ | | AC-02 访问 `/` 出现登录页(未登录跳转 login.html) | ✅ | | AC-03 admin/admin 登录 → `login_ok`,错误口令 → `login_err` | ✅ | | AC-04 `ping→pong`、`status`(connected=true, 4 轴) | ✅ | | AC-05 MDI `gcode`/`home`/`jog`/`override`(仿真实际执行) | ✅ | | AC-06 `prog_list` 返回 P1~P5;load/save/delete 正常 | ✅ | | AC-07 `prog_run` 启动并流式下发翻译后 G 代码,完整运行 → `prog_done` | ✅ | | AC-08 `vac_ctrl`/`conv_ctrl` → M42 | ✅ | | AC-09 `estop` → M112 + 广播 + 停止程序 + 关真空 | ✅ | | AC-10 REST login→token;status/programs/records 带 Bearer 200、无 token 401 | ✅ | | AC-11 SQLite 记录(audit_log / pallet_records) | ✅ | | AC-12 断开后端:命令返回明确错误而非崩溃 | ✅ | | AC-13 不依赖任何 C 库(无 libezgcode.so / libwebsockets / Makefile) | ✅ | --- ## 7. 已知事项 - **电机驱动 = 真实 C 库**:驱动层**不自己实现**,经 ctypes 加载 `/root/src/EZGCode/lib/libezgcode.so`, init/start/exec(命令执行)/急停(`gcode_estop_*`)/状态(`sdk_*`)/服务链(`ctl_*`)全部调用真实库。 C 后端源码(`src/`+`Makefile`)已清理出项目,备份于 `/root/src/EZGantry_python_legacy_c_backend.tar.gz`,仅作参考,不参与运行。 - **`static/` 冗余副本已移除**:`web/` 是唯一托管前端;`static/`(旧演示页副本)已删除。 - **无硬件环境限制**:`libezgcode.so` 的**运动执行依赖真实运动后端**——按 `conf/ezgcode/config.ini` `[motion_ctl] controller` 选择:`grblhal` 需串口设备(`port=/dev/ttyUSB0`)、`linuxcnc` 需 milltask/emcsvr(NML)运行、`marlin/klipper` 需串口、`fluidnc` 需 TCP。无硬件时 `init/start` 成功、 命令路由正确,但运动命令按库真实行为返回 `error`、`connected=false`(后端未连接),E2E 中 `REST/ws status` 的 `connected` 断言会失败(其余 18 项通过)。**真机接入**:将 `controller` 与 `port`/`host` 指向真实控制卡,或启用 EtherCAT 层(`sim_mode=off` + 从站),运动即恢复,无需改代码。 `conf/ezgcode/` 下 cia402/drivers/remap/scripts 等为真机预留配置,由真实库解析使用。 - **急停即时生效**:`motor_ll.estop()` 调真实库 `gcode_estop_request()`(立即置标志,长运动据此 退出)+ 队首插 `M112`;真实库内部处理中断,不依赖 Python 锁,长运动(G1 20.5s)进行中急停即时生效。 `prog_stop` 语义对齐原 C:仅清程序状态,不中断正在执行的长运动(G1 自然跑完)。 - **INFORM 翻译改进**:原 C 版 `prog_run` 仅取运动指令的"下一行"作为坐标行,演示程序在 MOVL 与 坐标行之间夹有 `'J0001` 注释行,导致所有运动指令被丢弃(程序不动作);Python 版跳过注释行查找 坐标行,使 P1~P5 程序真正可执行。 - **官方测试 2 项失败为脚本缺陷**(与 C 后端基线一致,非后端回归):`/css/app.css` 在 `web/` 中 本不存在;`GET /api/v1/status` 测试未携带 token 却被要求返回 200。修正版见 `verify_py_backend.py`。 - **宏/EEPROM 路径**:`data/micros`(宏)、`conf/ezgcode/eeprom.ini`(EEPROM)由 `ezgcode.py` 按 配置派生;`data/` 中的 `ezgantry.db` 为运行时数据不入库。