06. コンビネーション(Inverse3 +Wireless VerseGrip)
2台のデバイスを使ったチュートリアル:ハンドルを向けてボタンを押し続けると、Inverse3 のカーソルがその方向に移動します。カーソルは球形の作業領域内に制限されます。
学習内容:
- 同じステートフレーム内で 2 種類のデバイスを読み取る(
inverse3etwireless_verse_grip) - ハンドルの向きを、その位置から抽出する 四元数 (axe local
+Y) - 利用
set_cursor_positionカーソルを計算されたターゲットの位置に移動させるには - ターゲットを安全な作業領域の球体に限定する —Minverse では、これよりも小さい半径が使用されています。Inverse3
- 定義する ワークスペースのプリセット (
arm_front_centered) 五線譜の中心に音符の原点が来るように
進行
- 2台のデバイスを検出する:
- バリエーション C++ 質問者
GET /devices起動時にHTTP経由で接続し、その後、キャリブレーションのプロンプトを表示して、ユーザーがEnterキーを押すのを待ちます。 - Pythonは、WebSocketの最初のステータスフレームから、2台のデバイスの識別子を読み取りました。
- バリエーション C++ 質問者
- を登録してください セッションプロファイル そして、以下のように設定してください
configure.preset: arm_front_centered最初のメッセージ(一度限りの連絡)において。 - 各サイクルごとに、以下の
orientation取っ手の状態とbuttons.{a, b}. - 移動ボタンが押されている場合、ワールド空間におけるハンドルの方向を計算します(
R(q) · ĵ— 回転させた単位軸 +Y)を、これにSPEEDそして、それを目的の位置に追加してください。 - ターゲットを作業領域の球体内に限定し、以下を通じて送信してください。
set_cursor_position. - (Pythonのみ)状況に応じて球の半径を調整してください。
config.type本機の —minverse= 0.04 m、それ以外はすべて = 0.10 m。
パラメータ
| ノム | デフォルト値 | 役割 |
|---|---|---|
SPEED | 0.01 m/cycle | ボタンが押されている間は移動しない |
RADIUS_INVERSE3 | 0.10 m | Inverse3 /Inverse3x のワークスペースの制約半径 |
RADIUS_MINVERSE | 0.04 m | Minverse のワークスペースの制約範囲(Pythonのみ — C++は修正済み) 0.10 (ハード) |
PRINT_EVERY_MS | 200 | テレメトリのデータ転送速度の制限 |
| セッションプロファイル名 | co.haply.inverse.tutorials:combined | Haply Hub でこのシミュレーションを特定してください |
- Inverse3 が自動的にキャリブレーションを行うのを待ちます(または、グリップをインク壺の上に置き、LEDが点灯したままになるまで待ちます)。
- インク壺の取っ手を取り外してください。
- A または B を押し続けながらハンドルを回してください。カーソルは、ハンドルが向いている方向に移動します。
よく読まれている記事
各サイクルの状態トレースに基づいて:
data.inverse3[0].state.cursor_position—vec3data.wireless_verse_grip[0].state.orientation—quaterniondata.wireless_verse_grip[0].state.buttons.{a, b, c}— ブール値- (Python、最初のフレームのみ)
data.inverse3[0].config.type— 「Inverse3 」の半径、または「Minverse - (Python、最初のフレームのみ)
data.inverse3[0].status.calibrated— 値が誤っている場合、ユーザーにキャリブレーションを行うよう促す
送信と受信
四元数からの方向の計算(回転) +Y par R(q)) および球面への制約は、古典的な線形代数の範囲に属します — ソースファイルを参照してください。Inverse API に関しては、これは初期の接触処理と set_cursor_position 各サイクルごとに。
- Python
- C++ (nlohmann)
- C++ (Glaze)
一意の非同期ループ。Pythonは最初のステータスフレームから2つのデバイスの識別子を読み取り、接続確立時にプロファイルを結合し、 configure.preset: arm_front_centered au premier set_cursor_position.
async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)
if first_message:
first_message = False
inverse3_id = data["inverse3"][0]["device_id"]
grip_id = data["wireless_verse_grip"][0]["device_id"]
radius = get_workspace_radius(data["inverse3"][0].get("config", {}))
# Handshake: profile + preset + first position command
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": inverse3_id,
"configure": {"preset": {"preset": "arm_front_centered"}},
"commands": {"set_cursor_position": {"position": position}},
}],
}
else:
# Per tick: update position from grip pointing direction (classic math, not shown), send
request_msg = {
"inverse3": [{
"device_id": inverse3_id,
"commands": {"set_cursor_position": {"position": position}},
}],
}
await websocket.send(json.dumps(request_msg))
C++は、以下の方法を通じて2台のデバイスの識別子を検出します。 GET /devices 起動時(HTTP)に、WebSocketを開きます。 onmessage libhvのI/Oスレッド上で実行されます。メインスレッドは、入力待ちの状態のままブロックされます。
// Startup (synchronous):
const std::string inv3_device_id = get_first_device_id("inverse3");
const std::string grip_device_id = get_first_device_id("wireless_verse_grip");
// Per tick:
ws.onmessage = [&](const std::string &msg) {
json data = json::parse(msg);
// ... classic math (not shown): update local Inverse3State + WirelessVerseGripState,
// compute new position from grip orientation, clamp to sphere ...
json command;
command["inverse3"] = json::array();
command["inverse3"].push_back({
{"device_id", inv3_device_id},
{"commands", {{"set_cursor_position",
{{"position", {{"x", pos.x}, {"y", pos.y}, {"z", pos.z}}}}}}}
});
if (first_message) {
first_message = false;
command["session"] = {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:combined"}}}}}};
command["inverse3"][0]["configure"] = {
{"preset", {{"preset", "arm_front_centered"}}}};
}
ws.send(command.dump());
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
libhv と同じリコールモデルです。型付き構造体によって、状態フレームと出力コマンドの両方がモデル化されています — これら2つは std::vector<> dans devices_message これにより、1つの glz::read これら2種類の機器を製造すること。
// Struct models
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct button_state { bool a{}, b{}, c{}; };
struct wvg_state { quat orientation{}; uint8_t hall{}; button_state buttons{}; };
struct wvg_device { std::string device_id; wvg_state state; };
struct inverse_state { vec3 cursor_position{}, cursor_velocity{}; };
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message {
std::vector<inverse_device> inverse3;
std::vector<wvg_device> wireless_verse_grip;
};
struct set_cursor_position_cmd { vec3 position; };
struct commands_message {
std::optional<session_cmd> session;
std::vector<device_commands> inverse3;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
const auto &wvg = data.wireless_verse_grip[0].state;
cursor_pos = data.inverse3[0].state.cursor_position;
// ... classic math (not shown): if (wvg.buttons.a || wvg.buttons.b)
// move in pointing dir; clamp to sphere ...
commands_message out_cmds{};
device_commands dc{ .device_id = inv3_device_id };
dc.commands.set_cursor_position = set_cursor_position_cmd{cursor_pos};
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = combined */ };
dc.configure = device_configure{ .preset = preset_cfg{"arm_front_centered"} };
}
out_cmds.inverse3.push_back(std::move(dc));
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
チュートリアル 06 も SDK とともにローカルにインストールされています。詳しくは tutorials/06-haply-inverse-combined/ サービスのインストールディレクトリ内。
関連項目: 制御コマンド (set_cursor_position) ・ 型 (quaternion, vec3) ・ モンタージュと作業スペース(プリセット) ・ チュートリアル 03(ワイヤレス VG) ・ チュートリアル 05(位置制御)