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
操作方法
- Python
- C++
キーを押し続けると連続して移動します。離すと停止します。
| キー | 操作 |
|---|---|
→ / ← | +X / −X |
↑ / ↓ | +Y / −Y |
Page Up / Page Down | +Z / −Z |
= / - | ナビゲーションの速度を上げる/下げる |
0 | ワークスペースを原点にリセットする |
H | ヘルプを表示 |
Esc | 終了(Ctrl+C (それでも終了できます) |
行単位で入力してください。コマンドを入力してから、ENTER キーを押してください。
| コマンド | 操作 |
|---|---|
x+[N] / x-[N] | X軸の速度を ±N mm/フレームに設定(x+ (のみの場合は、現在の既定速度を使用) |
y+[N] / y-[N] | Y軸の速度を ±N mm/フレームに設定 |
z+[N] / z-[N] | Z軸の速度を ±N mm/フレームに設定 |
s+[N] / s-[N] | 既定速度を ±N mm/フレーム分だけ調整する |
stop | すべての速度をゼロにする |
reset | 速度をゼロにして、原点に戻す |
h | ヘルプを表示 |
Ctrl+C(または Ctrl+D / EOF)を押すと終了します。
仕組み
ステップ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
};
ソース
チュートリアル09は、SDKとともにローカルにインストールされています。サービスのインストールディレクトリ内の tutorials/09-haply-inverse-ws-remote-control/ ご確認ください。
関連: set_transform コマンド ・ セッション — 最初のメッセージによるハンドシェイク ・ マウントとワークスペース ・ セレクター ・ チュートリアル08 — HTTPのリモート設定 ・ チュートリアル07 — ベースとマウント