模拟通道
概述
模拟通道是一个高频双向 WebSocket 通道,用于交换设备状态以及发送会话或设备命令。
默认网址: ws://localhost:10001
可以在配置中更改端口。
核心合同(重要):
- 当客户端连接时,服务端会发送一条初始设备清单消息,其中包含完整的设备列表。
- 之后,服务器会针对从客户端接收到的每条消息,发送且仅发送一条 状态更新消息。
- 每个状态更新都包含所有设备的状态(及状态信息)。
:::info 模块 本页面记录了核心的模拟通道协议和命令。
其他模块/功能可以通过注册来扩展系统,并添加自己的命令和/或状态字段。相关内容在“模块”章节中有单独说明。 :::
议事规则
每个客户端消息对应一个响应
该服务会针对从客户端接收到的每条消息,发送一条包含所有设备状态的“状态更新”消息。
这意味着:
- 如果您需要获取设备状态的最新快照,必须发送一条指令(会话命令、探测命令或设备命令)。
- 该通道的行为类似于一个由客户端消息驱动的“tick”循环。
不施加力的轮询(探测命令)
如果您想在不施加力或更改模拟参数的情况下观察状态变化,请使用探测命令:
probe_position用于inverse3probe_orientation适用于Verse握把
探测命令不包含命令数据,仅强制执行设备信息查询,以便在下一次状态更新时,位置/姿态数据保持最新。
配置与状态
- "(《世界人权宣言》) 初始库存 消息包含设备
config,state和status. - 常规 各州最新动态 消息包含设备
state和status.
如果您需要再次创建包含配置的快照,请使用 session.force_render_full_state.
坐标系
Haply 默认Haply Z轴向上(Z-Up)的右手坐标系。
有两个会话级别的控件会影响坐标的解析和返回方式:
- session.set_coordinate_origin:更改所有设备使用的坐标原点。
- session.set_basis:更改Haply 应用程序坐标系之间的映射关系。
消息格式
本节介绍了封套和消息类型的高级概述。完整的示例将在本文档后文提供。
设备组
消息在顶层按设备类型分组(例如: inverse3, verse_grip, wireless_verse_grip). 每个设备类型键都映射到一个包含设备级对象的数组。
初始库存(服务器 → 客户端)
在 WebSocket 连接建立后立即发送一次。
每条条目包含:
device_idconfigstatestatus
参见:示例:初始库存有效载荷
状态更新(服务器 → 客户端)
每收到一条客户端消息就发送一次。
每条条目包含:
device_idstatestatus
参见:示例:状态更新负载
会话命令包(客户端 → 服务器)
会话命令是适用于当前连接/会话的操作,不针对特定设备。
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
设备命令包(客户端 → 服务器)
设备命令以数组形式发送到设备类型键下,从而允许在一条消息中向一个或多个设备发送命令。
{
"<device_type>": [
{
"device_id": "<id>",
"commands": {
"<command_name>": {
"...": "..."
}
}
}
]
}
您可以在一条消息中包含多个条目,以控制多台同类型的设备。
请注意: commands 这是一个字典,可以为给定的设备处理多个命令,但每种命令类型仅限一个。
命令参考
会话
强制渲染完整状态
请求获取所有设备状态和配置的快照。
{
"session": {
"force_render_full_state": {}
}
}
设置坐标原点
为所有设备设置坐标原点。
{
"session": {
"set_coordinate_origin": {
"coordinate_origin": "workspace_center"
}
}
}
支持的值:
device_base(默认)workspace_center
如果你将原点设置为 workspace_center,该 (0, 0, 0) 位置将移动到设备工作区的中心,具体位置因设备类型而异。
设定基准
设置当前会话的基础映射。 基础映射用于指导整个会话期间从Haply坐标系到您坐标系的转换。
在奠定基础之后:
- 系统返回的所有状态均以该基数表示。
- 作为其他命令一部分发送的所有值均以此为基准进行解释。
该映射是相对于Haply 定义的,并表示为 X, Y, Z,可选地在前面加上 + 或 -.
有效值的示例:
XYZ,ZYX+Y-Z+X,X-ZY
解释示例:
YZX意味着Y说得对,Z是前锋,X已上线。
类似《虚幻》的左键Z轴上移系统的示例(X-YZ):
{
"session": {
"set_basis": {
"basis": {
"permutation": "X-YZ"
}
}
}
}
所有设备命令
检测(所有设备)
使用探测命令来获取最新的位置/姿态信息,而无需施加力或其他模拟更改。
- inverse3:
probe_position - Verse 握把 (
verse_grip,wireless_verse_grip):probe_orientation
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"probe_position": {}
}
}
],
"verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
],
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
]
}
Inverse3
要向一个 inverse3 设备,包含一个匹配的条目 device_id 在……之下 inverse3 键。
控制一台设备:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
通过一条消息控制多台设备:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
},
{
"device_id": "049E",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
设置光标位置
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
设置光标力
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
设置角度位置
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"values": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
设置角扭矩
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torque": {
"values": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
扩展诗句手柄
"(《世界人权宣言》) set_extension_data 该命令是 Verse 握把扩展协议的一部分,适用于实现了板扩展通信协议的握把版本。
设置握把伸展数据
支持的数据长度:
- 上行数据量最多为 20 字节(客户端 → 设备)。
- 下行数据最多 12 字节(设备 → 客户端),在状态更新消息中以以下形式返回:
state.extension_data.
数据规范:
- 数组长度:20 字节
- 数值范围:每个数值为0–255
{
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"set_extension_data": {
"extension_data": [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
}
}
}
]
}
示例
初始库存有效载荷
当 WebSocket 连接成功时,该服务会发送一条包含完整设备列表的消息。初始消息包含以下内容: JSON 格式:
{
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"device_info": {
"minor_version": 1,
"major_version": 7,
"id": "04BA",
"model": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"port": "COM13",
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 1
},
"handedness": "right",
"torque_scaling": {
"enabled": true
}
},
"state": {
"angular_position": {
"a0": -69.31704,
"a1": 137.62952,
"a2": 19.832787
},
"angular_velocity": {
"a0": 0,
"a1": 0,
"a2": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"mode": "idle"
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"config": {
"port": "COM3",
"type": "verse_grip"
},
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"config": {
"port": "COM6",
"type": "wireless_verse_grip",
"major_version": 1,
"minor_version": 4,
"hardware_version": 1
},
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
]
}
更新状态有效载荷
该服务将针对收到的每条消息发送一条状态更新消息,其中包含所有设备的状态。
如果您想了解机器的状态,必须事先向其发送一条消息(例如探测命令或力值,即使这些值为零)。当将设备用作输入源(例如位置跟踪)而未施加外力时,这一点尤为重要。
状态更新消息包含以下内容: JSON 格式:
{
"inverse3": [
{
"device_id": "04BA",
"state": {
"angular_position": {
"a0": -69.31704,
"a1": 137.62952,
"a2": 19.832787
},
"angular_velocity": {
"a0": 0,
"a1": 0,
"a2": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"mode": "idle"
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
}
},
"status": {
"ready": true
}
}
],
"custom_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"extension_data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
"status": {
"ready": true
}
}
]
}