メインコンテンツへ移動

08. リモートセッション設定ツール

別の場所(別のアプリケーション、Unityのシーン、Haply Hubのデモなど)ですでに実行中のセッションを、そのデバイスにREST HTTPリクエストを送信することで再構成します。 このチュートリアルでは WebSocket は一切開かれません。別のアプリケーションがハプティックフィードバックを出力し続ける間、データベース、ワークスペースのプリセット、またはマウント変換を変更するために、GET、POST、DELETE リクエストのみを実行します。

ユースケース​

  • 実行中のデモをリアルタイムで調整します。 Haply HubOrbのデモを起動し、別のターミナルでこのチュートリアルを実行して、基本の順列を変更したり、ワークスペースのプリセットを編集したり、マウント変換を微調整したりしてください。デモを停止することなく、Orbの座標原点が即座に移動します。
  • ユーザーごとのワークスペースのキャリブレーション。 メインマシンでハプティックシーンをアクティブにしたまま、同じネットワーク上のオペレーターに、オフセット、回転、またはスケールの mount 仮想ワークスペースがユーザーのデスクトップと位置が一致するようにするため。
  • デバイス選択メニュー。 同じHTTPユーティリティ関数を使用して、 GET /devices (参照: チュートリアル 00) デバイスを列挙し、インタラクティブなメニューを構築するためです。つまり、デバイスを選択して再設定を行うことができますが、セッションのWebSocketには一切触れません。このチュートリアルでは、次のような質問をします。 /sessions et fixe *inverse/0 コード内では、この選択を、次によって制御されるセレクタに置き換える /devices 必要なのは、局所的な変更だけだ。
  • スクリプトによる設定。セッションの録画が開始される前に、事前準備(データベース、プリセット、編集の設定)を自動化し、各クライアントに設定を個別に組み込む必要をなくします。

前提条件​

チュートリアル08では、すでに実行中のセッションの設定を変更します。これを行うには、アクティブなハプティックセッションが1つ必要です。別のチュートリアル、Unityのシーン、あるいはHaply Hubのデモなど、どれでも構いません。

セッションを開始する最も手っ取り早い方法

Haply Hubを開き、Orb のデモを起動してから、それを直接ターゲットに設定してください:

./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

「Orb」シーンでは、デバイスのワークスペースに球体が表示されます。ベースやプリセットをループ再生したり、チュートリアル08に従ってマウントの変形を微調整したりすると、Orbの座標原点がリアルタイムで視覚的に移動します。

利用状況​

# Pick a session interactively (lists every session the service knows)
./08-haply-inverse-http-remote-config
python 08-haply-inverse-http-remote-config.py

# Target the Haply Hub Orb demo directly
./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

# Target one directly by selector
./08-haply-inverse-http-remote-config --session :my_profile:0
python 08-haply-inverse-http-remote-config.py --session "#42"

# Or by a wildcard profile pattern (first match) — handy when the exact profile is unknown
./08-haply-inverse-http-remote-config --session "co.haply.hub::*:0"

# Pick a specific device on the session (default is *inverse/0)
./08-haply-inverse-http-remote-config --device 'inverse3/#A12' # by exact id
./08-haply-inverse-http-remote-config --device '*inverse:left/0' # first left-handed inverse
./08-haply-inverse-http-remote-config --device '*inverse:right/0' # first right-handed inverse
./08-haply-inverse-http-remote-config --device '*verse_grip/0' # first grip-family device

起動時、チュートリアルではセッションのベース、プリセット、および現在の構成が表示され、その後、キー入力を待ちます。キーを1回押すごとに、1つのREST呼び出しが送信されます。

シミュレーションでプロファイル名を設定してください

プロファイル名のないセッションは、実行のたびに変化する数値識別子によってのみ特定できます。次の呼び出しを行ってください session.configure.profile.name メインアプリで最初のメッセージを受け取った時点で、次のような安定したセレクタを再利用できるようになります: --session :my_profile:0 実行のたびに。参照 セッション — プロフィール名.

