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

09. WebSocket ワークスペースナビゲーター

WebSocket チャネル経由で set_transform コマンドをストリーミングすることで、デバイスのワークスペース位置をリアルタイムにナビゲートできます。このチュートリアルでは、HTTP を使用してセッションとデバイスを検出します(チュートリアル08 とまったく同じ方法です)。その後、WebSocket を開き、ティックごとに位置の更新を送信します。

これは、2つの変換操作の違いを示しています:

操作チュートリアル典型的な用途
configure.mount — 永続的なマウント変換08一度だけ(またはまれに)設定し、デバイスの物理的な取り付けを記述する
set_transform — 永続的なワークスペース変換09(本チュートリアル)こちらも永続的ですが、リアルタイムのワークスペースナビゲーションのために、ティックごとにストリーミングできる

どちらのコマンドも HTTP と WebSocket 経由で利用でき、どちらも永続的です。サービスは最後に送信された値を記憶しています。違いは目的と更新頻度にあります。configure.mount は頻度の低い更新(物理的なセットアップ)に最適化されているのに対し、set_transform は高頻度のストリーミング(シーンのナビゲーション)に最適化されています。

ワークスペースの原点が実行時に変化する対象(手への追従、手続き的なオフセットのアニメーション、オペレーターによる仮想ワークスペースのライブな微調整など)を追跡する必要がある場合は、set_transform を使用してください。

マウントとワークスペースは親子関係にあります

個別に見ると、configure.mount と set_transform は同じ効果を生むように見えますが、両者は親子関係にあります。座標パイプライン(device → basis → mount → workspace → application)では、ワークスペース変換がマウントの上に適用されます。パイプライン全体については、マウントとワークスペースを参照してください。

ユースケース​

  • ライブのワークスペースナビゲーション。 触覚シーンを停止させることなく、デバイスのカーソルワークスペース(エンドエフェクターが到達できる境界領域)を移動させます。
  • オペレーターによる位置合わせ。 メインの触覚アプリケーションの実行中に、2つ目のターミナルにいる人が矢印キーを押し続けてワークスペースをずらせるようにします。ライブセッション中に、ユーザーごとのデスクキャリブレーションを行う場合に便利です。
  • 手続き的なワークスペース動作。 ティックごとに位置のみの変換パッチを送信することで、スクリプト(音声反応型、物理駆動型など)からワークスペース位置を制御します。

前提条件​

チュートリアル09には、アクティブなWebSocketセッションが必要です。つまり、実行中の触覚ループで Inverse3 を既に保持しているクライアントです。チュートリアル09はそのセッションにアタッチし、ワークスペースをリアルタイムに移動させます。

セッションを最も手早く起動する方法

Haply Hub を開き、Orb デモを起動します。すぐに対象にできる、永続的な触覚セッションが開かれます:

./09-haply-inverse-ws-remote-control --session co.haply.hub::demo-orb
python 09-haply-inverse-ws-remote-control.py --session "co.haply.hub::demo-orb"

Orbシーンは、ワークスペースの原点に球体を描画します。チュートリアル09でワークスペースを移動させると、シーン全体がリアルタイムに移動するため、即座に視覚的なフィードバックを得られます。

触覚ループを実行する他のチュートリアル(01 – 07)も同様に動作します。一方のターミナルでそれを起動し、もう一方のターミナルでチュートリアル09を起動して、対話的にセッションを検出させてください。

使い方​

# Discover session interactively, use first detected device
./09-haply-inverse-ws-remote-control
python 09-haply-inverse-ws-remote-control.py

# Target the Haply Hub Orb demo directly
./09-haply-inverse-ws-remote-control --session co.haply.hub::demo-orb
python 09-haply-inverse-ws-remote-control.py --session "co.haply.hub::demo-orb"

# Target a session directly, specify device
./09-haply-inverse-ws-remote-control --session :my_profile:0 --device A14
python 09-haply-inverse-ws-remote-control.py --session "#42" --device A14

操作方法​

キーを押し続けると連続的に移動します。離すと停止します。

