跳至主要内容

07. 基础与架设 演练场

通过交互方式驱动设备的 mount 转换,并演示 configure.basis、configure.preset 与 configure.mount 在通信报文层面上如何协同工作。保持一个固定的基准,使交互重点始终集中在 configure 命令上。

你将学到:

  • 设置 basis 轴置换("XZY" → Y 轴方向上的应用坐标系)
  • 选择 预设(arm_front),并了解它会配置哪些内容
  • 在运行时通过旋转四元数(由键盘控制)覆盖mount
  • 互斥规则:mount 与 preset 不能同时出现在同一个 设备配置 块中
  • 一次性 configure 语义:每次按键仅发送一条 configure 消息
  • (C++ Glaze)使用 std::optional 对互斥字段的建模

工作流程​

  1. 收到第一条消息时:发送 会话配置文件、configure.basis: "XZY" 以及 configure.preset: arm_front。为固定地面开始持续发送 set_cursor_force。
  2. 每个 tick:读取光标的 Y 坐标,计算 force_y = max(0, (floor_pos - y) * stiffness) 并发送。
  3. 当用户按下“mount”旋转键时,将 pending_configure = true 置位。
  4. 在下一个 tick:构建一个 configure.mount 块,其变换的 rotation 单位四元数(按当前俯仰角/偏航角,先 Z 后 X 进行复合)。省略 preset——二者通信协议中互斥。
  5. 重置键(R)清除覆盖设置;下次执行 configure 时将恢复为 preset。

参数​

名称默认值用途
BASIS"XZY"轴置换— Y 轴方向上的应用坐标系
DEVICE_PRESET / DEVICE_CONFIG_PRESET"arm_front"命名的预设 — 原点位于设备底座
FLOOR_POS_Y0.0 m固定地面平面(应用坐标系 Y 轴)
STIFFNESS1000 不适用地面弹簧常数
MOUNT_STEP_DEG10°每次按键的旋转角度
PRINT_EVERY_MS200遥测节流间隔

控制​

按键操作
W / S围绕设备沿+X轴(俯仰)旋转,旋转角度为±10°
A / D围绕设备沿+Z轴(偏航)旋转 ±10°
R重置挂载——恢复为默认设置
H显示控制键
Q退出
mount 与 preset 互斥

该服务将拒绝同时包含这两者的请求 设备配置 块。一旦用户覆盖了 mount,本教程在后续的每次 configure 中都会省略 preset。按下 R 之后,下次执行 configure 时将重新启用 preset 并去掉 mount。

不同的输入模式

C++ 变体在后台的 stdin 线程上按行读取输入(每个字母后需按 ENTER 键)。Python 则在主异步循环中使用 keyboard 对包进行实时按键轮询,无需按 ENTER 键。按键相同,命令也相同。

读取的状态字段​

来源: data.inverse3[i].state:

  • cursor_position.y — vec3,用于计算穿透地面的深度
  • current_cursor_force — 用于遥测上报

发送 / 接收​

各变体的负载结构相同;真正的区别在于每个变体如何构建互斥的 mount / preset 分支,以及输入线程如何通知 WebSocket 线程。

单一异步循环,通过 keyboard 对包进行实时按键轮询。pending_configure 这是一个全局标志,由按键处理函数设置,每次发送时 configure 块后清除。

async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)

if first_message:
first_message = False
device_id = data["inverse3"][0]["device_id"]
# Handshake: profile + basis + preset
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": device_id,
"configure": build_configure_block(first_handshake=True),
# -> {"basis": {"permutation": "XZY"},
# "preset": {"preset": "arm_front"}}
}],
}
else:
handle_key_inputs() # may set pending_configure = True (classic, not shown)

y = data["inverse3"][0]["state"]["cursor_position"]["y"]
force_y = 0.0 if y > FLOOR_POS_Y else (FLOOR_POS_Y - y) * STIFFNESS

entry = {
"device_id": device_id,
"commands": {"set_cursor_force":
{"vector": {"x": 0.0, "y": force_y, "z": 0.0}}},
}
if pending_configure:
entry["configure"] = build_configure_block(first_handshake=False)
# -> {"mount": {...}} OR {"preset": {...}} (never both)
pending_configure = False

request_msg = {"inverse3": [entry]}

await websocket.send(json.dumps(request_msg))
随 SDK 安装程序一同提供

教程 07 也随 SDK 一起安装在本地——请在服务安装目录下查看 tutorials/07-haply-inverse-basis-and-mount/。

源代码: Python·C++·C++ Glaze

相关内容: Basis 轴置换 · Mount 与工作区 · 设备配置 · 控制命令(set_cursor_force) · 类型(变换) · 教程 04(Hello Floor)