キーボードショートカット​

その通りアクション
B基本的な順列を閲覧する
Pワークスペースのプリセットを閲覧する
W / E / R編集モードの選択 — 位置 (mm) / 回転 (°) / スケール (%)
← / →現在のモードで −X / +X 進む
↑ / ↓現在のモードで +Y / −Y だけ進む
Page Up / Page Down現在のモードで +Z / −Z 方向に進む
= / -3軸すべてに対して均一なスケーリング(常に利用可能)
DeleteDELETE 基本設定 + プリセット + 設定 — デバイスのデフォルト設定に戻す
Hヘルプを表示
Escクィッター (Ctrl+C (これも動作します)

HTTPメソッド — GET、POST、DELETE​

このチュートリアルでは、3つのHTTPメソッドのみを使用しています。各リクエストは、標準のJSONエンベロープ ({"ok": true, "data": {...}} 成功した場合、 {"ok": false, "error": "..."} (失敗した場合)および、以下の3つのステータスコードのいずれか: 200 成功、 400 不正なリクエスト、 404 セレクタに一致する結果はありません。

動詞役割使用されたパス
GET現在の状態を確認する — セッション一覧、特定のセッションの検索、現在の設定値/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POST設定値の置換 — 本文はJSON形式です/<device_selector>/config/{basis,preset,mount}?session=...
DELETE設定値をデバイスのデフォルト値に戻す/<device_selector>/config/{basis,preset,mount}?session=...

HTTPユーティリティ関数​

3つの動詞の周りに薄いカプセル化の層を設けることで、チュートリアルの残りの部分が業務ロジックのように読み取れるようにします:

Pythonの活用 requests.Session() HTTPのキープアライブについて(リクエストごとのレイテンシを約50ミリ秒から約5ミリ秒に短縮):

http = requests.Session()

def api_get(path):
r = http.get(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def api_post(path, body):
r = http.post(f"{BASE_URL}{path}", json=body, timeout=3)
return r.json() if r.status_code == 200 else None

def api_delete(path):
r = http.delete(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def session_url(endpoint):
return f"{endpoint}?session={session_selector}"

セッションの紹介 — GET /sessions​

その挙動は、以下の要因によって決まります。 --session :

  • --session SELECTOR fourni → 1つだけ GET /sessions/<SELECTOR>. 200 → それを使う; 404 → エラーを報告して停止する。
  • オプションなし → GET /sessions (リスト) → プロファイル名付きのセッションを表示 → インデックスを要求 → 最終的なセレクタを構築(優先して :profile:0 利用可能な場合はそれを使用し、そうでない場合は #id).

SELECTOR で定義されているすべての形式を受け入れます セレクタ — セッションセレクタ : :profile:instance, #id, :-1, :0、シンプルなプロフィール名、またはモチーフ プロフィール名にワイルドカードを含む場合 comme co.haply.hub::*:0. このチュートリアルでは、文字列をそのまま渡します。その解析はサービス側で行われます。

def discover_session(session_arg):
global session_selector

if session_arg:
# Direct lookup (e.g. ":my_profile:0", "#42", ":-1")
if api_get(f"/sessions/{session_arg}") is None:
return False
session_selector = session_arg
return True

# Otherwise: list and pick
data = api_get("/sessions")
sessions = data.get("data", {}).get("sessions", [])
for i, s in enumerate(sessions):
name = s.get("config", {}).get("profile", {}).get("name", "default")
print(f" [{i}] session #{s['session_id']} profile={name}")

picked = sessions[int(input("Pick session index: "))]
name = picked.get("config", {}).get("profile", {}).get("name", "")
# Prefer the profile selector — it survives restarts; id doesn't
session_selector = (f":{name}:0" if name and name != "default"
else f"#{picked['session_id']}")
return True

デバイスセレクター — *inverse/0​

各設定呼び出しは、特定のデバイスに対して行われます。このチュートリアルでは、ファミリー+インデックスのワイルドカードセレクタを使用しています:

/*inverse/0/config/<key>
  • *inverse Inverseシリーズのすべてのデバイスに対応しています(inverse3, inverse3x, minverse) — このチュートリアルは、具体的なモデルがどれであっても、変更を加えることなくそのまま利用できます。
  • 0 このファミリーにおけるインデックス(0から始まる)です。なお、このチュートリアルでは最初のInverseについてのみ扱っています。

ターゲットを変更するには、文字列を1か所だけ変更すれば済みます。次のようになど --device (その道はすでに整備されている):

/verse_grip/0/config/basis?session=... # --device verse_grip/0
/*verse_grip/0/config/basis?session=... # --device '*verse_grip/0' — first grip, any kind
/inverse3/A14/config/mount?session=... # --device 'inverse3/#A14'
/*inverse:left/0/config/basis?session=... # --device '*inverse:left/0' — first left-handed inverse
/*inverse:right/0/config/basis?session=... # --device '*inverse:right/0' — first right-handed inverse

クラス :left / :right が適用される avant インデックス化 — *inverse:left/0 「0次逆数」を指す ゴーシェ », ではなく「0番目の逆数、できれば左巻きであることを願う」。参照 セレクタ — クラスフィルターはインデックス作成の前に解決される.

チュートリアル08は、対象のデバイスが検出できない場合に終了します

このチュートリアルでは、 GET …/config/basis 起動時。セッションに、 以下の条件に一致するデバイスが含まれていない場合 --device (例: --device '*inverse:left/0' (右利き専用 の設定の場合)、サービスは次のように応答します 404 no device matches selector '<sel>' ; チュートリアルには次のように表示されます Device '<sel>' not found in session '<sel>'. そして、ステータスで終わります 1. 古いデフォルト値を使用してキーボードループが起動されることはありません。

ご覧ください セレクター — 機器用セレクター 文法の詳細についてはこちらをご覧ください。ハードコーディングの代わりにデバイス選択メニューを作成するには、デバイスを以下のように列挙してください。 GET /devices?session=<selector> (チュートリアル 00) を入力し、 device_id 設定パスから選択します。

設定 POST — 基本、プリセット、マウント​

3つの設定キー、同じリクエスト構造、異なる本文のスキーマ。各POSTリクエストは 200 その結果得られた値を data、または 404 セッションまたはデバイスの選択項目に一致するものがない場合。

基礎​

POST /*inverse/0/config/basis?session=:my_profile:0
Content-Type: application/json

{"permutation": "XZY"}

回答: {"ok": true, "data": {"permutation": "XZY"}}

def post_basis():
perm, _ = BASIS_OPTIONS[basis_index]
api_post(session_url("/inverse3/0/config/basis"), {"permutation": perm})

プリセット​

POST /*inverse/0/config/preset?session=:my_profile:0
Content-Type: application/json

{"preset": "arm_front_centered"}

回答: {"ok": true, "data": {"preset": "arm_front_centered"}}

def post_preset():
preset = PRESET_OPTIONS[preset_index]
api_post(session_url("/inverse3/0/config/preset"), {"preset": preset})

マウント​

POST /*inverse/0/config/mount?session=:my_profile:0
Content-Type: application/json

{
"transform": {
"position": {"x": 0.02, "y": 0.0, "z": 0.0},
"rotation": {"w": 0.966, "x": 0.0, "y": 0.259, "z": 0.0},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0}
}
}

回答: {"ok": true, "data": {"transform": { ... }}} — 正規化後の実際の変換結果を返します。

def post_mount():
body = {
"transform": {
"position": {"x": mount_pos[0], "y": mount_pos[1], "z": mount_pos[2]},
"rotation": quat_from_euler_deg(*mount_rot),
"scale": {"x": mount_scale[0], "y": mount_scale[1], "z": mount_scale[2]},
}
}
api_post(session_url("/inverse3/0/config/mount"), body)
mount et preset 互いに排他的な

2つのキーのいずれかに対してPOSTリクエストを送信すると、デバイス上でもう一方のキーが消去されます。このチュートリアルでは、この処理について明示的には説明していません。各POSTリクエストは独立しており、競合はサーバー側で解決されます。WebSocket側における同様のルールについては、チュートリアル07を参照してください。

DELETEによるリセット — 3回の呼び出し​

reset 設定キーごとにDELETEを実行します。それぞれが 200 デフォルト値が復元された data.

def reset_all():
api_delete(session_url("/inverse3/0/config/basis"))
api_delete(session_url("/inverse3/0/config/preset"))
api_delete(session_url("/inverse3/0/config/mount"))

マウントの回転構成​

transform.rotation ネットワークを介して送信されるユニタリ四元数です。このチュートリアルでは、回転を固有オイラー角の3元組 Z-Y-X(X軸周りのピッチ、Z軸周りのヨー、Y軸周りのロール — いずれも度単位)として保存し、POSTのたびに四元数を再構成します。

def quat_from_euler_deg(pitch_x, yaw_z, roll_y):
"""Hamilton quaternion for q = q_z * q_y * q_x (apply X, then Y, then Z)."""
hx, hy, hz = (math.radians(a) * 0.5 for a in (pitch_x, roll_y, yaw_z))
cx, sx = math.cos(hx), math.sin(hx)
cy, sy = math.cos(hy), math.sin(hy)
cz, sz = math.cos(hz), math.sin(hz)
return {
"w": cz*cy*cx + sz*sy*sx,
"x": cz*cy*sx - sz*sy*cx,
"y": cz*sy*cx + sz*cy*sx,
"z": sz*cy*cx - cz*sy*sx,
}
四元数の規約

右巻き座標系で、スカラー部分が先頭にあるユニタリ・ハミルトン四元数(w) — サービスの他の部分と同じ規約です。詳しくは quaternion. 構成の順序は 本質的なZ-Y-X (q = q_z * q_y * q_x) :まずX軸周りのピッチ角を適用し、次にY軸周りのロール角を適用し、さらにZ軸周りのヨー角を適用します。

このチュートリアルでは、各状態行において、オイラー3要素の横に導出されたクォータニオンが表示されるため、デバイスが回転する前に合成を確認することができます。ローカルなオイラー状態は (0, 0, 0)、セッションにすでにどのような状態があろうとも――最初の mount POST 既存の値を上書きします。

入力モデル(概要)​

HTTPの統合こそが本質であり、キーボード操作の利便性は二の次です。意図的に採用した2つのショートカット:

  • Python パッケージを利用する keyboard — マルチプラットフォーム対応で、長押しキーの繰り返しをネイティブに処理します。矢印キー、 Page Up / Page Down et = / - 固定されている間は、取り付け軸を移動させます; B et P 立ち上がりエッジで、basisおよびpresetの値を順次切り替える。
  • C++ 活用する std::getline(std::cin, ...) およびコンパクトなトークン文法(x+20, sx-5, u+10) — 連続的な調整にはあまり使い勝手が良くないが、以下の手段に頼ることなく持ち運ぶことができる #ifdef コンソール用APIについては、各プラットフォームごとに固有のものです。

出典​

SDKのインストーラーに含まれています

チュートリアル08もSDKとともにローカルにインストールされています。詳しくは tutorials/08-haply-inverse-http-remote-config/ サービスのインストールディレクトリ内。

関連項目: セッション — リモート制御·セレクタ·デバイスの設定·ベースの切り替え·マウントとワークスペース·JSONの規約·チュートリアル 00 — デバイス一覧·チュートリアル 07 — Basisとマウント(WebSocket版)