跳转至主要内容

模拟频道

概述​

模拟通道是一个高频双向 WebSocket 通道,用于交换设备状态以及发送会话或 设备命令。

标准网址: ws://localhost:10001

该端口可在配置中进行修改。

基本行为规范(重要):

  • 一旦客户端建立连接,该服务就会发送一条包含初始库存信息的消息,其中列出了完整的设备列表。
  • 随后,服务器会针对客户端接收到的每条消息,发送exactly one 状态更新消息。
  • 每次状态更新都包含所有设备的状态(及运行状况)。
模块

本页介绍了模拟通道的核心协议及其命令。

附加模块或功能可向系统扩展并注册自定义命令和/或状态字段。相关内容在“模块”一节中另有说明。

议事规则​

每条客户端消息仅允许一个回复​

该服务会针对客户端接收到的每条消息,发送一条包含所有设备状态的状态更新消息。

这意味着:

  • 如果您需要获取设备状态的最新快照,则必须发送一条命令(会话命令、查询命令或设备命令)。
  • 该通道的行为类似于一个由客户端消息驱动的“Tick”循环。

不施加力的轮询(查询指令)​

如果您希望在不施加外力或更改模拟参数的情况下观察状态变化,请使用查询命令:

  • probe_position 用于 inverse3
  • probe_orientation 适用于Verse Grips

查询命令不包含命令数据,仅用于查询设备信息,以便在下次状态更新消息中,位置和姿态数据保持最新。

可能不需要查询命令

已经开始发送控制命令的模拟会话(set_cursor_force, set_cursor_position 等), müssen keine 发送查询命令——响应帧中始终包含设备状态。查询命令用于 纯粹的监督会议 gedacht(例如Haply Hub),这些设备需要查询设备状态,但无需控制该设备。

配置与状态​

  • Die 包含初始库存的信息 包含 config, state und status 设备的。
  • 常规 状态更新-包含消息 state und status 设备的。

如果您需要再次创建包含配置的快照,请使用 session.force_render_full_state.

configure vs. commands​

设备消息支持两种独立的映射关系: configure und commands. 它们用于不同的目的:

映射Zweck频率
configure一次性、永久性配置:预设、基础设置、安装、过滤器、模块设置仅发送一次,或如有更改
commands勾选“是”,不保持:力、位置、扭矩必须在每个时钟周期内发送

条目在 commands (set_cursor_force, set_cursor_position, set_angular_torques, set_angular_position) 将被 每帧执行一次,且不保存 – 如果您停止发送这些信号,效果将立即终止,设备将恢复至待机状态。请使用 configure 适用于所有需要在多个时钟周期内保持不变且无需重新发送的内容。

提示
set_transform 是一个特例

set_transform 位于 commands,但 持久的 – 该服务会保留 上次发送的转换,直到您发送新的转换为止。您 müssen 不要 在每个Tick中发送。但由于其目的是用于摄像机或场景导航, 因此以Tick速率进行流式传输(例如,当用户在 场景中平移时,每帧进行一次变换)是连续运动导航的预期使用模式。

坐标系​

Haply 默认使用以Z轴向上为基准的右手坐标系。

有两个配置项会影响坐标的解释和返回方式:

  • session.configure.basis: 更改Haply 坐标与应用程序坐标系之间的映射关系(参见 基础 (见下文)。
  • inverse3[*].configure.preset: 选择一个命名的工厂配置,该配置用于确定工作区的原点(例如,设置 arm_front_centered 该点 (0, 0, 0) (位于工作区域中央)。请参阅设备专用的“配置”部分。

消息格式​

本节介绍了父级信封和消息类型。完整的示例将在本文档后文中给出。

设备组​

消息在最高级别按设备类型分组(例如 inverse3, verse_grip, wireless_verse_grip). 每个设备类型键都与一个包含设备特定对象的数组相关联。

Inventar 首字母缩写(服务器 → 客户端)​

在建立 WebSocket 连接后立即发送一次。

每条记录包含:

  • device_id
  • config
  • state
  • status

参见:示例:初始库存的有效载荷

状态更新(服务器 → 客户端)​

对于每条客户端消息,该消息都会被发送一次。

每条记录包含:

  • device_id
  • state
  • status

参见:示例:状态更新有效载荷

会话命令的封装(客户端 → 服务器)​

会话命令是指适用于当前连接或会话的操作,而非特定于某台设备。

{
"session": {
"<command_name>": {
"...": "..."
}
}
}

设备命令封装(客户端 → 服务器)​

设备命令以数组形式发送至设备类型键下,因此可以通过单条消息将命令发送给一个或多个设备。每个设备条目均支持 configure- 映射以及一个 commands-映射。

{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}

您可以指定多个条目,以便在一条消息中控制多台同类型设备。 请注意, commands 一个词典可以包含针对特定设备的多个命令,但每种命令类型仅包含一个。

那片田野 execute​

每个命令或 configure 条目都接受一个可选的布尔值 "execute" (标准: true). 请设置 "execute": false,以便解析该条目但不应用它。这对于基于反射的序列化器(例如 Unity)非常有用 JsonUtility),它们总是输出每个字段。

"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }

命令参考​

会话​

强制执行状态的完整渲染​

请索取所有设备状态和配置的快照。

{
"session": {
"force_render_full_state": {}
}
}

配置会话​

与会话相关的持久化配置通过 session.configure 已发送。这符合……的模式。 configure-设备级映射。

简介​

设置Haply Hub用于识别模拟环境并为每个应用保存设备特定配置的会话配置文件名称。

{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
基准(整个会议期间)​

设置整个会话的基础映射。基础映射描述了如何 将Haply 坐标转换为应用程序的坐标系; 一旦设置完成,所有设备状态都将以此基础坐标系为基准返回,所有 发送的值也将以此基础坐标系进行解释。

置换字符串由Haply 指定,其参考系为Z轴朝上的右手坐标系——这是一个由 X, Y, Z,可选,前面可加 + oder简体中文(大陆) -. 示例:

  • XYZ, ZYX, +Y-Z+X, X-ZY
  • YZX 这意味着您 Y 符合Haply 的法律立场,您的 Z Haply 及其相关方向 X Haply 的上升趋势。

Z轴朝上的左旋坐标系示例(Unreal, X-YZ):

{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
警告
从……迁移 session.set_basis

关于轴号正负号的约定,在过时的 session.set_basis und session.configure.basis 已更改。原本在旧命令下能正常工作的置换,在新命令下可能会产生反向变换。如有需要,请将坐标轴的正负号颠倒(例如, X-ZY zu XZ-Y).

适用于所有设备的命令​

探测(所有设备)​

使用探测命令来获取当前的位置和姿态信息,同时不施加任何力或进行其他仿真更改。

  • 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": {}
}
}
]
}

