メインコンテンツまでスキップ

06. 統合(Inverse3 + ワイヤレス VerseGrip)

2 デバイスのチュートリアルです。グリップを向けてボタンを押し続けると、その方向へ Inverse3 のカーソルが動きます。カーソルは球状のワークスペース内に制限されます。

学習内容:

  • 同じ state フレーム内の 2 種類のデバイス(inverse3 と wireless_verse_grip)を読み取る
  • クォータニオン からグリップの向き(ローカルの +Y 軸)を取り出す
  • set_cursor_position を使い、計算したターゲットへカーソルを動かす
  • ターゲットを安全なワークスペース球内に制限する。Minverse は Inverse3 より小さい半径を使用する
  • ワークスペースのプリセット(arm_front_centered)を設定し、原点が到達範囲の中央に来るようにする

ワークフロー​

  1. 両方のデバイスを検出します:
    • C++ 版は起動時に HTTP 経由で GET /devices を照会し、キャリブレーションのプロンプトを表示して ENTER の入力を待ちます。
    • Python は最初の WebSocket state フレームから両デバイスの ID を読み取ります。
  2. 最初のメッセージで セッションプロファイル を登録し、configure.preset: arm_front_centered を設定します(ワンショットのハンドシェイク)。
  3. 各ティックで、グリップの orientation と buttons.{a, b} の状態を読み取ります。
  4. 移動ボタンが押されている場合、グリップのワールド空間での方向(R(q) · ĵ、回転後の単位 +Y 軸)を計算し、SPEED でスケールした値をターゲット位置に加算します。
  5. ターゲットをワークスペース球内に制限し、set_cursor_position で送信します。
  6. (Python のみ)デバイスの config.type に応じて球の半径を調整します。minverse の場合は 0.04 m、それ以外は 0.10 m です。

パラメータ​

名前デフォルト用途
SPEED0.01 m/tickボタン押下中の移動ステップ
RADIUS_INVERSE30.10 mInverse3 / Inverse3x 用のワークスペース制限半径
RADIUS_MINVERSE0.04 mMinverse 用のワークスペース制限半径(Python のみ — C++ は 0.10 固定)
PRINT_EVERY_MS200テレメトリの送信頻度制限
セッションプロファイル名co.haply.inverse.tutorials:combinedHaply Hub でこのシミュレーションを識別
実行前にキャリブレーションする
  • Inverse3 に自己キャリブレーションさせます(またはグリップをインクウェルに置き、LED が点灯するまで待ちます)。
  • グリップをインクウェルから取り外します。
  • A または B を押したままグリップを回転させます。カーソルはグリップが向いている方向に動きます。

読み取る state フィールド​

各ティックの state フレームから以下を読み取ります:

  • data.inverse3[0].state.cursor_position — vec3
  • data.wireless_verse_grip[0].state.orientation — quaternion
  • data.wireless_verse_grip[0].state.buttons.{a, b, c} — 真偽値
  • (Python、最初のフレームのみ)data.inverse3[0].config.type — Inverse3 と Minverse のどちらの半径を使うかを選択
  • (Python、最初のフレームのみ)data.inverse3[0].status.calibrated — false の場合はユーザーにプロンプトを表示

送信 / 受信​

クォータニオンから方向を求める計算(R(q) で +Y を回転)と球によるクランプは、線形代数の定番の計算です。詳細はソースファイルを参照してください。Inverse API 側では、ハンドシェイクと各ティックでの set_cursor_position の呼び出しを行います。

単一の非同期ループです。Python は最初の state フレームから両デバイスの ID を読み取ります。ハンドシェイクでは、最初の set_cursor_position にプロファイルと configure.preset: arm_front_centered を付与します。

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))
SDK インストーラーに同梱

チュートリアル 06 も SDK と一緒にローカルにインストールされています。サービスのインストールディレクトリ配下の tutorials/06-haply-inverse-combined/ を参照してください。

ソース: Python · C++ · C++ Glaze

関連: 制御コマンド(set_cursor_position) · 型(quaternion, vec3) · マウントとワークスペース(プリセット) · チュートリアル 03(ワイヤレス VG) · チュートリアル 05(位置制御)