WebSocket 프로토콜
시뮬레이션 채널은 포트 10001의 WebSocket 연결입니다. 이 채널은 애플리케이션과 서비스 사이의 실시간 명령/상태 루프를 전달합니다.
핵심 규칙: 메시지 하나당 응답 하나
서비스는 클라이언트로부터 수신한 각 메시지에 대해 정확히 하나의 상태 업데이트를 전송합니다:
폴링도, 구독도, 대역 외 푸시도 없습니다. 업데이트 주기는 전적으로 클라이언트의 전송 빈도에 의해 결정됩니다.
1단계 — 초기 인벤토리(서비스의 첫 번째 메시지)
연결되면 서비스는 감지된 모든 장치에 대해 config, state, status를 포함한 전체 스냅샷을 전송합니다:
{
"session_id": 7,
"inverse3": [
{
"device_id": "049D",
"config": {
"type": "inverse3",
"port": "COM12",
"device_info": { "major_version": 7, "minor_version": 4, "id": "049D", "device_type": 4, "uuid": "…" },
"extended_device_id": "…",
"extended_firmware_version": "…",
"gravity_compensation": { "enabled": true, "scaling_factor": 1.0 },
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": { "enabled": true },
"home_return": { "enabled": false },
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 } },
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": 0.0 }
}
},
"state": {
"cursor_position": { "x": 0.0, "y": -0.12, "z": 0.15 },
"cursor_velocity": { "x": 0.0, "y": 0.0, "z": 0.0 },
"angular_position": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"angular_velocity": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"body_orientation": { "w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0 },
"current_cursor_force": { "x": 0.0, "y": 0.0, "z": 0.0 },
"current_cursor_position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"current_angular_torques": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"current_angular_position": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"control_domain": "cartesian",
"control_mode": "idle",
"transform": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 } },
"transform_velocity": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 0, "y": 0, "z": 0 } }
},
"status": {
"calibrated": true,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [],
"wireless_verse_grip": []
}
세 블록은 업데이트 빈도가 서로 다릅니다:
| 블록 | 포함 내용 | 변경 빈도 |
|---|---|---|
config | 펌웨어 정보, 프리셋, basis, 마운트, 필터 | 드물게 — 명시적인 구성 변경 시에만 |
state | 위치, 속도, 힘, 방향, 변환 | 매 틱 (고빈도) |
status | Ready, calibrated, 전원 공급, in_use | 가끔 (저빈도) |
2단계 — 첫 번째 명령 전송
초기 인벤토리를 파싱하여 장치 ID를 찾은 다음, 세션 프로필, 초기 구성, 그리고/또는 제어 명령을 담은 메시지를 다시 전송하십시오:
{
"session": {
"configure": {
"profile": { "name": "co.haply.inverse.tutorials:hello-floor" }
}
},
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
},
"commands": {
"set_cursor_force": { "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
}
}
]
}
3단계 — 이후 상태 업데이트
첫 번째 교환 이후에는 구성 변경으로 인한 전체 스냅샷 푸시가 발생하지 않는 한, 서비스는 state + status만 전송합니다(config 제외):
{
"session_id": 7,
"inverse3": [
{
"device_id": "049D",
"state": { "cursor_position": { … }, "cursor_velocity": { … }, … },
"status": { "ready": true, "calibrated": true, … }
}
]
}
4단계 — 반복
명령 전송 → 상태 업데이트 수신. 루프는 전송 속도에 따라 계속 진행됩니다.
configure와 commands의 차이
보내는 메시지의 각 디바이스 항목에는 다음 두 가지 맵이 포함될 수 있습니다:
| 맵 | 용도 | 지속성 |
|---|---|---|
configure | 일회성 설정: preset, basis, mount, filters, 모듈 설정 | 변경 전까지 유지 |
commands | 틱 단위 제어: force, position, torques | 한 번 적용된 후 폐기 |
configure 키의 전체 목록은 세션 구성을, commands 항목은 제어 명령을 참고하십시오.
set_transform은 예외적인 경우입니다set_transform은 commands 아래에 있지만 **지속형(persistent)**입니다. 서비스는 새 값을 보내기 전까지 마지막 값을 유지합니다. 이는 카메라가 움직이는 동안 변환 값을 스트리밍하면서도, 입력 사이에 마지막 위치가 유지되기를 원하는 씬 탐색 용도이기 때문입니다.
execute 플래그
configure 또는 commands 항목에 "execute": false를 포함하면, 서비스가 페이로드를 적용하지 않고 구문 분석 및 검증만 수행합니다.
{
"inverse3": [{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front", "execute": true },
"damping": { "scalar": 0.0, "execute": false }
}
}]
}
이는 모든 필드를 항상 출력하는 리플렉션 기반 직렬화기(예: Unity의 JsonUtility)에 유용합니다. 사용하지 않는 항목은 execute: false로 설정하여 실제 구성을 덮어쓰지 않도록 하십시오. 기본값은 true이며, 플래그를 생략하면 “정상적으로 적용”됩니다.
경고
probe_position과 probe_orientation은 제어 명령을 보내지 않는 세션(예: Haply Hub)을 위한 모니터링 전용 keepalive 메시지입니다. 세션이 이미 set_cursor_force, set_cursor_position 등을 보내고 있다면 프로브를 함께 보내지 마십시오. 디바이스 상태는 이미 모든 응답에 포함되어 있습니다. 두 가지를 함께 사용하면 대역폭이 낭비되고 session-probe-dropped 이벤트가 발생할 수 있습니다.
명령이 아무런 효과가 없는 것처럼 보인다면 필드 이름이 잘못되었을 가능성이 높습니다. 서비스는 현재 인식되지 않는 JSON 키를 오류 없이 무시합니다. 서비스 로그를 확인하고, API 레퍼런스에서 필드 이름이 올바른지 검증하십시오. 이 동작은 향후 버전에서 변경될 예정입니다.