新增 WMS 搬运规则与任务管理
支持搬运规则维护、候选预览、任务生成、预占、下发、取消和完成,并完善仓储管理前端交互。
This commit is contained in:
@@ -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` 安装 |
|
||||
|
||||
---
|
||||
|
||||
## 接入方式
|
||||
|
||||
### 方式 A:HTTP 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 模型导出与配置规范 |
|
||||
|
||||
---
|
||||
|
||||
*数字孪生开发组*
|
||||
Reference in New Issue
Block a user