8.7 KiB
8.7 KiB
AGV GLB 接入教程
面向 MES、数字孪生大屏、调度平台等外部系统的集成指南。
API 协议细节见 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
# 或命令行
.\start.ps1
手动启动:
python serve.py
# 默认端口 8765;可通过环境变量 PORT 修改
首次部署且需使用 analyze_agv.py 扫描 GLB 时:
.\start.ps1 -Setup
2. 打开播放页
服务启动后,在浏览器中保持以下页面打开:
http://127.0.0.1:8765/viewer/play.html
配置页(编辑动作,非播放必需):
http://127.0.0.1:8765/viewer/index.html
3. 验证连通
curl http://127.0.0.1:8765/api/catalog
返回 ok: true 且 data.vehicles 非空即表示服务正常。
4. 发送第一条播放命令
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
响应示例:
{
"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 示例:
{
"version": 1,
"method": "playAction",
"params": {
"modelId": "英招",
"actionId": "zuozhuan"
}
}
成功响应:
{
"version": 1,
"ok": true,
"data": {
"dispatched": true,
"subscribers": 1,
"modelId": "英招",
"activeActions": ["zuozhuan"]
},
"error": null
}
失败响应:
{
"version": 1,
"ok": false,
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "params.modelId 必填"
}
}
| 响应字段 | 含义 |
|---|---|
subscribers |
当前连接的播放页数量;0 = 无播放页,命令已缓存 |
activeActions |
当前正在播放的动作 ID 列表 |
dispatched |
命令是否已成功写入总线 |
更多请求/响应示例见 AGV播放API.md。
代码示例
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
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 嵌入
<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,避免硬编码。
生产部署
- 播放页常开:大屏或工控机全屏打开
play.html,或用 iframe 嵌入业务页面 - 单实例:同一端口只运行一个
serve.py - 自定义端口:
PORT=9000 python serve.py或.\start.ps1 -Port 9000 - 健康检查:定期
GET /api/catalog确认服务存活 - 新增车辆:将 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 | HTTP 协议与请求/响应格式 |
| AGV_GLB导出规范.md | GLB 模型导出与配置规范 |
数字孪生开发组