跳至主要内容

WebSocket 协议

仿真通道是位于端口 10001上的 WebSocket 连接。它承载着您的应用程序与服务之间的实时命令/状态循环。

核心规则:每条消息对应一次响应​

服务会对客户端发来的每一条消息,恰好发送一次状态更新:

不存在轮询、订阅或带外推送。更新频率完全由您的发送速率决定。


第 1 步——初始清单(服务端发送的第一条消息)​

连接时,服务会向每个检测到的设备发送一份完整快照,其中包括 config、state 和 status:

{
"session_id": 7,
"inverse3": [
{
"device_id": "049D",
"config": {
"type": "inverse3",
"port": "COM12",
"device_info": { "major_version": 7, "minor_version": 4, "id": "049D", "device_type": 4, "uuid": "…" },
"extended_device_id": "…",
"extended_firmware_version": "…",
"gravity_compensation": { "enabled": true, "scaling_factor": 1.0 },
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": { "enabled": true },
"home_return": { "enabled": false },
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 } },
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": 0.0 }
}
},
"state": {
"cursor_position": { "x": 0.0, "y": -0.12, "z": 0.15 },
"cursor_velocity": { "x": 0.0, "y": 0.0, "z": 0.0 },
"angular_position": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"angular_velocity": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"body_orientation": { "w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0 },
"current_cursor_force": { "x": 0.0, "y": 0.0, "z": 0.0 },
"current_cursor_position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"current_angular_torques": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"current_angular_position": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"control_domain": "cartesian",
"control_mode": "idle",
"transform": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 } },
"transform_velocity": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 0, "y": 0, "z": 0 } }
},
"status": {
"calibrated": true,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [],
"wireless_verse_grip": []
}

这三个数据块的更新频率各不相同:

数据块包含内容变更频率
config固件信息、预设、基础、安装、滤波器很少——仅在明确修改配置时
state位置、速度、力、姿态、变换每个 tick(高频)
status就绪、已校准、电源、in_use偶尔(低频)

第 2 步——发送第一条命令​

解析初始清单以查找您的设备 ID,然后回传一条消息,其中包含您的会话配置、任何初始配置和/或控制命令:

{
"session": {
"configure": {
"profile": { "name": "co.haply.inverse.tutorials:hello-floor" }
}
},
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
},
"commands": {
"set_cursor_force": { "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
}
}
]
}

第 3 步——后续状态更新​

首次交互之后,除非配置变更触发了完整快照的推送,否则服务将仅发送 state + status(不含 config):

{
"session_id": 7,
"inverse3": [
{
"device_id": "049D",
"state": { "cursor_position": { … }, "cursor_velocity": { … }, … },
"status": { "ready": true, "calibrated": true, … }
}
]
}

第 4 步 — 重复​

发送命令 → 接收状态更新。循环将按照你的发送速率持续进行。


configure 与 commands​

在你发送的消息中,每个设备条目可以包含两个映射:

映射用途持久性
configure一次性设置:预设、基础设置、挂载、过滤器、模块配置保持有效,直至被修改
commands逐周期控制:力、位置、转矩仅使用一次,之后即失效

关于 configure 有关键的完整列表,请参阅 会话配置;关于 commands 条目,请参阅 控制命令。

备注
set_transform 这是一个特例

set_transform 位于 commands 下,但它是持久的——该服务会保留最后一个值,直到你发送新值为止。这是因为它的用途是场景导航:你在相机移动时会流式发送变换,但希望在两次按键之间保留最后的位置。


execute 标志​

任何 configure 或 commands 条目均可包含 "execute": false,使服务解析与校验加载但不应用。

{
"inverse3": [{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front", "execute": true },
"damping": { "scalar": 0.0, "execute": false }
}
}]
}

这对于基于反射的序列化器(例如 Unity 的 JsonUtility)非常有用,这类序列化器总会输出所有字段:将未使用的条目设为 execute: false,这样它们就不会覆盖实际配置。默认值为 true——省略该标志即表示“正常使用”。


警告​

如果你已经在发送命令,请不要再进行探测

probe_position 和 probe_orientation 这仅是用于监控的保活消息,适用于不发送控制命令的会话(例如Haply Hub)。如果你的会话已经在发送 set_cursor_force、set_cursor_position 等命令,请不要再同时发送探测消息——设备状态已包含在每条响应中。同时使用这两者不仅会浪费带宽,还可能触发 session-probe-dropped 事件。

未知键将被静默忽略

如果某条命令似乎没有任何效果,很可能是字段名有误。目前,服务会在不报错的情况下忽略无法识别的 JSON 键。请检查服务日志,并参照API 参考文档核实字段名。此行为计划在未来版本中进行更改。