新增 WMS 搬运规则与任务管理

支持搬运规则维护、候选预览、任务生成、预占、下发、取消和完成,并完善仓储管理前端交互。
This commit is contained in:
ArtoriasWu
2026-06-25 11:02:22 +08:00
parent 1cb8091bbe
commit 15405ba114
101 changed files with 4167704 additions and 152 deletions
@@ -0,0 +1,368 @@
# AGV GLB 接入教程
> 面向 MES、数字孪生大屏、调度平台等外部系统的集成指南。
> API 协议细节见 [AGV播放API.md](./AGV播放API.md)。
---
## 架构
```
┌─────────────────┐ HTTP POST/GET ┌──────────────┐ SSE 推送 ┌─────────────────┐
│ 你的业务系统 │ ──────────────────────▶│ serve.py │ ───────────────▶│ play.html │
│ (任意语言) │ /api/catalog │ :8765 │ /api/agv/events│ (3D 播放页) │
│ │ /api/agv │ │ │ │
└─────────────────┘ └──────────────┘ └─────────────────┘
```
| 组件 | 职责 |
|------|------|
| **业务系统** | 调用 HTTP 接口,无需了解 Three.js |
| **serve.py** | 协议校验、命令缓存与转发 |
| **play.html** | 3D 渲染;须保持打开才会执行动作 |
播放页未打开时,命令会**缓存**;打开 `play.html` 后自动同步执行。
---
## 快速开始
### 1. 启动服务
**Windows(推荐):** 双击项目根目录 `start.bat`
```powershell
# 或命令行
.\start.ps1
```
**手动启动:**
```bash
python serve.py
# 默认端口 8765;可通过环境变量 PORT 修改
```
首次部署且需使用 `analyze_agv.py` 扫描 GLB 时:
```powershell
.\start.ps1 -Setup
```
### 2. 打开播放页
服务启动后,在浏览器中保持以下页面打开:
```
http://127.0.0.1:8765/viewer/play.html
```
配置页(编辑动作,非播放必需):
```
http://127.0.0.1:8765/viewer/index.html
```
### 3. 验证连通
```bash
curl http://127.0.0.1:8765/api/catalog
```
返回 `ok: true``data.vehicles` 非空即表示服务正常。
### 4. 发送第一条播放命令
```bash
curl -X POST http://127.0.0.1:8765/api/agv \
-H "Content-Type: application/json" \
-d "{\"version\":1,\"method\":\"playAction\",\"params\":{\"modelId\":\"英招\",\"actionId\":\"zuozhuan\"}}"
```
响应中 `data.subscribers >= 1` 表示播放页在线;为 `0` 时命令已缓存,打开播放页后会自动执行。
---
## 依赖说明
| 用途 | 要求 |
|------|------|
| 运行服务 + 播放 | Python 3.10+(标准库即可) |
| 前端 Three.js | CDN 加载,**无需 npm install** |
| GLB 扫描工具 | 可选,运行 `.\start.ps1 -Setup` 安装 |
---
## 接入方式
### 方式 AHTTP API(推荐)
业务系统直接请求 `serve.py` 提供的 REST 接口,适合 Python、Java、C#、Node.js 等任意语言。
### 方式 B:iframe 嵌入(适合前端大屏)
`play.html` 嵌入 iframe,通过 `postMessage``AgvPlaybackApi` 控制,协议字段与 HTTP 一致。
---
## 标准流程
```
① 启动 serve.py
② 打开 play.html(可全屏或 iframe 嵌入大屏)
③ GET /api/catalog → 获取 modelId、actionId
④ POST /api/agv → playAction / stopAction / clearActions
⑤ 检查 data.subscribers >= 1
```
---
## HTTP API
**Base URL** `http://127.0.0.1:8765`
**协议版本:** `version: 1`
**Content-Type** `application/json`
### GET /api/catalog — 查询目录
接入前先调用,获取可用的 `modelId``actionId`
```
GET http://127.0.0.1:8765/api/catalog
```
响应示例:
```json
{
"version": 1,
"ok": true,
"data": {
"vehicles": [
{
"modelId": "英招",
"name": "英招",
"actions": [
{ "id": "zuozhuan", "label": "左转" }
]
}
]
},
"error": null
}
```
### POST /api/agv — 控制播放
| method | 说明 | params |
|--------|------|--------|
| `playAction` | 播放动作,自动切换车辆 | `modelId` + `actionId` 必填 |
| `stopAction` | 停止指定动作 | `modelId` + `actionId` 必填 |
| `clearActions` | 停止该车全部动作 | 仅 `modelId` 必填 |
**playAction 示例:**
```json
{
"version": 1,
"method": "playAction",
"params": {
"modelId": "英招",
"actionId": "zuozhuan"
}
}
```
**成功响应:**
```json
{
"version": 1,
"ok": true,
"data": {
"dispatched": true,
"subscribers": 1,
"modelId": "英招",
"activeActions": ["zuozhuan"]
},
"error": null
}
```
**失败响应:**
```json
{
"version": 1,
"ok": false,
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "params.modelId 必填"
}
}
```
| 响应字段 | 含义 |
|----------|------|
| `subscribers` | 当前连接的播放页数量;`0` = 无播放页,命令已缓存 |
| `activeActions` | 当前正在播放的动作 ID 列表 |
| `dispatched` | 命令是否已成功写入总线 |
更多请求/响应示例见 [AGV播放API.md](./AGV播放API.md)。
---
## 代码示例
### Python
```python
import requests
BASE = "http://127.0.0.1:8765"
def get_catalog():
r = requests.get(f"{BASE}/api/catalog", timeout=5)
r.raise_for_status()
return r.json()["data"]["vehicles"]
def play_action(model_id: str, action_id: str):
payload = {
"version": 1,
"method": "playAction",
"params": {"modelId": model_id, "actionId": action_id},
}
r = requests.post(f"{BASE}/api/agv", json=payload, timeout=5)
r.raise_for_status()
data = r.json()
if not data["ok"]:
raise RuntimeError(data["error"]["message"])
if data["data"]["subscribers"] == 0:
print("警告: 播放页未打开,命令已缓存")
return data
vehicles = get_catalog()
vehicle = vehicles[0]
action_id = vehicle["actions"][0]["id"]
play_action(vehicle["modelId"], action_id)
```
### JavaScript
```javascript
const BASE = "http://127.0.0.1:8765";
async function getCatalog() {
const res = await fetch(`${BASE}/api/catalog`);
const json = await res.json();
return json.data.vehicles;
}
async function playAction(modelId, actionId) {
const res = await fetch(`${BASE}/api/agv`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
version: 1,
method: "playAction",
params: { modelId, actionId },
}),
});
const json = await res.json();
if (!json.ok) throw new Error(json.error.message);
return json;
}
```
### iframe 嵌入
```html
<iframe
id="agv-player"
src="http://127.0.0.1:8765/viewer/play.html"
style="width:100%;height:100%;border:none"
></iframe>
<script>
const iframe = document.getElementById("agv-player");
// postMessage(协议与 HTTP 一致)
iframe.contentWindow.postMessage(
{
version: 1,
id: "req-1",
method: "playAction",
params: { modelId: "英招", actionId: "zuozhuan" },
},
"*"
);
window.addEventListener("message", (e) => {
if (e.data?.id === "req-1") console.log("播放结果:", e.data);
});
// 同源时可直接调用 AgvPlaybackApi
iframe.onload = async () => {
const api = iframe.contentWindow.AgvPlaybackApi;
await api.request({
version: 1,
method: "playAction",
params: { modelId: "英招", actionId: "zuozhuan" },
});
};
</script>
```
---
## 业务映射建议
| 业务事件 | API 调用 |
|----------|----------|
| AGV 开始某动作 | `playAction` + 对应 `actionId` |
| AGV 停止当前动作 | `stopAction` + 对应 `actionId` |
| AGV 复位 / 全部停止 | `clearActions` |
| 切换 AGV 型号 | `playAction` 带新的 `modelId`(自动切车) |
建议启动时拉一次 catalog,将业务编码(如 `"TURN_LEFT"`)映射到 `actionId`,避免硬编码。
---
## 生产部署
1. **播放页常开**:大屏或工控机全屏打开 `play.html`,或用 iframe 嵌入业务页面
2. **单实例**:同一端口只运行一个 `serve.py`
3. **自定义端口**`PORT=9000 python serve.py``.\start.ps1 -Port 9000`
4. **健康检查**:定期 `GET /api/catalog` 确认服务存活
5. **新增车辆**:将 GLB + JSON 放入 `agv/`,更新 `agv/manifest.json`,重启服务后 catalog 自动更新
---
## 常见问题
| 现象 | 处理 |
|------|------|
| `subscribers: 0` | 先打开 `play.html` |
| 页面打不开 | 只保留一个 `serve.py` 进程 |
| Postman 卡住 | 重启 `serve.py` |
| `params.modelId 必填` | 三个 method 都要带 `modelId` |
| `UNKNOWN_METHOD` | POST 只能用 3 个 method;查目录用 GET |
| `动作不存在` | 先调 catalog 核对 `actionId` |
---
## 相关文档
| 文档 | 说明 |
|------|------|
| [AGV播放API.md](./AGV播放API.md) | HTTP 协议与请求/响应格式 |
| [AGV_GLB导出规范.md](./AGV_GLB导出规范.md) | GLB 模型导出与配置规范 |
---
*数字孪生开发组*