Files
planet/docs/ue-ndisplay-websocket-protocol.md
2026-05-09 15:51:46 +08:00

6.7 KiB
Raw Permalink Blame History

UE nDisplay WebSocket 协议草案

目的

本协议定义后端到 UE nDisplay 集群运行时之间的场景同步契约。

设计目标:

  • 由 UE 主节点单点接入后端
  • 使用全量快照重建完整场景
  • 使用增量更新降低同步成本
  • 支持控制帧和显示配置
  • 支持序列号、重放和重同步

适用范围

该协议建立在通用 WebSocket 协议之上,参考 websocket_protocol.md

新增的核心内容是 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

服务端 -> 客户端

{
  "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

客户端 -> 服务端

{
  "type": "subscribe",
  "timestamp": "2026-04-17T02:30:01.000Z",
  "data": {
    "channels": ["ue_scene", "alerts"]
  }
}

sync_request

客户端 -> 服务端

{
  "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 全量快照

服务端 -> 客户端

{
  "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 增量更新

服务端 -> 客户端

{
  "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

客户端 -> 服务端

{
  "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

服务端 -> 客户端

{
  "type": "control_acknowledged",
  "timestamp": "2026-04-17T02:32:00.050Z",
  "data": {
    "target": "ue_scene",
    "command": "set_camera_mode",
    "accepted": true
  }
}

heartbeat

双向

{
  "type": "heartbeat",
  "timestamp": "2026-04-17T02:32:30.000Z",
  "data": {
    "action": "ping"
  }
}

服务端返回:

{
  "type": "heartbeat",
  "timestamp": "2026-04-17T02:32:30.020Z",
  "data": {
    "action": "pong"
  }
}

场景实体结构

每个场景实体建议包含:

  • id
  • entity_type
  • revision
  • geo
  • visual
  • metrics
  • labels
  • status

示例:

{
  "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 事件并入统一场景层
  • 为控制命令增加真正的状态持久化与回执
  • 为主节点断线重连补状态恢复策略