应用变换(所有设备)​

为某台设备定义工作区转换(从设备域到应用域)。

与其他不同 commands-条目是 set_transform 持久 – 该服务会保留上次发送的值,直到您发送新值为止。您无需在每次 tick 时都传输该值。但由于其目的是用于摄像机或场景导航,因此在持续移动时,按 tick 速率进行流式传输是预期中的使用模式(例如,在用户移动场景时,每帧进行一次变换)。

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_transform": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
Verse 握把 / 无线 Verse 握把

对于Verse grip和Wireless Verse grip这两款设备,只有该组件会产生影响 rotation 在转换过程中——Grip 仅报告方向信息,因此没有任何东西会 position oder简体中文(大陆) scale 可以缩放或移动。这些字段 position und scale 仅出于与Inverse3 保持模式一致性的原因,这些值才会被接受(并在快照中返回)。通常的做法是将其保留为身份值(position = {0,0,0}, scale = {1,1,1}).

配置适用于Inverse3 的设备​

配置条目存储在 configure-地图已发送。您可以选择“仅发送一次”或“有更改时发送”。

预设​

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
}
}
]
}

可用值: device_defaults, arm_front, arm_front_centered, led_front, led_front_centered, custom.

基础(设备层面)​

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}

安装​

指定设备的安装转换。请注意 transform- 包装在……之内 mount.

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"mount": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
Mount 架构中的不对称性

Die 命令页面 (客户端到服务器)涵盖了以下转换: "mount": { "transform": { ... } }. Die 快照页面 (服务器到客户端)采用扁平化架构: "mount": { "position": {...}, "rotation": {...}, "scale": {...} }. 这是设计使然——命令使用统一的封装器,而快照则直接序列化转换结果。请注意,不要将快照结构复制到命令有效载荷中。

阻尼​

控制均匀和/或方向依赖型阻尼。至少需填写一个字段。

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}

您还可以设置方向依赖型阻尼,或者同时设置这两种类型:

"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }

力量之门​

设置 Force Gate 的衰减增益(0.0 至 1.0)。

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}

Inverse3-命令​

向一个 inverse3-要发送设备,请添加一条包含相应 device_id 在钥匙下方 inverse3 hinzu。命令按每个时钟周期生效,必须在每个时钟周期重新发送一次才能保持有效。

控制单个设备:

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

通过一条消息控制多台设备:

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
},
{
"device_id": "049E",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

设置光标位置​

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"position": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

设置 Cursor-Kraft​

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

设置角度位置​

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"angles": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}

设置角扭矩​

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torques": {
"torques": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}

Verse Grip 的配置​

Verse Grip 和 Wireless Verse Grip 这两款设备支持相同的 configure- 类似Inverse3 的密钥用于 preset, basis und mount.

Verse Grip 扩展协议​

该命令 set_extension_data 它是 Verse Grips 扩展协议的一部分,用于与实现了板扩展协议的 Grip 版本配合使用。

设置握把扩展数据​

支持的数据长度:

  • 上行方向(客户端 → 设备)最多 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",
"port": "COM13",
"device_info": {
"major_version": 7,
"minor_version": 1,
"id": "04BA",
"device_type": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 1
},
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": {
"enabled": true
},
"home_return": {
"enabled": false
},
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": 0 }
},
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"config": {
"type": "verse_grip",
"port": "COM3",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"config": {
"type": "wireless_verse_grip",
"port": "COM6",
"major_version": 1,
"minor_version": 4,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 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
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
]
}

示例:状态-更新-有效载荷​

该服务会针对每条接收到的消息发送一条状态更新消息,其中包含所有设备的状态。

如果您想了解系统的状态,必须事先向系统发送一条消息(例如一个探测命令或一个力值,即使这些值为零)。当设备作为输入源使用(例如用于位置跟踪)且未施加外力时,这一点尤为重要。

状态更新消息内容如下: JSON-格式:

{
"inverse3": [
{
"device_id": "04BA",
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"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
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"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
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"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],
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"ready": true
}
}
]
}