시뮬레이션 채널
개요
시뮬레이션 채널은장치의 상태를주고받고,세션 또는 장치에 대한명령을 전송하는 데 사용되는 고주파수 양방향 WebSocket 채널입니다.
기본 URL: ws://localhost:10001
포트는 설정에서 변경할 수 있습니다.
핵심 사항 (중요):
- 클라이언트가 연결되면, 서비스는 모든 장치의 전체 목록이 포함된 초기 인벤토리 메시지를 전송합니다.
- 그 후, 서버는 클라이언트로부터 수신한 메시지 하나마다 정확히 하나의 State Update 메시지를 전송합니다.
- 각 ‘상태 업데이트’에는모든 장치의상태(및 상태 정보)가 포함됩니다.
이 페이지에서는시뮬레이션 채널의 핵심 프로토콜과 명령어에 대해설명합니다.
추가 모듈이나 기능을 통해 고유한 명령어나 상태 필드를 등록하여 시스템을 확장할 수 있습니다. 이에 대해서는 ‘모듈’ 섹션에서 별도로 설명하고 있습니다.
프로토콜 규칙
클라이언트 메시지당 하나의 응답
이 서비스는 클라이언트로부터 수신한각 메시지에대해,모든장치의 상태를 포함하는 하나의 State Update 메시지를 전송합니다.
이것은 다음과 같은 의미입니다:
- 장치 상태에 대한 최신 스냅샷이 필요한 경우,어떤 형태의 메시지(세션 명령, 프로브 명령 또는 장치 명령)를 전송해야 합니다.
- 이 채널은 클라이언트 메시지에 의해 구동되는 ‘틱’ 루프와 같은 방식으로 작동합니다.
전력을 가하지 않고 폴링하기 (프로브 명령)
힘을 가하거나 시뮬레이션 매개변수를 변경하지 않고 상태 변화를 모니터링하려면 프로브 명령을 사용하십시오:
inverse3~용probe_position- Verse Grip용
probe_orientation
프로브 명령어에는 명령어 데이터가 포함되지 않으며, 단지 장치 정보 조회를 강제할 뿐입니다. 이를 통해 다음 상태 업데이트(State Update) 시 위치 및 방향 데이터가 최신 상태로 갱신됩니다.
제어 명령어(set_cursor_force、set_cursor_position (등)을 이미 전송한 시뮬레이션 세션에서는 프로브 명령을 전송할 필요는 없습니다. 장치의 상태는 항상 응답 프레임에 포함됩니다. 프로브는 장치를 작동시키지 않고도 장치의 상태를 폴링해야 하므로,감시 전용세션(예: Haply Hub)용입니다.
설정 및 상태
- 초기 재고 메시지에는 기기의
config、state、status이 포함됩니다. - 일반적인 주별 현황 메시지에는 기기의
state그리고status이 포함됩니다.
설정이 포함된 스냅샷을 다시 생성해야 하는 경우,session.force_render_full_state 를 사용해 주십시오.
configure 그리고 commands 차이
장치 메시지는,configure 그리고 commands 두 개의 별도 지도를 지원합니다. 이 두 지도는 서로 다른 목적으로 사용됩니다:
| 지도 | 목적 | 발송 빈도 |
|---|---|---|
configure | 한 번만 설정하면 영구적으로 유지되는 구성: 프리셋, 기준(basis), 장착(mount), 필터, 모듈 설정 | 1회, 또는 변경 시 전송 |
commands | 틱별 비영속적 명령: 힘, 위치, 토크 | 매 틱마다 전송해야 함 |
commands 내의 항목(set_cursor_force、set_cursor_position、set_angular_torques、set_angular_position)는,틱마다 한 번씩 적용되며, 저장되지는 않습니다.. 전송을 중지하면 효과가 즉시 사라지고 기기는 대기 상태로 돌아갑니다. 재전송하지 않아도 틱을 넘겨 유지해야 하는 항목으로는,configure 를 사용해 주십시오.
set_transform 이는 특별한 경우입니다set_transform 는 commands ... 아래에 놓이게 되지만,영구적인입니다. 서비스는 새로운 트랜스폼을 전송할 때까지 마지막으로 전송된 트랜스폼을 유지합니다. 매 틱마다 전송합니다.필요하지 않습니다. 그렇긴 하지만, 그 목적은 카메라/씬 내 탐색이므로, 틱 레이트로 스트리밍하는 것(예: 사용자가 씬을 팬할 때 프레임마다 하나의 트랜스폼을 전송하는 방식)이 연속적인 움직임 탐색에서 예상되는 사용 패턴입니다.
좌표계
Haply 는 기본적으로 Z-Up의 오른손 좌표계를 사용합니다.
다음 두 가지 configure 항목이 좌표의 해석 방식과 반환 방식에 영향을 미칩니다:
session.configure.basis: Haply 의 좌표와 애플리케이션 좌표계 간의 대응 관계를 변경합니다(아래의 기초 (참조).inverse3[*].configure.preset: 워크스페이스의 시작점을 설정할, 이름이 지정된 공장 출하 시 구성을 선택합니다(예:arm_front_centered는(0, 0, 0)(워크스페이스 중앙에 배치합니다). 각 기기의 ‘Configure’ 섹션을 참조하십시오.
메시지 형식
이 섹션에서는 메시지의 상위 수준 엔벨로프와 메시지 유형에 대해 설명합니다. 전체 예제는 이 문서의 뒷부분에 나와 있습니다.
장치 그룹
메시지는 최상위 수준에서 기기 유형별로 그룹화됩니다(예: inverse3、verse_grip、wireless_verse_grip). 각 장치 유형의 키는 해당 장치별 객체 배열에 해당합니다.
초기 인벤토리 (서버 → 클라이언트)
WebSocket 연결이 수립된 직후 한 번 전송됩니다.
각 항목에는 다음 내용이 포함됩니다:
device_idconfigstatestatus
참조: 예: 초기 인벤토리의 페이로드
상태 업데이트 (서버 → 클라이언트)
클라이언트 메시지마다 한 번씩 전송됩니다.
각 항목에는 다음 내용이 포함됩니다:
device_idstatestatus
세션 명령의 엔벨로프 (클라이언트 → 서버)
세션 명령어는 현재 연결/세션에 적용되는 작업이며,특정 장치에 국한되지 않습니다.
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
디바이스 명령의 엔벨로프 (클라이언트 → 서버)
장치 명령은 장치 유형 키 아래에 배열 형태로 전송되며, 하나의 메시지로 하나 이상의 장치에 명령을 보낼 수 있습니다. 각 장치의 항목은,configure 지도와 commands 두 가지 지도 모두를 지원합니다.
{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}
동일한 종류의 여러 장치에 명령을 전송하기 위해, 하나의 메시지에 여러 항목을 포함시킬 수 있습니다.
commands 이는 사전이며, 하나의 장치에 대해 여러 개의 명령을 포함할 수 있습니다. 단, 명령 유형별로 하나만 포함할 수 있습니다.
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"
}
}
}
}
기본 (세션 수준)
세션 전체에 대한 기준 매핑을 설정합니다. 기준 매핑은Haply 의 좌표가 애플리케이션 좌표계로 어떻게 변환되는지를 나타냅니다. 설정 후에는 모든 디바이스 상태가 해당 기준에 따라 반환되며, 전송되는 모든 값도 해당 기준에 따라 해석됩니다.
순열 문자열은Haply 에 제시된 우측계·Z축이 위를 향하는 좌표계를 기준으로 표현됩니다.X、Y、Z 의 순열로, 필요에 따라 + 또는 - 을 앞에 붙입니다. 예:
XYZ、ZYX、+Y-Z+X、X-ZYYZX는, 애플리케이션의YHaply 의 오른쪽 방향,ZHaply 의 앞쪽 방향으로,X이는 Haply 보다 위쪽을 가리킨다는 것을 의미합니다.
왼손 좌표계·Z축이 위를 향하는 예시 (언리얼,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(
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 는영구적인입니다. 서비스는 다음 값을 전송할 때까지 마지막으로 전송된 값을 유지합니다. 매 틱마다 전송할 필요는 없지만, 카메라나 씬 내비게이션과 같은 용도인 만큼, 연속적인 움직임에 대해서는 틱 단위로 스트리밍하는 것이 일반적인 사용 방식입니다(예: 사용자가 씬을 팬하는 동안 프레임마다 변환 값 하나를 전송하는 경우).
{
"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。
베이스(디바이스 레벨)
{
"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": {...} }。
이는 의도된 설계입니다. 명령어는 통합된 래퍼를 사용하며, 스냅샷은 변환 결과를 직접 직렬화합니다. 스냅샷의 형식을 명령어의 페이로드에 복사하지 않도록 주의하십시오.
감쇠
균일한 감쇠 및/또는 방향별 감쇠를 제어합니다. 적어도 하나의 필드를 지정해야 합니다.
{
"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 ...를 포함하는 항목을 포함합니다. 이 명령은 틱 단위로 유효하며, 유효한 상태를 유지하려면 매 틱마다 다시 전송해야 합니다.
하나의 기기에 명령을 전송할 경우:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"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
}
}
}
},
{
"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 확장 프로토콜의 일부입니다.
그립 확장 데이터 설정
지원되는 데이터 길이:
- 업스트림(클라이언트 → 디바이스): 최대 20바이트.
- 다운스트림(디바이스 → 클라이언트): 최대 12바이트.
state.extension_dataState 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
}
}
]
}
예: 상태 업데이트 페이로드
이 서비스는 수신된 메시지마다 모든 장치의 상태를 포함하는 상태 업데이트 메시지를 하나씩 전송합니다.
장치의 상태를 확인하고 싶다면, 사전에 메시지를 전송해 두어야 합니다(예: 값이 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
}
}
]
}