- edc-web: Flask 项目骨架(设备管理、测试操作、测试信息三大页面) - edc_server: 升级子模块(tb_serialnet 透传支持) - docs: 测试工装EDC管理系统需求文档
250 lines
9.8 KiB
Markdown
250 lines
9.8 KiB
Markdown
# 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 不冲突(同库不同连接)
|