Files
vd_test_fixture/.hermes/plans/2026-05-28_edc-web-implementation.md
wangfq 70dd3f8246 feat: 新增 edc-web Flask 前端管理系统 + 需求文档
- edc-web: Flask 项目骨架(设备管理、测试操作、测试信息三大页面)
- edc_server: 升级子模块(tb_serialnet 透传支持)
- docs: 测试工装EDC管理系统需求文档
2026-05-28 09:40:45 +08:00

250 lines
9.8 KiB
Markdown
Raw 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.
# edc-web 实施计划
## 目标
`vd_test_fixture` 项目中新增 `edc-web` Flask 前端 + 扩展 `edc_server` 后端,实现测试工装 EDC 的 Web 管理系统。
## 架构概览
```
┌──────────────────────┐ ┌──────────────────────┐
│ edc-web (Flask) │ │ edc_server (asyncio) │
│ 前端 Web 界面 │ │ 后端通信服务 │
│ REST API │ │ UDP/TCP + 轮询 │
└──────┬───────────────┘ └──────┬───────────────┘
│ │
└────────┬───────────────────┘
┌──────▼──────┐
│ MySQL │
│ edc 数据库 │
└─────────────┘
```
## 一、数据库变更
### 1.1 新增表 `tb_serialnet`(透传发送表)
```sql
CREATE TABLE IF NOT EXISTS `tb_serialnet` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`dnt_id` INT NOT NULL COMMENT 'FK → dnt_info.id',
`send_pkg` VARCHAR(380) DEFAULT '' COMMENT '发送指令包(hex)',
`rcv_pkg` VARCHAR(380) DEFAULT '' COMMENT '接收指令包(hex)',
`state` TINYINT DEFAULT 0 COMMENT '0未发送, 1已发送, 2已完成, 3超时失败',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_dnt_state` (`dnt_id`, `state`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
**修改文件**: `edc_server/src/models.py` — 在 `_create_tables()` 中添加建表语句
### 1.2 新增 tb_serialnet CRUD 函数
`models.py` 中添加:
| 函数 | 用途 |
|------|------|
| `get_pending_serialnet(dnt_id)` | 获取该设备 state=0 的第一个待发送记录 |
| `mark_serialnet_sent(id)` | 标记为 state=1 (已发送) |
| `mark_serialnet_done(id, rcv_pkg)` | 标记为 state=2 (收到回复) |
| `mark_serialnet_timeout(id)` | 标记为 state=3 (超时失败) |
| `get_serialnet_stats(dnt_id)` | 返回 total/sent/done/failed 计数 |
| `insert_serialnet(dnt_id, send_pkg)` | 插入新指令 |
## 二、edc_server 扩展
### 2.1 新增 `serialnet_loop()` 轮询任务
`handlers.py` 中新增:
```python
async def serialnet_loop():
"""轮询 tb_serialnet下发待发送的透传指令"""
while True:
for device_id, dnt_id in list(_registry.items()):
record = await get_pending_serialnet(dnt_id)
if record:
# 构造 SerialNet JSON → 通过 UDP 发送
# 更新 state=1
...
await asyncio.sleep(0.2)
```
**关键点**
- 同一设备同一时间只处理一条(避免冲突)
- 需要在 UDP transport 上发送数据 — 这要求 server.py 把 transport 暴露出来
### 2.2 server.py 改造
-`EDCProtocol` 的 transport 保存到全局/模块级变量,供 `serialnet_loop` 使用
- TCP handler 也需要能发送 SerialNet → 需要保存 TCP writer 映射 `{device_id: writer}`
### 2.3 B2 响应匹配 tb_serialnet
`tsreport_handler` 收到 B2 数据包后,除了写入 `tb_state_tst`,还要:
- 查找该设备 state=1 的 tb_serialnet 记录
- 更新为 state=2写入 rcv_pkg
### 2.4 超时检测
`serialnet_loop` 中检查 state=1 且超过 10 秒的记录 → 标记为 state=3
## 三、edc-web Flask 应用
### 3.1 目录结构
```
vd_test_fixture/edc-web/
├── run.py # 入口
├── requirements.txt # Flask, pymysql, ...
├── app/
│ ├── __init__.py # Flask 工厂
│ ├── config.py # 配置
│ ├── models.py # 数据库操作(同步 pymysql
│ ├── routes/
│ │ ├── __init__.py
│ │ ├── devices.py # 设备页面 API
│ │ ├── test_op.py # 测试操作 API
│ │ └── test_data.py # 测试信息 API
│ ├── templates/
│ │ ├── base.html # 基模板(菜单)
│ │ ├── devices.html # 设备列表页
│ │ ├── test_op.html # 测试操作页
│ │ └── test_data.html # 测试信息页
│ └── static/
│ ├── css/
│ │ └── style.css
│ └── js/
│ ├── devices.js
│ ├── test_op.js
│ └── test_data.js
```
### 3.2 页面路由
| 路由 | 页面 | 说明 |
|------|------|------|
| `/` | devices.html | 设备列表(默认首页) |
| `/test/<int:dnt_id>` | test_op.html | 测试操作页 |
| `/test-data` | test_data.html | 测试信息页 |
### 3.3 REST API
#### 设备相关
| Method | Path | 说明 |
|--------|------|------|
| GET | `/api/devices` | 获取 dnt_info 列表(含在线状态) |
| PUT | `/api/devices/<id>/name` | 修改终端名称 |
| GET | `/api/devices/<id>/status` | 获取设备最新测试状态 |
#### 测试操作
| Method | Path | 说明 |
|--------|------|------|
| POST | `/api/command` | 发送单次指令 {dnt_id, cmd} |
| POST | `/api/automation/start` | 开始自动化 {dnt_id, count} |
| POST | `/api/automation/stop` | 停止自动化 {dnt_id} |
| GET | `/api/automation/<dnt_id>/progress` | 获取进度 {total, done, failed, remaining} |
| GET | `/api/automation/<dnt_id>/averages` | 获取平均值 |
#### 测试信息
| Method | Path | 说明 |
|--------|------|------|
| GET | `/api/test-data` | 分页查询测试数据(join dnt_info + tb_state_tst) |
| GET | `/api/test-data/export` | 导出 CSV |
### 3.4 前端实现要点
#### 设备页面 (`devices.html`)
- 表格列: 设备编码(serial)、名称(name,可编辑)、IP、在线状态(state)、版本(version)、操作(测试按钮)
- 名称编辑: 点击名称单元格 → 变为 input → 回车/blur 提交 PUT
- 在线状态: state=1 绿色"在线", state=0 灰色"离线"
- 操作列: "测试" 按钮 → 跳转 `/test/<dnt_id>`
#### 测试操作页 (`test_op.html`)
- **左侧操作区**:
- 指令按钮: 开始测试(B0)、测试复原(B1)、电机前进(BA)、电机后退(BB)、电机停止(BC)
- 点击 → POST /api/command → edc_server 通过 SerialNet 发送
- 自动化区域:
- 测试次数 input + 开始/结束按钮
- 进度条(完成/总数 + 失败数)
- 实现: 前端轮询 /api/automation/<id>/progress (1s间隔)
- 开始后: 前端每次 state=2 时自动插入下一条 0xB0
- 超时: 前端 JS 计时器10秒后 state 仍是 1 → 标记为失败
- **右侧信息区**:
- 最近一次测试数据显示(实时刷新)
- 平均值显示: 峰峰值、开始工作频率、进入工作频率、进入距离、离开距离、进入速度、离开速度
- 平均值只计算成功记录(不计失败的 state=3
#### 测试信息页 (`test_data.html`)
- 表格: JOIN dnt_info.serial + tb_state_tst 全部字段
- 分页: 后端 LIMIT/OFFSET
- 搜索: 按 serial、日期范围筛选
- 导出: CSV 下载
## 四、自动化流程详解
```
前端点击"开始"(次数=N)
├─ 清空前端平均值缓存
├─ POST /api/automation/start (dnt_id, count=N)
│ └─ 后端插入第1条 tb_serialnet (state=0, send_pkg=0xB0)
├─ 前端开始轮询 (1s间隔)
│ │
│ ├─ GET /api/automation/<id>/progress
│ │ └─ 返回 {total:N, done:M, failed:F, remaining:R}
│ │
│ └─ 前端逻辑:
│ - 如果 state=2 (完成) → done++ → 如果 done < N: 插入下一条 0xB0
│ - 如果 state=1 超过10秒 → 标记 timeout → failed++
│ - 更新进度条
│ - 如果 done + failed >= N 或 用户点击"结束" → 停止
└─ edc_server 后台:
├─ serialnet_loop: state=0 → 发送 UDP SerialNet → state=1
└─ tsreport_handler: 收到 B2 → 写入 tb_state_tst → 匹配 state=1 → state=2
```
## 五、DG430 指令构造
所有指令基于 DG430 串口协议,通过 SerialNet 透传:
| 按钮 | 命令 | hex 数据包 (addr=0x01) | 备注 |
|------|------|------------------------|------|
| 开始测试 | 0xB0 | `7F 81 01 B0 30 32` | |
| 测试复原 | 0xB1 | `7F 81 01 B1 31 33` | |
| 电机前进 | 0xBA | `7F 81 01 BA 3A 3C` | |
| 电机后退 | 0xBB | `7F 81 01 BB 3B 3D` | |
| 电机停止 | 0xBC | `7F 81 01 BC 3C 3E` | |
addr=0x01 → ADDR字段=0x80+0x01=0x81
XOR: 0x81 ^ 0x01 ^ 0xB0 = 0x30, SUM: (0x81+0x01+0xB0) & 0xFF = 0x32
## 六、实施步骤
| 步骤 | 内容 | 文件 |
|------|------|------|
| 1 | 添加 tb_serialnet 表 + CRUD 函数 | `edc_server/src/models.py` |
| 2 | 新增 serialnet_loop + 改造 server.py (暴露 transport) | `edc_server/src/handlers.py`, `server.py` |
| 3 | B2 响应匹配 tb_serialnet | `edc_server/src/handlers.py` |
| 4 | 创建 edc-web 项目骨架 (Flask 工厂) | `edc-web/app/__init__.py` |
| 5 | 设备页面 API + 前端 | `edc-web/app/routes/devices.py`, `templates/devices.html` |
| 6 | 测试操作页 API + 前端 | `edc-web/app/routes/test_op.py`, `templates/test_op.html` |
| 7 | 测试信息页 API + 前端 | `edc-web/app/routes/test_data.py`, `templates/test_data.html` |
| 8 | 集成测试 | - |
## 七、注意事项
1. **防止冲突**: tb_serialnet 对同一设备同时只处理一条 (state=0/1 只有一条)
2. **超时处理**: 前端 JS 10 秒定时器 + 后端 serialnet_loop 也做超时兜底
3. **平均值计算**: 只计算 state=2 (成功) 的记录,排除 state=3 (失败)
4. **edc_server UDP 发送**: 目前 edc_server 只接收 UDP需要在 EDCProtocol 中保存 transport 引用
5. **edc-web 用同步 pymysql**: Flask 是同步框架,用 pymysql 直接连 MySQL与 edc_server 的 aiomysql 不冲突(同库不同连接)