キー操作
→ / ←+X / −X
↑ / ↓+Y / −Y
Page Up / Page Down+Z / −Z
= / -ナビゲーション速度を上げる / 下げる
0ワークスペースを原点にリセット
Hヘルプを表示
Esc終了(Ctrl+C でも終了できます)

仕組み​

ステップ1 — HTTP セッションとデバイスの検出​

このチュートリアルは、チュートリアル08 と同じ discover_session() / discover_device() ヘルパーを再利用します。まず GET /sessions(--session を指定する場合は GET /sessions/<selector>)を、次に GET /devices(または --device を直接使用)を呼び出します。どちらのヘルパーも同一のCLIフラグと同一の SESSION_HELP テキストを受け付けるため、チュートリアル00または08のセレクターを、ここでも変更せずに使用できます。

ステップ2 — WebSocket セッションと最初のメッセージのハンドシェイク​

このチュートリアルでは、ws://localhost:10001 に対して WebSocket 接続を開きます。最初に受信したメッセージのみを契機として、セッションプロファイルの登録を送信します:

{
"session": {"configure": {"profile": {"name": "co.haply.inverse.tutorials:ws-remote-control"}}},
"inverse3": [{"device_id": "...", "commands": {"set_transform": {"transform": {"position": {"x": 0, "y": 0, "z": 0}}}}}]
}

以降のティックでは session を省略し、inverse3 コマンド配列のみを送信します。

ステップ3 — ティックごとの set_transform​

set_transform は部分的な変換を受け付けます。つまり、変更したいフィールドのみを指定できます。このチュートリアルでは位置のみを送信し、回転と拡大縮小はセッションの既定値のまま残ります(configure.mount で設定された値がある場合は、その値)。

{
"inverse3": [{"device_id": "...", "commands": {"set_transform": {"transform": {"position": {"x": 0.02, "y": 0.0, "z": 0.01}}}}}]
}

位置はティックごとに累積されます。約1 kHzで、サービスは毎ティック set_transform を再適用します(ゼロ次ホールド)。そのため、ワークスペースは最後に送信した位置に留まります。

set_transform と configure.mount の比較

どちらのコマンドも、セッションごとに永続的な変換を保持します。ただし、ワークスペース変換はパイプラインでマウントの上に位置します(マウントとワークスペースを参照)。チュートリアル09が各ティックで set_transform をストリーミングしている間は、新しい WebSocket フレームを受信するたびに、前回ストリーミングされたワークスペース変換が置き換えられます。そのため、同じデバイスに set_transform をストリーミングしている他のクライアントは、次のティックで上書きされます。configure.mount は独立しており、その値を保持し続けます。実際には、関心事ごとにチャネルを1つ選んでください:物理的なセットアップにはマウントを、ライブのナビゲーションには set_transform を使用します。

スレッドモデル(C++)​

C++ チュートリアルでは、位置の累積器を共有する2つのスレッドを使用しています:

スレッド役割
メインスレッドstd::getline(std::cin, ...) を読み取り、トークンを解析して、nav_vel[] と default_step を更新する
WebSocket コールバックスレッドnav_vel[] * dt を使って nav_pos[] を進め、set_transform をシリアライズして送信する

std::mutex nav_mutex は nav_pos[]、nav_vel[]、default_step を保護します。last_tick のタイムスタンプと first_ws_msg フラグは WS コールバックにローカルな値であり、ミューテックスは不要です。

ws.onmessage = [&](const std::string &) {
float pos_snap[3];
{
std::lock_guard<std::mutex> lk(nav_mutex);
for (int i = 0; i < 3; ++i) nav_pos[i] += nav_vel[i] * 0.001f; // mm/frame → m
for (int i = 0; i < 3; ++i) pos_snap[i] = nav_pos[i];
}
// build and send set_transform with pos_snap
};

ソース​

SDKインストーラーに同梱

チュートリアル09はSDKと一緒にローカルにインストールされています。サービスのインストールディレクトリ内の tutorials/09-haply-inverse-ws-remote-control/ を確認してください。

関連: set_transform コマンド · セッション — 最初のメッセージによるハンドシェイク · マウントとワークスペース · セレクター · チュートリアル08 — HTTP リモート設定 · チュートリアル07 — ベースとマウント