# 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 ``` --- ## 业务映射建议 | 业务事件 | 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 模型导出与配置规范 | --- *数字孪生开发组*