02. VerseGripの印刷
最初の有線VerseGripから、姿勢(四元数+Z-X-Yオイラー角)、ホールセンサーの電圧レベル、およびボタン状態をリアルタイムで出力します。
学習内容:
- ステートフレームから読み込む
quaternion姿勢 - 四元数を、度単位の Z-X-Y オイラー角に変換する(+X が右、+Y が前、+Z が上)
- 将
probe_orientation独立オブザーバーとして機能するための生存維持コマンド - 最初のメッセージのみハンドシェイクが必要なモード(チュートリアル01と同じ)
ワークフロー
- [リンク先]を開く
ws://localhost:10001WebSocket を確立し、最初のステータスフレームを待ちます。 - から
verse_grip配列の中から、最初に現れる有線VerseGripをdevice_id。 - 使用 会話構成 また、各デバイスごとに
probe_orientation保活コマンドの構築リクエスト(これは空のオブジェクトコマンドであり、保持姿勢データがステートフレームに継続して表示されるようにするためのものです)。 - リクエストを送信してから、削除してください
sessionフィールド――これは1回限りのハンドシェイクにのみ使用されます。 - その後、各フレームごとに四元数をオイラー角に変換し、スロットリング方式でテレメトリデータを出力します。各サイクルごとに、接続維持コマンドを再送信します。
パラメータ
| 名称 | デフォルト値 | 用途 |
|---|---|---|
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 Z-X-Y(ヨー → ピッチ → ロール)の順で内旋する。しないでください使用 glm::eulerAngles——これは異なる規約に従っているため、ここでは誤った結果が読み取られてしまいます。3つの言語版では同じ数学計算が実装されています。具体的な数式については、ソースコードをご参照ください。
probe_orientationセッションが未Inverse3 にコマンドを送信する際は、probe_orientation そうして初めて意味があります。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 ハートビートコマンド。最初のパケットにはセッション設定情報が含まれますが、それ以降の各パケットにはハートビートコマンドのみが含まれます。
- 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> 1回限りのハンドシェイクを処理するために使用されます:初回送信後に呼び出されます .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と同じ2つの引数を受け取ります:
--full— 1行の要約ではなく、元のJSONペイロードデータをフォーマットされた形式で出力します。--query-config— 各サイクルごとに再注入を行うsession.force_render_full_state,各フレームにconfig状態の増分だけでなく、ブロック(デバイスタイプ、ファームウェア、インストール変換など)も反映されます。これは、デバッグ設定をリアルタイムで変更する際に非常に役立ちます。
2つの引数は組み合わせて使用できます。C++ バージョンでは、これらの引数は提供されていません。
チュートリアル 02 も SDK とともにローカルにインストールされます。サービスがインストールされているディレクトリ内の tutorials/02-haply-inverse-print-verse-grip/ 中で探す。
出典: Python ・C++ ・C++ Glaze
関連情報: 型(四元数) ・ 制御コマンド(probe_orientation) ・ WebSocket プロトコル ・ チュートリアル 03(ワイヤレス VG)