01. 显示Inverse3
连接到WebSocket 模拟环境,并从服务报告的第一个Inverse3 开始,实时传输光标位置、速度和力度数据。
您将学到:
- 建立 WebSocket 连接,并接收包含完整状态的初始消息
- 使用以下方式发送零强度的保持活动(keepalive)信号:
set_cursor_force以便保持会话处于活动状态 - 保存会话配置文件,以便Haply Hub能够识别您的仿真
- 仅限首条消息的握手模式:在首次发送后移除 session/configure
- 阅读
transform工作空间(位置、旋转、缩放)与部分更新语义 - 将控制台的显示速率限制在可读的频率
流程
- 建立与……的 WebSocket 连接
ws://localhost:10001. 该服务会立即发送一封 完整状态帧 该列表列出了已连接的设备。 - 在第一个帧中,选择
device_id从第一个Inverse3 开始,构建一条由两部分组成的请求消息:session.configure.profile.name: 将模拟结果保存到 Haply Hub.- 一笔订单
set_cursor_force按设备发送,且向量为零。该服务将其用作保持信号:只要有命令到达,它就会继续发送状态帧。
- 转发该消息。 删除该字段
session在下一个周期之前:会话配置文件是一次性的握手,而后续周期仅发送该命令。 - 对于接下来的每个状态帧:显示字段
vec3从光标(位置、速度、力)采集数据,采样率限制在约10 Hz,然后发送零力保持信号。
参数
| Nom | 缺陷 | 角色 |
|---|---|---|
URI | ws://localhost:10001 | WebSocket 模拟通道的 URL |
PRINT_EVERY_MS | 100 | 控制台显示限制 |
| 会话配置文件名称 | co.haply.inverse.tutorials:print-inverse3 | 在Haply Hub中识别此模拟 |
阅读过的州冠军
自 data.inverse3[0].state :
cursor_position,cursor_velocity,current_cursor_force: 类型vec3chacuntransform: 工作空间变换;子对象带有position(vec3),rotation(quaternion) 和scale(vec3)
等于其恒等值的子域(position: {0,0,0}, rotation: {w:1,x:0,y:0,z:0}, scale: {1,1,1}) 就是 omis 从有效载荷中提取,以节省带宽。读取时请务必提供默认值(例如 .value("position", default_pos) 在 C++ 中, .get("position", default_pos) (在 Python 中)。启用 serialization/explicit_fields 以便始终接收每个字段。
发送与接收
WebSocket 循环:接收状态帧,构建并返回一个 控制帧. 第一条控制帧携带会话握手信息以及一个零强制保持信号,其中 set_cursor_force; 每个后续帧不传输 que简体中文(大陆) 保持信号(会话已被撤销)。
- Python
- C++ (nlohmann)
- C++ (Glaze)
唯一的异步循环: recv() → 构建命令 → send() → 重复。
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"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-inverse3"}}},
"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_force":
{"vector": {"x": 0.0, "y": 0.0, "z": 0.0}}},
}]
}
await websocket.send(json.dumps(request_msg))
request_msg.pop("session", None) # one-shot handshake
libhv 在其自身的 I/O 线程上处理 WebSocket;按帧处理的代码位于 ws.onmessage. 主线程只需等待按下回车键。
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["inverse3"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-inverse3"}}}}}}},
{"inverse3", json::array({
{{"device_id", device_id},
{"commands", {{"set_cursor_force",
{{"vector", {{"x", 0.0}, {"y", 0.0}, {"z", 0.0}}}}}}}},
})},
};
}
ws.send(request_msg.dump());
request_msg.erase("session"); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
与 nlohmann 变体相同的 libhv 回调(callback)模型:仅主体部分有所不同。Glaze 在编译时使用反射:声明反映 JSON 格式的结构,然后调用 glz::read / glz::write_json. std::optional<session_cmd> 传递一次性握手;如果未定义,Glaze 会在 JSON 序列化中省略该字段。
// Struct models
struct vec3 { float x{}, y{}, z{}; };
struct inverse_state {
vec3 cursor_position{}, cursor_velocity{}, current_cursor_force{};
/* + body_orientation, angular_position, angular_velocity */
};
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message { std::vector<inverse_device> inverse3; };
struct set_cursor_force_cmd { vec3 vector; };
struct commands_message {
std::optional<session_cmd> session; // omitted from JSON when unset
std::vector<device_commands> inverse3;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
commands_message out_cmds{};
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = print-inverse3 */ };
}
// ... populate out_cmds.inverse3 with zero-force keepalive ...
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
命令行选项(Python)
Python 版本支持两个选项,这些选项会改变教程中显示的内容:
| 选项 | Effet |
|---|---|
--full | 显示每个状态帧的格式化 JSON 总有效载荷,而非一行摘要。这有助于了解该服务发出的字段。 |
--query-config | 重新注入 session.force_render_full_state: {} 在每个输出周期中,以便该服务重新生成一个完整的快照(包括该块 config :设备类型、固件、预设、构图、滤镜等)在每一帧中。如果不启用此选项, config 仅在第一个帧中出现,而后续帧中仅包含以流式传输方式发送的增量更新。 |
这两种选项可以结合使用: python 01-haply-inverse-print-inverse3.py --full --query-config 通过该代码块显示完整的 JSON 有效载荷 config 每个周期都会显示,这便于通过Haply Hub或HTTP API实时观察参数的变化。参见 session.force_render_full_state pour la commande WebSocket sous-jacente.
C++ 版本不提供这些选项:它们始终显示一行摘要,且不接受 config 与第一帧相同。
教程 01 也随 SDK 一起安装在本地:请参阅 tutorials/01-haply-inverse-print-inverse3/ 在服务安装目录中。
另请参阅: WebSocket 协议 · 控制命令(set_cursor_force) · 会话 · 类型 (vec3)