09. WebSocket 工作区导航器
通过在 WebSocket 通道上进行流式传输 set_transform 命令,实时导航设备的工作场所位置。本教程使用 HTTP 来发现会话和设备(与 教程 08 (完全相同),然后打开一个 WebSocket,并在每个 tick 时推送位置更新。
下表说明了两种变换操作之间的区别:
| 操作 | 教程 | 典型用法 |
|---|---|---|
configure.mount — 持久化的挂载转换 | 08 | 只需设置一次(或极少更改),用于描述设备的物理安装方式 |
set_transform — 工作空间变换的持久化 | 09(本教程) | 虽然同样是持久化的,但可以每 tick 进行流式发送,用于实时工作空间导航 |
这两种命令都可以通过 HTTP 和 WebSocket 使用,而且都是持久化的——服务会记住你最后发送的值。两者的区别在于用途和更新频率:configure.mount 针对低频更新(物理设置)进行了优化,而 set_transform 针对高频流式发送(场景导航)进行了优化。
当工作空间原点需要跟随运行时发生变化的对象时,请使用 set_transform——例如跟随手部动作、为程序化偏移制作动画,或让操作员实时微调虚拟工作空间。
单独来看,configure.mount 和 set_transform 虽然看起来会产生相同的效果,但二者之间存在父子关系:在坐标管线中,工作空间变换作用于挂载之上(device → basis → mount → workspace → application)。完整管线请参见 挂载与工作区。
使用场景
- 实时工作空间导航。在不中断触觉场景的情况下,移动设备光标的工作空间(即末端执行器可到达的包络区域)。
- 由操作员驱动的对齐。让另一台终端中的用户按住方向键,在主触觉应用运行时平移工作空间——适用于在实时会话期间为每位用户进行桌面校准。
- 程序化工作空间运动。通过脚本(音频驱动、物理驱动等)控制工作空间的位置,每个 tick 仅发送仅包含位置的变换补丁。
先决条件
教程 09 需要一个正在运行的 WebSocket 会话——即在运行中的触觉循环中已有Inverse3 的客户端。教程 09 随后将连接到该会话,并实时平移其工作空间。
打开Haply Hub,启动 Orb演示。这将打开一个持久的触觉会话,你可以立即将其设为目标:
./09-haply-inverse-ws-remote-control --session co.haply.hub::demo-orb
python 09-haply-inverse-ws-remote-control.py --session "co.haply.hub::demo-orb"
“Orb” 场景会在工作空间原点处渲染一个球体——按照教程 09“移动工作空间”操作,可实时平移整个场景,让你即时获得视觉反馈。
任何其他运行触觉循环的教程(01 – 07)同样适用。先在终端中启动它,然后在第二个终端中启动教程 09,让它以交互方式发现会话即可。
用法
# Discover session interactively, use first detected device
./09-haply-inverse-ws-remote-control
python 09-haply-inverse-ws-remote-control.py
# Target the Haply Hub Orb demo directly
./09-haply-inverse-ws-remote-control --session co.haply.hub::demo-orb
python 09-haply-inverse-ws-remote-control.py --session "co.haply.hub::demo-orb"
# Target a session directly, specify device
./09-haply-inverse-ws-remote-control --session :my_profile:0 --device A14
python 09-haply-inverse-ws-remote-control.py --session "#42" --device A14
控制
- Python
- C++
按住某个键即可持续移动,松开即停止。
| 按键 | 操作 |
|---|---|
→ / ← | +X / −X |
↑ / ↓ | +Y / −Y |
Page Up / Page Down | +Z / −Z |
= / - | 提高 / 降低导航速度 |
0 | 将工作空间重置到原点 |
H | 显示帮助 |
Esc | 退出(Ctrl+C (同样有效) |
基于行输入——输入后按 ENTER 键。
| 命令 | 操作 |
|---|---|
x+[N] / x-[N] | 将 X 轴速度设置为 ±N mm/帧(直接输入) x+ (则使用当前默认速度) |
y+[N] / y-[N] | 将 Y 轴速度设置为 ±N 毫米/帧 |
z+[N] / z-[N] | 将 Z 轴速度设置为 ±N mm/帧 |
s+[N] / s-[N] | 以 ±N 毫米/帧 为单位调整默认速度 |
stop | 将所有速度清零 |
reset | 将速度归零并返回原点 |
h | 显示帮助 |
按Ctrl+C(或 Ctrl+D / EOF)退出。
工作原理
第 1 步 — HTTP 会话与设备发现
本教程复用与 教程 08 相同的 discover_session() / discover_device() 辅助函数:先执行 GET /sessions(如果指定了 --session,则为 GET /sessions/<selector>),然后执行 GET /devices(或直接使用 --device)。这两个辅助函数接受相同的命令行参数,并且 SESSION_HELP 文本完全一致——来自教程 00 或 08 的选择器无需修改即可在此使用。
第 2 步 — WebSocket 会话与首条消息的握手
本教程将打开一个指向 ws://localhost:10001 的 WebSocket。仅在收到第一条消息时,它将发送会话配置注册信息:
{
"session": {"configure": {"profile": {"name": "co.haply.inverse.tutorials:ws-remote-control"}}},
"inverse3": [{"device_id": "...", "commands": {"set_transform": {"transform": {"position": {"x": 0, "y": 0, "z": 0}}}}}]
}
后续的 tick 省略 session 字段——仅发送 inverse3 命令数组。
第 3 步 — 每个 tick 的 set_transform
set_transform 接受部分变换——只需包含你希望修改的字段。本教程仅发送位置;旋转和缩放将保持会话的默认值(即 configure.mount (如果有的话,所设置的值)。
{
"inverse3": [{"device_id": "...", "commands": {"set_transform": {"transform": {"position": {"x": 0.02, "y": 0.0, "z": 0.01}}}}}]
}
位置将按每个 tick 累积。在约 1 kHz 的频率下,服务将在每个 tick 重新应用 set_transform(零阶保持),因此工作区将保持在你上次推送时的位置。
set_transform 与 configure.mount 的区别这两个命令都会为每个会话存储一个持久化变换,但在管道中,工作区变换位于挂载点之上(参见 挂载与工作区)。当教程 09 在每个 tick 时流式发送 set_transform 此时,每个新的 WebSocket 帧都会替换之前流式发送的工作空间变换——因此,任何其他流式发送至同一设备的 set_transform 的客户端,都会在下一个 tick 被覆盖。configure.mount 彼此独立,且其值保持不变。在实际应用中,每类职责各选一条通道:挂载通道用于物理设置,set_transform 用于实时导航。
线程模型(C++)
C++ 教程中,两个线程共享一个位置累加器:
| 线程 | 职责 |
|---|---|
| 主线程 | 读取 std::getline(std::cin, ...),解析令牌,更新 nav_vel[] 和 default_step |
| WebSocket 回调线程 | 使用 nav_vel[] * dt 推进 nav_pos[],序列化 set_transform 并发送 |
std::mutex nav_mutex 保护 nav_pos[]、nav_vel[] 和 default_step。last_tick 时间戳与 first_ws_msg 标志是 WS 回调的局部变量,无需互斥锁。
ws.onmessage = [&](const std::string &) {
float pos_snap[3];
{
std::lock_guard<std::mutex> lk(nav_mutex);
for (int i = 0; i < 3; ++i) nav_pos[i] += nav_vel[i] * 0.001f; // mm/frame → m
for (int i = 0; i < 3; ++i) pos_snap[i] = nav_pos[i];
}
// build and send set_transform with pos_snap
};
源代码
教程 09 已随 SDK 一起安装在本地,请在服务安装目录下的 tutorials/09-haply-inverse-ws-remote-control/ 中查找。
相关内容: set_transform 命令 · 会话 — 首条消息握手 · 挂载与工作区 · 选择器 · 教程 08 — HTTP 远程配置 · 教程 07 — Basis 与挂载