본문으로 바로 가기

시뮬레이션 채널

개요​

시뮬레이션 채널은 장치 상태를교환하고 세션 또는 장치 명령을 전송하는 데 사용되는 고빈도 양방향 WebSocket 채널입니다.

기본 URL: ws://localhost:10001

포트는 설정에서 변경할 수 있습니다.

핵심 규정 (중요):

  • 클라이언트가 연결하면 서비스는 전체 장치 목록이 포함된 초기 인벤토리 메시지를전송합니다.
  • 그 후 서버는 클라이언트로부터 받은 메시지 하나마다 정확히 하나의 상태 업데이트 메시지를 전송합니다.
  • 각 상태 업데이트에는 모든 장치의 상태(및 상태 정보)가 포함됩니다.
모듈

이 페이지는 시뮬레이션 채널의 핵심 프로토콜과 명령어를 설명합니다.

추가 모듈/기능은 자체 명령어 및/또는 상태 필드를 등록하여 시스템을 확장할 수 있습니다. 이에 대한 내용은 모듈 섹션에 별도로 설명되어 있습니다.

프로토콜 규칙​

클라이언트 메시지당 하나의 응답​

이 서비스는 클라이언트로부터 수신한 각 메시지에 대해, 모든 장치의 상태를 담은 하나의 상태 업데이트 메시지를 전송합니다.

이는 다음을 의미합니다:

  • 장치 상태의 최신 스냅샷이 필요한 경우, 반드시 어떤 명령(세션 명령, 프로브 명령 또는 장치 명령)을 전송해야 합니다.
  • 이 채널은 클라이언트 메시지에 의해 구동되는 “틱” 루프처럼 작동합니다.

힘을 가하지 않고 폴링하기 (프로브 명령)​

힘을 가하거나 시뮬레이션 매개변수를 변경하지 않고 상태 변화를 관찰하려면 프로브 명령을 사용하십시오:

  • 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라는 두 개의 별도 맵을 지원합니다. 두 맵은 서로 다른 목적을 가지고 있습니다:

지도용도전송 빈도
configure일회성 영구 설정: 프리셋, 베이스, 마운트, 필터, 모듈 설정한 번 전송하거나 변경 시 전송
commands틱마다 적용되는 비영구 항목: 힘, 위치, 토크매 틱마다 반드시 전송해야 함

commands해당 항목(set_cursor_force, set_cursor_position, set_angular_torques, set_angular_position)은 틱당 한 번 적용되며 저장되지 않습니다 -- 전송을 중단하면 효과가 즉시 사라지고 장치는 유휴 상태로 돌아갑니다. 다시 전송하지 않아도 틱 단위로 유지되어야 하는 항목에는 configure를 사용하십시오.

참고
set_transform이는 예외적인 경우입니다

set_transform은 commands에 속하지만 영구적인입니다 -- 서비스는 새로운 트랜스폼이 전송될 때까지 마지막으로 전송된 트랜스폼을 유지합니다. 매 틱마다 반드시 전송할 필요는 없습니다. 다만 목적이 카메라/장면 탐색이므로, 연속 동작 탐색에서는 틱 주기로 스트리밍하는 것(예: 사용자가 장면을 이동할 때 프레임마다 트랜스폼 하나씩 전송)이 예상되는 사용 패턴입니다.

좌표계​

Haply의 기본 좌표계는 Z축이 위쪽을 향하는 오른손 좌표계입니다.

좌표를 해석하고 반환하는 방식에 영향을 미치는 configure 항목은 두 가지입니다:

  • session.configure.basis : Haply 좌표와 애플리케이션 좌표계 간의 매핑을 변경합니다 (아래 기초 (참조).
  • inverse3[*].configure.preset : 작업 공간 원점을 설정하는 이름이 지정된 공장 구성을 선택합니다 (예: arm_front_centered는 (0, 0, 0)을 작업 공간 중앙에 배치합니다). 각 장치별 ‘Configure’ 섹션을 참조하십시오.

메시지 형식​

이 섹션에서는 상위 수준의 엔벨로프와 메시지 유형에 대해 설명합니다. 전체 예시는 이 문서의 뒷부분에서 확인할 수 있습니다.

장치 그룹​

메시지는 최상위 수준에서 장치 유형별로 그룹화됩니다(예: inverse3, verse_grip, wireless_verse_grip). 각 장치 유형 키는 장치별 객체의 배열에 매핑됩니다.

초기 인벤토리 (서버 → 클라이언트)​

WebSocket 연결이 설정된 직후 한 번 전송됩니다.

각 항목에는 다음이 포함됩니다:

  • device_id
  • config
  • state
  • status

참고: 예시: 초기 인벤토리 페이로드

상태 업데이트 (서버 → 클라이언트)​

클라이언트 메시지마다 한 번씩 전송됩니다.

각 항목에는 다음이 포함됩니다:

  • device_id
  • state
  • status

참고: 예시: 상태 업데이트 페이로드

세션 명령 엔벨로프 (클라이언트 → 서버)​

세션 명령은 현재 연결/세션에 적용되며, 장치별로 구분되지 않는 동작입니다.

{
"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-up 좌표계를 기준으로 표현합니다. X, Y, Z의 순열이며, 선택적으로 + 또는 - 기호를 앞에 붙일 수 있습니다. 예시는 다음과 같습니다:

  • XYZ, ZYX, +Y-Z+X, X-ZY
  • YZX는 사용자의 Y 축이 Haply의 오른쪽, Z 축이 Haply의 앞쪽, X 축이 Haply의 위쪽을 가리킨다는 뜻입니다.

왼손 좌표계 Z-up 예시(언리얼, 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 그립(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 그립 / 무선 Verse 그립

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": {...} }. 이는 의도된 설계입니다. command는 통합 래퍼를 사용하는 반면, snapshot은 변환을 직접 직렬화하기 때문입니다. snapshot 형식을 command 페이로드에 복사하지 않도록 주의하십시오.

댐핑​

균일 댐핑 및/또는 방향성 댐핑을 제어합니다. 최소 한 개의 필드는 반드시 포함되어야 합니다.

{
"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 확장 프로토콜의 일부이며, 보드 확장 통신 프로토콜을 구현하는 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
}
}
]
}

예시: 상태 업데이트 페이로드​

이 서비스는 수신하는 메시지마다 모든 장치의 상태를 포함하는 상태 업데이트 메시지를 하나씩 전송합니다.

장치의 상태를 확인하려면 사전에 메시지를 전송해야 합니다(예: 값이 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
}
}
]
}