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

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

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

ユースケース​

  • 実行中のデモをリアルタイムで調整します。 Haply HubでOrbデモを起動し、別のターミナルでこのチュートリアルを実行して、基本順列を入れ替えたり、ワークスペースプリセットを変更したり、マウント変換をわずかに移動させたりしてみてください。デモを停止することなく、Orbシーンの座標系が即座に変化します。
  • ユーザーごとのワークスペースのキャリブレーション。 ホストコンピュータ上でハプティックシーンを実行し、同じネットワーク上のオペレーターが mount-オフセット/回転/拡大縮小を設定し、仮想ワークスペースがユーザーのデスクと一致するようにします。
  • デバイス選択機能付きのオプションメニュー。 これらのHTTPヘルパーは、 GET /devices 照会する(参照: チュートリアル 00)、デバイスの一覧を表示し、インタラクティブなメニューを構築する(デバイスを選択して、その後再設定を行う)ために、セッションのWebSocketには一切触れないようにします。このチュートリアルでは、次のように尋ねます /sessions ab およびハードコーディング *inverse/0; より大きなものへの移行 /devices ただし、制御された選択は局所的な変更である。
  • スクリプトによる再構成。録画セッションが開始される前に、事前手順(ベース、プリセット、マウントの設定)を自動化し、各クライアントに設定を組み込む必要をなくします。

前提条件​

チュートリアル 08 では、すでに実行中のセッションの設定を変更します。これを行うには、任意のアクティブなハプティックセッション(別のチュートリアル、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回のREST呼び出しが送信されます。

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

プロファイル名のないセッションには、数値IDを介してのみアクセスできます。このIDは実行のたびに変化します。メインアプリケーションでは、最初のメッセージ送信時に session.configure.profile.name 呼び出します。その後、次のような安定したセレクタを --session :my_profile:0 すべての実行にわたって再利用できます。詳しくは セッション — プロフィール名.

キー割り当て​

味アクション
B基底の順列を適用する
Pワークスペースプリセットを切り替える
W / E / Rマウント編集モードの選択 — 位置 (mm) / 回転 (°) / 拡大率 (%)
← / →現在のモードで−X / +X ステップ進む
↑ / ↓現在のモードで +Y / −Y を1ステップ
Page Up / Page Down現在のモードで +Z / −Z を1ステップ
= / -3軸すべてで同時に均一なスケーリング(常に利用可能)
DeleteDELETE 基本設定 + プリセット + マウント — デバイスのデフォルト設定にリセット
Hヘルプを表示
Esc終了 (Ctrl+C (これも機能します)

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

このチュートリアルでは、3つのHTTPメソッドのみを使用しています。各呼び出しでは、標準の JSON-Envelope ({"ok": true, "data": {...}} 成功した場合、 {"ok": false, "error": "..."} (エラーの場合)および、3つのステータスコードのうちの1つ: 200 成功、 400 不正なリクエスト、 404 Selektorは何も見つかりませんでした。

方法Zweck使用されたパス
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-Keep-Alive について(リクエストごとのレイテンシを約 50 ms から約 5 ms に短縮):

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 指定された → ein GET /sessions/<SELECTOR>. 200 → 使用する; 404 → エラーを表示する。
  • 旗なし → GET /sessions (一覧) → プロファイル名付きのセッションを表示 → インデックスを照会 → 最終的なセレクタを作成(推奨 :profile:0、利用可能な場合は;そうでない場合は #id).

SELECTOR 以下の条件を満たすあらゆる形式を受け入れます Selektoren — セッションセレクター 次のように定義されている: :profile:instance, #id, :-1, :0、簡単なプロフィール名、または プロフィール名のワイルドカード-次のような 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​

設定の呼び出しは、1回の呼び出しにつき1つのデバイスに限定されます。このチュートリアルでは、ファミリーワイルドカードとインデックスを組み合わせたセレクタを使用しています:

/*inverse/0/config/<key>
  • *inverse Inverseシリーズのどのデバイスにも適合します(inverse3, inverse3x, minverse) — このチュートリアルは、具体的なモデルに関係なく、そのまま利用できます。
  • 0 このファミリー内ではインデックスは0から始まる――このチュートリアルでは、常に最初の逆関数についてのみ言及している。

別のデバイスに切り替えるには、文字列を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 wird vor インデックス作成に適用される — *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>'. aus で始まり、ステータス 1. その際、旧式の標準値を用いたキーボードループは開始されません。

完全な構文については、以下をご覧ください。 セレクター — デバイスセレクター. 固定値の代わりにデバイス選択メニューを作成するには、以下の方法でデバイスの数を数えてください。 GET /devices?session=<selector> auf (チュートリアル 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 und preset 互いに排他的である

いずれかのフィールドに対するPOSTリクエストが送信されると、デバイス上ではもう一方のフィールドが削除されます。このチュートリアルでは、この点については明示的に扱っていません。各POSTリクエストは独立しており、競合はサーバー側で解決されます。WebSocketの観点から見た同様のルールについては、チュートリアル07を参照してください。

DELETE-リセット — 3回の呼び出し​

reset 設定キーごとに1つの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 istは変換において単位クォータニオンです。このチュートリアルでは、回転をZ-Y-Xの順序(X軸周りのピッチ、Z軸周りのヨー、Y軸周りのロール — すべて度単位)で表されるイントリinsicオイラー3元組として保存し、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 sowie = / - ボタンを押し続けている間、マウント軸を移動させます; B und P 立ち上がりエッジで、ベースとプリセットの間を周期的に切り替わります。
  • 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 — 基本とマウント(WebSocket版)