feat: add ue_scene websocket integration

This commit is contained in:
opencode
2026-05-09 11:32:07 +08:00
parent 61baee00f6
commit 8f67d8d108
12 changed files with 2684 additions and 49 deletions

View File

@@ -0,0 +1,352 @@
# UE nDisplay WebSocket 协议草案
## 目的
本协议定义后端到 UE `nDisplay` 集群运行时之间的场景同步契约。
设计目标:
- 由 UE 主节点单点接入后端
- 使用全量快照重建完整场景
- 使用增量更新降低同步成本
- 支持控制帧和显示配置
- 支持序列号、重放和重同步
## 适用范围
该协议建立在通用 WebSocket 协议之上,参考 [websocket_protocol.md](/D:/work/planet/planet/websocket_protocol.md:1)。
新增的核心内容是 `ue_scene` 频道。
## 连接模型
### 客户端
- UE 主节点连接 `ws://<backend-host>:8000/ws?token=<access_token>`
- UE 渲染节点不直接连接后端场景通道
### 订阅
UE 主节点建议订阅:
- `ue_scene`
- `alerts`
- `dashboard`
## 协议规则
- 每次场景更新都带单调递增的 `sequence`
- 增量更新必须包含 `base_sequence`
- UE 如果发现序列断裂,应先发 `sync_request`
- 服务端优先返回可重放的增量历史;无法重放时返回全量快照
- 后端始终发送地理坐标UE 本地负责转成球体世界坐标
- 控制消息与场景消息分离
## 消息类型
### `connection_established`
服务端 -> 客户端
```json
{
"type": "connection_established",
"timestamp": "2026-04-17T02:30:00.000Z",
"data": {
"connection_id": "conn_primary_node_01",
"server_version": "0.19.0",
"heartbeat_interval": 30,
"supported_channels": [
"ue_scene",
"alerts",
"dashboard",
"datasource_tasks"
]
}
}
```
### `subscribe`
客户端 -> 服务端
```json
{
"type": "subscribe",
"timestamp": "2026-04-17T02:30:01.000Z",
"data": {
"channels": ["ue_scene", "alerts"]
}
}
```
### `sync_request`
客户端 -> 服务端
```json
{
"type": "sync_request",
"timestamp": "2026-04-17T02:30:05.000Z",
"data": {
"channel": "ue_scene",
"reason": "initial_connect",
"last_sequence": null
}
}
```
`reason` 常见值:
- `initial_connect`
- `sequence_gap`
- `manual_resync`
- `profile_changed`
### `data_frame` 全量快照
服务端 -> 客户端
```json
{
"type": "data_frame",
"channel": "ue_scene",
"timestamp": "2026-04-17T02:30:06.000Z",
"data": {
"update_type": "full",
"sequence": 12,
"cluster_time": "2026-04-17T02:30:06.000Z",
"display_profile": {
"profile_id": "polarized-wall-a",
"stereo_mode": "polarized",
"screen_width_m": 3.0,
"screen_height_m": 2.0,
"target_refresh_hz": 120
},
"camera_state": {
"mode": "auto_cruise",
"path_id": "global_overview",
"fov": 42.0
},
"payload": {
"meta": {
"generated_at": "2026-04-17T02:30:06.000Z",
"total_records": 20800,
"state_hash": 123456789
},
"layers": {
"satellites": {
"revision": 111,
"items": []
},
"supercomputers": {
"revision": 222,
"items": []
},
"gpu_clusters": {
"revision": 333,
"items": []
},
"submarine_cables": {
"revision": 444,
"items": []
},
"landing_points": {
"revision": 555,
"items": []
},
"alerts": {
"revision": 0,
"items": []
}
}
}
}
}
```
### `data_frame` 增量更新
服务端 -> 客户端
```json
{
"type": "data_frame",
"channel": "ue_scene",
"timestamp": "2026-04-17T02:31:00.000Z",
"data": {
"update_type": "incremental",
"sequence": 13,
"base_sequence": 12,
"cluster_time": "2026-04-17T02:31:00.000Z",
"meta": {
"generated_at": "2026-04-17T02:31:00.000Z",
"total_records": 20805,
"state_hash": 123456790
},
"changes": {
"gpu_clusters": {
"added": [],
"updated": [],
"removed": []
},
"submarine_cables": {
"added": [],
"updated": [],
"removed": []
}
}
}
}
```
### `control_frame`
客户端 -> 服务端
```json
{
"type": "control_frame",
"timestamp": "2026-04-17T02:32:00.000Z",
"data": {
"target": "ue_scene",
"command": "set_camera_mode",
"arguments": {
"mode": "manual",
"camera_id": "operator_cam_1"
}
}
}
```
常见命令:
- `set_camera_mode`
- `set_camera_path`
- `focus_entity`
- `set_layer_visibility`
- `set_alert_filter`
- `set_display_profile`
- `pause_auto_cruise`
- `resume_auto_cruise`
### `control_acknowledged`
服务端 -> 客户端
```json
{
"type": "control_acknowledged",
"timestamp": "2026-04-17T02:32:00.050Z",
"data": {
"target": "ue_scene",
"command": "set_camera_mode",
"accepted": true
}
}
```
### `heartbeat`
双向
```json
{
"type": "heartbeat",
"timestamp": "2026-04-17T02:32:30.000Z",
"data": {
"action": "ping"
}
}
```
服务端返回:
```json
{
"type": "heartbeat",
"timestamp": "2026-04-17T02:32:30.020Z",
"data": {
"action": "pong"
}
}
```
## 场景实体结构
每个场景实体建议包含:
- `id`
- `entity_type`
- `revision`
- `geo`
- `visual`
- `metrics`
- `labels`
- `status`
示例:
```json
{
"id": "gpu_clusters:123",
"entity_type": "gpu_cluster",
"revision": 987654321,
"geo": {
"lat": 35.9327,
"lng": -84.3107,
"alt": 0.0
},
"visual": {
"style": "pulse_marker",
"size": 1.0,
"color": "#FF8C42"
},
"metrics": {
"name": "Frontier"
},
"labels": {
"title": "Frontier",
"subtitle": "Oak Ridge, US"
},
"status": {
"health": "normal",
"alert_level": "none"
}
}
```
## 同步策略
UE 主节点建议这样处理:
1. 保存本地最后一次成功应用的 `sequence`
2. 收到增量帧时检查 `base_sequence`
3.`base_sequence` 不等于本地序列,则立即发 `sync_request`
4. 如果服务端能重放缺失增量,则按顺序依次应用
5. 如果服务端无法重放,则接收并应用新的全量快照
6. 场景更新应在主节点原子提交,再交给集群同步显示
## 服务端重同步规则
服务端建议行为:
- `initial_connect`:直接返回全量快照
- `manual_resync`:直接返回全量快照
- `profile_changed`:直接返回全量快照
- `sequence_gap`:优先尝试从历史缓存中回放增量
- 如果历史缓存不完整:回退到全量快照
- 如果客户端已是最新序列:返回空变化增量或保持静默
## 集群职责约束
- 只有 UE 主节点接收后端场景同步消息
- 渲染节点不直接重放后端消息
- 服务端表达的是场景意图,而不是每个节点的底层渲染状态
## 推荐的后端扩展
-`ue_scene` 增加更多真实可视化层
- 将告警/BGP 事件并入统一场景层
- 为控制命令增加真正的状态持久化与回执
- 为主节点断线重连补状态恢复策略