09. WebSocket 워크스페이스 내비게이터
set_transform 명령을 WebSocket 채널을 통해 스트리밍함으로써 장치의 워크스페이스 위치를 실시간으로 탐색합니다. 이 튜토리얼은 HTTP 기반으로 세션과 장치를 탐색하고(튜토리얼 08과 정확히 같은 방식), 이후 매 틱마다 위치 업데이트를 전송하기 위해 WebSocket 연결을 엽니다.
이 표는 두 가지 변환 연산의 차이를 보여 줍니다:
| 연산 | 튜토리얼 | 일반적인 용도 |
|---|---|---|
configure.mount — 영구 마운트 변환 | 08 | 장치의 물리적 장착을 설명하기 위해 한 번(또는 드물게) 설정 |
set_transform — 영구 워크스페이스 변환 | 09 (본 튜토리얼) | 역시 영구적이지만, 실시간 워크스페이스 탐색을 위해 매 틱 스트리밍할 수 있음 |
두 명령 모두 HTTP 및 WebSocket 환경에서 사용할 수 있으며, 둘 다 영구적입니다. 서비스는 마지막으로 보낸 값을 기억합니다. 두 명령의 차이는 목적과 업데이트 빈도에 있습니다. configure.mount는 드문 업데이트(물리적 설정)에 최적화되어 있는 반면, set_transform은 고빈도 스트리밍(장면 탐색)에 최적화되어 있습니다.
워크스페이스 원점이 런타임에 변하는 대상을 추적해야 할 때 set_transform을 사용하십시오. 예를 들어 손을 따라가거나, 절차적 오프셋을 애니메이션으로 구현하거나, 운용자가 가상 워크스페이스를 실시간으로 미세 조정하도록 할 때 사용합니다.
개별적으로 보면 configure.mount와 set_transform은 같은 효과를 내는 것처럼 보이지만, 두 값은 부모-자식 관계입니다. 좌표 파이프라인(device → basis → mount → workspace → application)에서 워크스페이스 변환은 마운트 위에 적용됩니다. 전체 파이프라인은 마운트 및 워크스페이스를 참조하십시오.
사용 사례
- 실시간 워크스페이스 탐색. 햅틱 장면을 멈추지 않고 장치의 커서 워크스페이스(엔드 이펙터가 도달할 수 있는 경계 영역)를 이동합니다.
- 운용자 주도 정렬. 메인 햅틱 앱이 실행되는 동안 두 번째 터미널에서 다른 사용자가 방향키를 눌러 워크스페이스를 이동하도록 합니다. 라이브 세션 중 사용자별 작업대 캘리브레이션에 유용합니다.
- 절차적 워크스페이스 모션. 매 틱마다 위치 전용 변환 패치를 전송하여 스크립트(오디오 반응형, 물리 기반 등)로 워크스페이스 위치를 구동합니다.
사전 요구 사항
튜토리얼 09에는 활성 WebSocket 세션이 필요합니다. 즉, 이미 실행 중인 햅틱 루프에서 Inverse3을(를) 사용하고 있는 클라이언트여야 합니다. 튜토리얼 09는 해당 세션에 연결한 뒤 워크스페이스를 실시간으로 이동합니다.
Haply Hub을(를) 열고 Orb 데모를 실행하십시오. 그러면 바로 대상으로 지정할 수 있는 영구 햅틱 세션이 열립니다:
./09-haply-inverse-ws-remote-control --session co.haply.hub::demo-orb
python 09-haply-inverse-ws-remote-control.py --session "co.haply.hub::demo-orb"
Orb 장면은 워크스페이스 원점에 구체를 렌더링합니다. 튜토리얼 09로 워크스페이스를 이동하면 장면 전체가 실시간으로 이동하여 즉각적인 시각 피드백을 제공합니다.
햅틱 루프를 실행하는 다른 튜토리얼(01 – 07)도 모두 작동합니다. 한 터미널에서 해당 튜토리얼을 시작한 다음, 두 번째 터미널에서 튜토리얼 09를 실행하여 세션을 대화식으로 탐색하게 하십시오.
사용법
# Discover session interactively, use first detected device
./09-haply-inverse-ws-remote-control
python 09-haply-inverse-ws-remote-control.py
# Target the Haply Hub Orb demo directly
./09-haply-inverse-ws-remote-control --session co.haply.hub::demo-orb
python 09-haply-inverse-ws-remote-control.py --session "co.haply.hub::demo-orb"
# Target a session directly, specify device
./09-haply-inverse-ws-remote-control --session :my_profile:0 --device A14
python 09-haply-inverse-ws-remote-control.py --session "#42" --device A14
조작 방법
- Python
- C++
키를 누르고 있으면 연속적으로 이동합니다. 키에서 손을 떼면 멈춥니다.
| 키 | 동작 |
|---|---|
→ / ← | +X / −X |
↑ / ↓ | +Y / −Y |
Page Up / Page Down | +Z / −Z |
= / - | 탐색 속도 증가 / 감소 |
0 | 워크스페이스를 원점으로 재설정 |
H | 도움말 표시 |
Esc | 종료 (Ctrl+C도 동작) |
줄 단위 입력 방식입니다. 명령을 입력한 후 ENTER를 누르십시오.
| 명령 | 동작 |
|---|---|
x+[N] / x-[N] | X 속도를 ±N mm/프레임으로 설정 (x+만 입력하면 현재 기본 속도를 사용) |
y+[N] / y-[N] | Y 속도를 ±N mm/프레임으로 설정 |
z+[N] / z-[N] | Z 속도를 ±N mm/프레임으로 설정 |
s+[N] / s-[N] | 기본 속도를 ±N mm/프레임만큼 조정 |
stop | 모든 속도를 0으로 설정 |
reset | 속도를 0으로 설정하고 원점으로 복귀 |
h | 도움말 표시 |
종료하려면 Ctrl+C(또는 Ctrl+D / EOF)를 누르십시오.
동작 원리
1단계 — HTTP 세션 및 장치 탐색
이 튜토리얼은 튜토리얼 08과 동일한 discover_session() / discover_device() 헬퍼를 재사용합니다. GET /sessions(또는 --session과 함께 쓰는 GET /sessions/<selector>)를 호출한 다음, GET /devices를 호출하거나 --device를 직접 사용합니다. 두 헬퍼는 동일한 SESSION_HELP 텍스트와 함께 같은 CLI 플래그를 받으며, 튜토리얼 00 또는 08의 셀렉터는 여기서도 수정 없이 동작합니다.
2단계 — WebSocket 세션, 첫 메시지 핸드셰이크
이 튜토리얼은 ws://localhost:10001로 WebSocket 연결을 엽니다. 처음 수신한 메시지에 대해서만 세션 프로필 등록 정보를 전송합니다:
{
"session": {"configure": {"profile": {"name": "co.haply.inverse.tutorials:ws-remote-control"}}},
"inverse3": [{"device_id": "...", "commands": {"set_transform": {"transform": {"position": {"x": 0, "y": 0, "z": 0}}}}}]
}
이후 틱에서는 session을 생략하며, inverse3 명령 배열만 전송됩니다.
3단계 — 틱마다 set_transform 실행
set_transform은 부분 변환을 허용합니다. 즉, 변경하려는 필드만 지정하면 됩니다. 이 튜토리얼은 위치만 전송하며, 회전과 스케일은 세션 기본값(설정된 경우 configure.mount 명령으로 지정한 값)을 그대로 유지합니다.
{
"inverse3": [{"device_id": "...", "commands": {"set_transform": {"transform": {"position": {"x": 0.02, "y": 0.0, "z": 0.01}}}}}]
}
위치는 틱 단위로 누적됩니다. 약 1 kHz에서 서비스는 매 틱마다 set_transform을 다시 적용합니다(영차 홀드, zero-order hold). 따라서 워크스페이스는 마지막으로 전송한 위치에 그대로 머무릅니다.
set_transform과 configure.mount의 차이두 명령 모두 세션별로 영구 변환을 저장하지만, 파이프라인에서 워크스페이스 변환은 마운트 위에 놓입니다(마운트 및 워크스페이스 참조). 튜토리얼 09가 매 틱마다 set_transform을 스트리밍하는 동안에는 새로운 WebSocket 프레임이 도착할 때마다 이전에 스트리밍된 워크스페이스 변환이 대체됩니다. 따라서 같은 장치에 set_transform을 스트리밍하는 다른 클라이언트는 다음 틱에서 덮어쓰여집니다. configure.mount는 독립적이며 자신의 값을 유지합니다. 실무에서는 관심사마다 채널을 하나씩 선택하십시오. 물리적 설정에는 마운트를, 실시간 탐색에는 set_transform을 사용합니다.
스레딩 모델 (C++)
C++ 튜토리얼은 위치 누산기를 공유하는 두 개의 스레드를 사용합니다:
| 스레드 | 역할 |
|---|---|
| 메인 스레드 | 입력 읽기(std::getline(std::cin, ...)), 토큰 파싱, nav_vel[] 및 default_step 갱신 |
| WebSocket 콜백 스레드 | nav_vel[] * dt 값을 사용해 nav_pos[]를 전진시키고, set_transform을 직렬화하여 전송 |
std::mutex nav_mutex는 nav_pos[], nav_vel[], default_step을 보호합니다. last_tick 타임스탬프와 first_ws_msg 플래그는 WS 콜백에 국한된 지역 변수이므로 뮤텍스가 필요하지 않습니다.
ws.onmessage = [&](const std::string &) {
float pos_snap[3];
{
std::lock_guard<std::mutex> lk(nav_mutex);
for (int i = 0; i < 3; ++i) nav_pos[i] += nav_vel[i] * 0.001f; // mm/frame → m
for (int i = 0; i < 3; ++i) pos_snap[i] = nav_pos[i];
}
// build and send set_transform with pos_snap
};
소스 코드
튜토리얼 09는 SDK와 함께 로컬에 설치됩니다. 서비스 설치 디렉터리 아래의 tutorials/09-haply-inverse-ws-remote-control/에서 확인하십시오.
관련 문서: set_transform 명령 · 세션 — 첫 메시지 핸드셰이크 · 마운트 및 워크스페이스 · 셀렉터 · 튜토리얼 08 — HTTP 원격 설정 · 튜토리얼 07 — Basis 및 마운트