Appearance
多端遥测可视化:一套极简 WebSocket 协议
个人技术实验记录。目标是验证"实时数据能不能稳定驱动 3D 场景",不是设计一个工业协议。
场景里放几十栋建筑是静态的,真正要看的是动态数据在空间里的状态。我不想要一套复杂的工业协议,所以自己定了个只有三种消息的版本:遥测、指令、回执。
一、协议全文
传输:WebSocket,本地端口自定。所有消息单层 JSON,无嵌套。
服务端 → 客户端:遥测
每秒一条,连接建立时立即补发一条(避免客户端等一秒才有画面):
json
{
"type": "telemetry",
"ts": 1792309614,
"devices": [
{ "deviceId": "DEV-01", "temperature": 23.6, "power": true, "x": 9.1, "y": 10.6, "z": 9.0 },
{ "deviceId": "DEV-02", "temperature": 24.2, "power": false, "x": 25.0, "y": -31.6, "z": 9.0 }
]
}x / y / z 直接复用建模时的局部米制坐标,客户端不需要再做任何地理换算。z 给的是屋顶高度,标记物直接浮在建筑上方。
客户端 → 服务端:指令
json
{ "type": "command", "commandId": "CMD-0001", "deviceId": "DEV-01", "action": "power_on", "value": 0 }action 只留四个取值:power_on / power_off / temp_up / temp_down。
服务端 → 客户端:回执
json
{ "type": "ack", "commandId": "CMD-0001", "result": "ok" }二、几个刻意的设计决定
1. commandId 由客户端生成,服务端原样回传 这样客户端能把自己的请求和回执对上,不用维护"当前最新序号"这种全局状态。一开始我让服务端生成,结果并发点两次按钮就分不清哪个回执对应哪次操作。
2. 先 ack,再广播新遥测 顺序反了会出现"回执说成功,但画面还是旧状态",看起来像 bug。ack 只表示指令收到并已处理,遥测里的变化才是最终状态。
3. 设备首次出现就自动建 Actor,不做显式的 create 消息 少一种消息、少一个状态同步问题。代价是不能靠"消息消失"来表达设备移除,所以设备下线用 power: false 表达,而不是从数组里删掉。
4. 断线后固定 3 秒重连,重连不清空已有 Actor 3 秒是拍的:更短会在服务端真挂的时候把连接打爆,更长时画面卡太久。不清空 Actor 是为了避免"重连后场景闪一下全空",视觉上稳定很多。
5. 坐标单位换算写死在客户端 UE 侧是厘米,Web 侧是米。约定"协议里一律是米",各端自己乘,协议层不做单位协商——一旦允许协商,两端实现就会开始漂移。
三、这套东西不适合什么
说清楚边界,免得有人当真照抄:
- 不是可靠传输:没做序号、没做重传。遥测本身有下一帧覆盖,丢一条无所谓;但指令如果丢在网络上,客户端只会等不到 ack,不会知道原因。
- 没有鉴权:只在本地回环跑,纯实验。真要做远端,这层必须补。
- 没有历史:只有最新状态,不能回放。要做趋势得另加存储,不该塞进这个协议。
四、下一步
- 加一个
seq字段,让客户端能发现"遥测断流了但连接还在" - 指令超时后允许重发,配合幂等的
commandId - 设备数量上百之后,遥测按变化量发送(现在每秒全量,几十台还行)