02. VerseGrip 输出
从第一台有线 VerseGrip 设备实时传输方向(四元数+ Z-X-Y 欧拉角)、霍尔传感器电平和按钮状态。
学习内容:
- 在状态帧中
quaternion方向识别 - 将四元数转换为以度为单位的 Z-X-Y 欧拉角(+X 向右,+Y 向前,+Z 向上)
probe_orientation将其用作独立观察者的保持活动机制- 仅在第一条消息中发送的握手模式(与教程 01相同)
工作流
ws://localhost:10001打开 WebSocket 并等待第一个状态帧。verse_grip数组中第一个有线VerseGrip的device_id选择。- 会话配置文件以及按设备分类
probe_orientation创建一个包含 keepalive 的请求(这是一个空对象指令,用于确保状态帧中的抓取方向数据持续传输)。 - 发送请求后
session删除该字段。该字段仅用于一次性握手。 - 此后,在每个帧中将四元数转换为欧拉角,并输出经过限速处理的遥测数据。在每个时钟周期内重新发送保持活动信号。
参数
| 姓名 | 默认值 | 用途 |
|---|---|---|
URI | ws://localhost:10001 | 仿真通道 WebSocket URL |
PRINT_EVERY_MS | 100 | 控制台输出限流 |
| 会话配置文件名称 | co.haply.inverse.tutorials:print-verse-grip | Haply 在 Hub 中识别此模拟 |
转换是应用程序框架 +X right, +Y forward, +Z up这是一种基于内参(intrinsic)的 Z-X-Y(偏航 → 俯仰 → 滚转)方式。 glm::eulerAngles请勿使用。 由于该函数遵循不同的规范,因此在此处会被错误地解读。三种语言版本均实现了相同的公式,具体公式请参阅源代码。
probe_orientation如果确实有必要的话probe_orientation该会话向 Inverse3 发送了指令 不发送 仅在此情况下有用。Inverse3 (当向系统发出关于力、位置、扭矩等指令时),系统会自动将与之配对的VerseGrip的方向信息流式传输到每个状态帧中,因此无需探针。 probe_orientation请仅将此工具用于本教程中介绍的此类独立握持监测工具。
“正在阅读”字段
data.verse_grip[0].state摘自:
orientation—quaternion(w, x, y, z)hall— 整型霍尔传感器值button— 布利安transform.rotation— 工作区旋转(quaternion). 在方向专用设备上,由于位置和比例不适用,因此绝不会显示。
transform.rotation银恒等四元数 {w:1,x:0,y:0,z:0}当与……相同时,该项将被省略。读取时请提供默认值。若要始终获取该值,请 serialization/explicit_fields请启用该功能。
发送 / 接收
WebSocket 循环:接收状态帧,进行握手并 probe_orientation 配置 keepalive 并重新发送。第一个发送的消息包含会话配置文件,此后所有帧仅包含 keepalive。
- 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["verse_grip"][0]["device_id"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-verse-grip"}}},
"verse_grip": [{
"device_id": device_id,
"commands": {"probe_orientation": {}} # empty — keepalive
}]
}
await websocket.send(json.dumps(request_msg))
request_msg.pop("session", None) # one-shot handshake
libhv 在 I/O 线程中运行 WebSocket — 每帧操作是 ws.onmessage在……处进行处理。主线程会阻塞,等待 ENTER 键的输入。
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["verse_grip"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-versegrip"}}}}}}},
{"verse_grip", json::array({
{{"device_id", device_id},
{"commands", {{"probe_orientation", json::object()}}}},
})},
};
}
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
它使用相同的 libhv 回调模型。指定类型的结构体 nlohmann::json替换为 — probe_orientation_cmd是一个空结构体(Glaze 将其称为 {}(记录为)。 std::optional<session_cmd>处理仅执行一次的握手过程,并在首次传输后 .reset()调用该方法后,此项将在后续的 JSON 输出中被省略。
// Struct models
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct grip_state { quat orientation{}; bool button{}; uint8_t hall{}; };
struct grip_device { std::string device_id; grip_state state; };
struct devices_message { std::vector<grip_device> verse_grip; };
struct probe_orientation_cmd {}; // empty object on the wire
struct commands_message {
std::optional<session_cmd> session; // one-shot — omitted when unset
std::vector<device_commands> verse_grip;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = print-versegrip */ };
device_commands dc{ .device_id = data.verse_grip[0].device_id };
dc.commands.probe_orientation = probe_orientation_cmd{};
out_cmds.verse_grip.push_back(std::move(dc));
}
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
out_cmds.session.reset(); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
命令行参数(Python)
Python 变体支持与教程 01相同的两个标志:
--full— 不再显示一行摘要,而是将原始 JSON 有效载荷以易于阅读的方式输出。--query-config— 每个发出的 tick 都session.force_render_full_state通过重新注入,使每个帧不再仅传递状态增量,而是config请确保包含块(设备类型、固件、挂载转换等)。这在实时调试设置更改时非常有用。
这两个选项可以一起使用。C++ 版本不提供此选项。
教程 02 也随 SDK 一起安装在本地了——位于服务安装目录下的 tutorials/02-haply-inverse-print-verse-grip/您可以在……处查看。
相关: 类型(四元数) · 控制命令 (probe_orientation) · WebSocket 协议 · 教程 03(无线 VG)