メインコンテンツへスキップ

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 — ベースとマウント