シミュレーションチャンネル
概要
シミュレーションチャネルは、デバイスの状態を交換したり、セッションや デバイスコマンドを送信したりするために使用される、高頻度の双方向WebSocketチャネルです。
基本URL: ws://localhost:10001
ポートは設定で変更できます。
主要な規約(重要):
- クライアントが接続すると、サービスはすべてのデバイスのリストを含む初期インベントリメッセージを送信します。
- それ以降、サーバーはクライアントから受け取ったメッセージごとに、正確に1つの 状態更新メッセージを送信します。
- 各ステータス更新には、すべてのデバイスのステータス(およびステータス情報)が含まれます。
このページでは、シミュレーションチャネルの主要なプロトコルとコマンドについて解説しています。
追加のモジュールや機能は、独自のコマンドやステータスフィールドを登録することで、システムを拡張することができます。これらについては、モジュールセクションで別途説明されています。
プロトコルのルール
クライアントからのメッセージ1件につき1つの応答
このサービスは、クライアントから受信した各メッセージごとに、すべてのデバイスの状態をまとめた1つの状態更新メッセージを送信します。
これは、次のことを意味します:
- デバイスの状態に関する最新のスナップショットが必要な場合は、何らかの操作(セッションコマンド、プローブコマンド、またはデバイスコマンド)を必ず送信する必要があります。
- このチャンネルは、クライアントメッセージによって駆動される「ティック」ループのように動作します。
力を加えずにポーリングする(プローブコマンド)
力を加えたり、シミュレーションのパラメータを変更したりせずに状態の変化を観察するには、probe コマンドを使用してください:
inverse3の場合probe_position- Verse gripの場合は
probe_orientation
プローブコマンドにはコマンドデータは含まれておらず、デバイス情報の照会を強制的に実行することで、次回の状態更新時に位置・方向データが最新の状態になるようにします。
すでに制御コマンド(set_cursor_force, set_cursor_position など)を送信しているシミュレーションセッションは、プローブコマンドを送信する 必要ありません -- デバイスの状態は、常に応答フレームに含まれます。プローブは、デバイスを駆動することなく、デバイスの状態をポーリングする必要があります。 監視専用 これは、セッション(例:Haply Hub)用です。
構成と状態
- 初期在庫 メッセージには、デバイスの
config,state,statusが含まれます。 - 一般 ステータスの更新 メッセージには、デバイスの
stateわあstatusが含まれます。
構成を含むスナップショットが再び必要になった場合は session.force_render_full_stateを使用してください。
configureわあ commands
デバイスメッセージは configureわあ commandsという2つの別々のマップに対応しています。これら2つのマップは、それぞれ異なる目的を持っています:
| マップ | 用途 | 送信頻度 |
|---|---|---|
configure | 1回限りの永続設定:プリセット、ベース、マウント、フィルター、モジュール設定 | 一度送信するか、変更時に送信 |
commands | 各ティックごとに適用される非永続的な項目:力、位置、トルク | 各ティックごとに必ず送信する必要があります |
commandsの項目(set_cursor_force, set_cursor_position, set_angular_torques, set_angular_position)は 1ティックごとに1回適用され、保存されません -- 送信を停止すると、効果は即座に消え、デバイスはアイドル状態に戻ります。再度送信しなくても、ティックごとに維持される必要がある項目には、 configureを使用してください。
set_transformこれは例外的なケースですset_transformは commandsに属するが 恒久的なです -- サービスは、新しいトランスフォームが送信されるまで、最後に送信されたトランスフォームを維持します。
毎ティック送信する必要はありません。ただし、目的がカメラ/シーンのナビゲーションであるため、連続動作のナビゲーションでは、ティック周期でストリーミングすること(例:ユーザーがシーン内を移動する際、フレームごとに1つのトランスフォームを送信する)が想定される使用パターンです。
座標系
Haplyのデフォルトの座標系は、Z軸が上向きの右利き座標系です。
座標の解析および返却方法に影響を与えるconfigure項目は2つあります:
session.configure.basis:Haply 座標とアプリケーション座標系間のマッピングを変更します(以下 基礎 (参照)。inverse3[*].configure.preset: ワークスペースの原点を配置する、名前が指定された工場構成を選択します(例:arm_front_centeredは(0, 0, 0)をワークスペースの中央に配置します)。各デバイスの「Configure」セクションを参照してください。
メッセージ形式
このセクションでは、上位レベルのエンベロープとメッセージタイプについて説明します。完全な例については、このドキュメントの後半で紹介します。
デバイスグループ
メッセージは、最上位レベルでデバイスタイプごとにグループ化されます(例: inverse3, verse_grip, wireless_verse_grip). 各デバイスタイプのキーは、デバイスごとのオブジェクトの配列にマッピングされます。
初期インベントリ(サーバー → クライアント)
WebSocket接続の設定直後に1回送信されます。
各項目には、以下の内容が含まれます:
device_idconfigstatestatus
ステータスの更新(サーバー → クライアント)
クライアントメッセージごとに1回送信されます。
各項目には、以下の内容が含まれます:
device_idstatestatus
セッションコマンドエンベロープ(クライアント→サーバー)
セッションコマンドは、現在の接続/セッションに適用されるものであり、デバイスごとに区別される動作ではありません。
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
デバイスコマンドエンベロープ(クライアント → サーバー)
デバイスコマンドは、デバイスタイプキーの下に配列として送信されるため、1つのメッセージで1つ以上のデバイスにコマンドを送信することができます。各デバイスエントリは configure マップと commands すべてのマップに対応しています。
{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}
1つのメッセージに複数の項目を含めることで、同じ種類の複数のデバイスにコマンドを送信することができます。
commandsは辞書であり、特定のデバイスに対して複数のコマンドを含めることはできますが、コマンドの種類ごとに1つしか含めることはできません。
execute フィールド
すべての command または configure 項目は任意です。 "execute" ブール値(デフォルト値 true)を受け取ります。 "execute": falseに設定すると、項目は解析されるだけで、適用はされません。これは、すべてのフィールドを常に出力するリフレクションベースのシリアライザ(例:Unity JsonUtility)に役立ちます。
"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
コマンドの参照
セッション
全体の状態を強制的にレンダリングする
すべてのデバイスの状態と構成のスナップショットをリクエストします。
{
"session": {
"force_render_full_state": {}
}
}
セッションの設定
セッションレベルの永続的な設定は session.configureを通じて送信されます。これはデバイスレベルの configure マップのパターンをそのまま踏襲します。
プロフィール
セッションプロファイルの名前を設定します。この名前は、シミュレーションを識別し、アプリごとのデバイス設定を維持するために、Haply Hubで使用されます。
{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
Basis(セッションレベル)
セッション全体のベースマッピングを設定します。ベースマッピングとは、Haply の座標をアプリケーションの座標系に変換する方法を定義するものです。このマッピングを設定すると、以降のすべてのデバイス状態が当該ベースで返され、送信されるすべての値も当該ベースに基づいて解釈されます。
順列文字列は、Haplyの右手座標系/Z軸上向きの座標系に基づいて表現されます。 X, Y, Zの順列であり、必要に応じて + または - 記号を先頭に付けることができます。例は以下の通りです:
XYZ,ZYX,+Y-Z+X,X-ZYYZXはユーザーのY軸がHaplyの右側にあり、Z軸がハプリの前方にあり、Xこれは、軸がハプリの上方を指していることを意味します。
左利き座標系(Z軸上向き)の例(Unreal、 X-YZ):
{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
session.set_basisからの移行軸符号の規則はレガシーな命令です session.set_basisで新しいコマンド session.configure.basisに変更されました。従来のコマンドで機能していた順列が、新しいコマンドでは逆の変換を生成する可能性があります。必要に応じて、軸の符号を反転させてください(例: X-ZYは XZ-Yとなります)。
すべてのデバイスコマンド
プロービング(すべてのデバイス)
力やその他のシミュレーション変更を適用せずに、最新の位置・方向情報を取得するには、プロービングコマンドを使用します。
- inverse3:
probe_position - ヴァース・グリップ(
verse_grip,wireless_verse_grip):probe_orientation
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"probe_position": {}
}
}
],
"verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
],
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
]
}
変換設定(すべてのデバイス)
デバイスのワークスペース変換(デバイス空間からアプリケーション空間へ)を設定します。
他の commands 項目とは異なり set_transformは 恒久的なです。つまり、サービスは新しい値が送信されるまで、最後に送信された値を維持します。毎ティック送信する必要はありませんが、目的がカメラやシーンのナビゲーションであるため、連続的な動きではティック単位でストリーミングするのが一般的な使用パターンです(例:ユーザーがシーン内を移動している間、フレームごとに1つの変換データを送信する)。
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_transform": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
Verse grip および Wireless Verse grip デバイスについては、変換の rotation コンポーネントにのみ効果が現れます。このグリップは方向情報のみを参照するため、 positionイナ scale調整または移動する対象がありません。 position および scale このフィールドは、Inverse3 スキーマとの整合性を保つためだけに許可されており(スナップショットにもそのまま返されます)、同一値(position = {0,0,0}, scale = {1,1,1})とするのが標準的な方法です。
Inverse3 デバイスの設定
構成項目は configure マップに送信します。この項目は永続的なものであるため、一度だけ送信するか、値が変更されたときに送信してください。
プリセット
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
}
}
]
}
利用可能な値: device_defaults, arm_front, arm_front_centered, led_front, led_front_centered, custom.
Basis(デバイスレベル)
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}
マウント
デバイスマウントの変換を設定します。 mount 内部の transform ラッパーにご注意ください。
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"mount": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
コマンド 側(クライアントからサーバーへ)は変換をラップします: "mount": { "transform": { ... } }.
スナップショット 側(サーバーからクライアントへ)はフラットな構造になっています: "mount": { "position": {...}, "rotation": {...}, "scale": {...} }.
これは意図的な設計です。command は統合ラッパーを使用するのに対し、snapshot は変換を直接シリアライズするためです。snapshot 形式を command のペイロードにコピーしないようご注意ください。
ダンピング
均一な減衰および/または方向性のある減衰を制御します。少なくとも1つのフィールドは必ず含める必要があります。
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}
方向性ダンピングを設定したり、両方を同時に設定したりすることもできます:
"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }
ヒム・ゲート
ヒムゲートの減衰ゲイン(0.0~1.0)を設定します。
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}
Inverse3 コマンド
inverse3 デバイスにコマンドを送信するには inverse3 キーの下にある一致する device_idを含む項目が含まれます。このコマンドはティックごとに適用され、有効な状態を維持するには、ティックごとに再送信する必要があります。
1つのデバイスにコマンドを送信する:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
1つのメッセージで複数のデバイスにコマンドを送信する:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
},
{
"device_id": "049E",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
カーソルの位置設定
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"position": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
カーソルの強度設定
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
角度位置の設定
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"angles": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
角度トルク設定
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torques": {
"torques": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Verse Gripの設定
Verse Grip および Wireless Verse Grip デバイスは、 preset, basis, mountについて Inverse3で使用されているものと同じ configure キーに対応しています。
拡張Verse Grip
set_extension_data このコマンドは、Verse GRIP拡張プロトコルの一部であり、ボード拡張通信プロトコルを実装するGRIPバージョンで使用されます。
グリップ拡張データの設定
サポートされるデータ長:
- アップストリーム(クライアント → デバイス):最大20バイト。
- ダウンストリーム(デバイス → クライアント):最大12バイトで、
state.extension_data「State Update」メッセージに戻ります。
データ仕様:
- 配列の長さ:20バイト
- 値の範囲:各値は0~255
{
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"set_extension_data": {
"extension_data": [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
}
}
}
]
}
例
例:初期デバイス一覧のペイロード
WebSocket接続時、サービスはすべてのデバイス一覧を含むメッセージを送信します。最初のメッセージは以下の通りです。 JSON 形式に従います:
{
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"port": "COM13",
"device_info": {
"major_version": 7,
"minor_version": 1,
"id": "04BA",
"device_type": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 1
},
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": {
"enabled": true
},
"home_return": {
"enabled": false
},
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": 0 }
},
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"config": {
"type": "verse_grip",
"port": "COM3",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"config": {
"type": "wireless_verse_grip",
"port": "COM6",
"major_version": 1,
"minor_version": 4,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
]
}
例:ステータス更新のペイロード
このサービスは、受信するメッセージごとに、すべてのデバイスの状態を含む状態更新メッセージを1つずつ送信します。
デバイスの状態を確認したい場合は、事前にメッセージを送信する必要があります(例:値が0であっても、プローブコマンドや力値など)。力を加えず、デバイスを入力ソース(例:位置追跡)として使用する場合、これは特に重要です。
ステータス更新メッセージは以下の通りです。 JSON 形式に従います:
{
"inverse3": [
{
"device_id": "04BA",
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
],
"custom_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"extension_data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"ready": true
}
}
]
}