본문으로 이동

08. 원격 세션 구성 도구

다른 곳 (다른 애플리케이션, Unity 씬, Haply Hub 데모 등)에서 이미 실행 중인 세션을 해당 기기로 REST HTTP 호출을 전송하여 재구성할 수 있습니다. 이 튜토리얼은 웹소켓을 열지 않습니다. 다른 애플리케이션이 햅틱 피드백을 계속 생성하는 동안, 데이터베이스, 작업 공간 사전 설정 또는 편집 변환을 수정하기 위해 GET, POST 및 DELETE 요청만 수행합니다.

사용 사례​

  • 실행 중인 데모를 실시간으로 조정합니다. Haply Hub Orb 데모를 실행한 다음, 별도의 터미널에서 이 튜토리얼을 실행하여 기본 순서를 변경하거나, 작업 공간 사전 설정을 수정하거나, 조립 변형을 미세하게 조정해 보세요. 데모가 중단되지 않은 상태에서 Orb의 좌표 기준점이 즉시 이동합니다.
  • 사용자별 작업 공간 보정. 주 컴퓨터에서 햅틱 장면을 활성화된 상태로 유지하고, 동일한 네트워크에 속한 다른 작업자가 다음 중 하나를 전송하도록 하십시오: mount 가상 작업 공간이 사용자의 바탕화면과 일치하도록 하기 위해.
  • 기기 선택 메뉴. 동일한 HTTP 유틸리티 함수들이 다음을 조회할 수 있습니다 GET /devices (자세히 보기 튜토리얼 00) 기기를 나열하고 대화형 메뉴를 구성하기 위해 — 기기를 선택한 다음 재구성하기 — 세션의 WebSocket을 건드리지 않고도 가능합니다. 이 튜토리얼에서는 다음과 같이 묻습니다. /sessions 그리고 고정 *inverse/0 코드 내에서, 하지만 이 선택을 다음으로 대체하여 /devices 단순히 국부적인 수정만 필요합니다.
  • 스크립트를 통한 구성. 세션이 녹화를 시작하기 전에 필요한 사전 단계(데이터베이스 정의, 사전 설정 및 마운트)를 자동화하여, 각 클라이언트에 구성을 일일이 적용할 필요가 없습니다.

선행 조건​

튜토리얼 08에서는 이미 실행 중인 세션을 재구성합니다. 이를 위해서는 활성 상태인 햅틱 세션이 하나 필요합니다. 다른 튜토리얼, Unity 씬, 또는 Haply Hub의 데모 등 무엇이든 상관없습니다.

세션을 시작하는 가장 빠른 방법

Haply Hub를 열고 Orb 데모를 실행한 다음, 해당 데모를 직접 선택하세요:

./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

Orb 씬은 디바이스의 작업 공간에 구체를 표시합니다. 기본 설정이나 사전 설정을 반복해서 탐색하거나, 튜토리얼 08을 통해 편집 변형을 약간 조정하면 Orb의 좌표 기준점이 실시간으로 시각적으로 이동합니다.

이용률​

# Pick a session interactively (lists every session the service knows)
./08-haply-inverse-http-remote-config
python 08-haply-inverse-http-remote-config.py

# Target the Haply Hub Orb demo directly
./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

# Target one directly by selector
./08-haply-inverse-http-remote-config --session :my_profile:0
python 08-haply-inverse-http-remote-config.py --session "#42"

# Or by a wildcard profile pattern (first match) — handy when the exact profile is unknown
./08-haply-inverse-http-remote-config --session "co.haply.hub::*:0"

# Pick a specific device on the session (default is *inverse/0)
./08-haply-inverse-http-remote-config --device 'inverse3/#A12' # by exact id
./08-haply-inverse-http-remote-config --device '*inverse:left/0' # first left-handed inverse
./08-haply-inverse-http-remote-config --device '*inverse:right/0' # first right-handed inverse
./08-haply-inverse-http-remote-config --device '*verse_grip/0' # first grip-family device

시작 시 튜토리얼은 세션의 기본 설정, 사전 설정 및 현재 구성을 표시한 다음 키 입력을 기다립니다. 키를 한 번 누를 때마다 하나의 REST 호출이 전송됩니다.

시뮬레이션에서 프로필 이름을 지정하세요

프로필 이름이 없는 세션은 실행할 때마다 변경되는 숫자 식별자로만 식별할 수 있습니다. 다음을 호출하십시오. session.configure.profile.name 메인 앱에서 첫 번째 메시지가 도착하는 즉시: 그러면 다음과 같은 안정적인 선택기를 재사용할 수 있습니다. --session :my_profile:0 매 실행 시마다. 참조 세션 — 프로필 이름.

키보드 단축키​

그 말이 맞네요액션
B기본 순열 살펴보기
P작업 공간 사전 설정 살펴보기
W / E / R편집 모드 선택 — 위치 (mm) / 회전 (°) / 확대/축소 (%)
← / →현재 모드에서 −X / +X만큼 앞으로 이동
↑ / ↓현재 모드에서 +Y / −Y 방향으로 이동
Page Up / Page Down현재 모드에서 +Z / −Z 방향으로 이동
= / -세 축 모두에 대해 동시에 균일한 스케일링 ± (항상 사용 가능)
DeleteDELETE 기본 설정 + 사전 설정 + 구성 — 기기의 기본값으로 복원하기
H도움말 보기
Esc포기자 (Ctrl+C (이것도 작동합니다)

HTTP 동사 — GET, POST, DELETE​

이 튜토리얼에서는 세 가지 HTTP 동사만을 사용합니다. 각 호출은표준 JSON 쉘 ({"ok": true, "data": {...}} 성공할 경우, {"ok": false, "error": "..."} (실패 시) 및 다음 세 가지 상태 코드 중 하나: 200 성공, 400 구문 오류가 있는 쿼리, 404 선택기에 대한 결과가 없습니다.

동사역할사용된 경로
GET현재 상태 확인 — 세션 목록, 특정 세션 검색, 현재 구성 값/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POST구성 값 교체 — 본문은 JSON 형식입니다/<device_selector>/config/{basis,preset,mount}?session=...
DELETE설정 값을 기기의 기본값으로 복원하기/<device_selector>/config/{basis,preset,mount}?session=...

HTTP 유틸리티 함수​

세 개의 동사 주위에 얇은 캡슐화 층을 씌워, 튜토리얼의 나머지 부분이 마치 비즈니스 로직처럼 읽히도록 합니다:

파이썬 활용 requests.Session() HTTP 키프-얼라이브(Keep-Alive)의 경우(요청당 지연 시간을 약 50ms에서 약 5ms로 단축):

http = requests.Session()

def api_get(path):
r = http.get(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def api_post(path, body):
r = http.post(f"{BASE_URL}{path}", json=body, timeout=3)
return r.json() if r.status_code == 200 else None

def api_delete(path):
r = http.delete(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def session_url(endpoint):
return f"{endpoint}?session={session_selector}"

세션 살펴보기 — GET /sessions​

행동은 다음 요인에 따라 달라집니다. --session :

  • --session SELECTOR fourni → 단 하나 GET /sessions/<SELECTOR>. 200 → 그것을 사용하다; 404 → 오류를 알리고 중단합니다.
  • 선택 사항 없음 → GET /sessions (목록) → 프로필 이름이 포함된 세션 표시 → 인덱스 요청 → 최종 선택기 생성 (우선순위: :profile:0 사용 가능한 경우; 그렇지 않으면 다음을 사용하십시오 #id).

SELECTOR 에서 정의된 모든 형식을 허용합니다 선택기 — 세션 선택기 : :profile:instance, #id, :-1, :0, 간단한 프로필 이름, 또는 모티프 프로필 이름에 와일드카드 포함 comme co.haply.hub::*:0. 이 튜토리얼은 채널을 있는 그대로 전달하며, 이를 분석하는 것은 서비스의 역할입니다.

def discover_session(session_arg):
global session_selector

if session_arg:
# Direct lookup (e.g. ":my_profile:0", "#42", ":-1")
if api_get(f"/sessions/{session_arg}") is None:
return False
session_selector = session_arg
return True

# Otherwise: list and pick
data = api_get("/sessions")
sessions = data.get("data", {}).get("sessions", [])
for i, s in enumerate(sessions):
name = s.get("config", {}).get("profile", {}).get("name", "default")
print(f" [{i}] session #{s['session_id']} profile={name}")

picked = sessions[int(input("Pick session index: "))]
name = picked.get("config", {}).get("profile", {}).get("name", "")
# Prefer the profile selector — it survives restarts; id doesn't
session_selector = (f":{name}:0" if name and name != "default"
else f"#{picked['session_id']}")
return True

기기 선택기 — *inverse/0​

각 구성 호출은 특정 장치를 대상으로 합니다. 이 튜토리얼에서는 제품군 + 인덱스 형식의 와일드카드 선택자를 사용합니다:

/*inverse/0/config/<key>
  • *inverse Inverse 제품군의 모든 기기에 해당합니다 (inverse3, inverse3x, minverse) — 이 튜토리얼은 구체적인 모델에 관계없이 별다른 수정 없이 바로 사용할 수 있습니다.
  • 0 이 계열에서 0부터 시작하는 인덱스입니다. — 이 튜토리얼에서는 첫 번째 Inverse에 대해서만 다룹니다.

대상을 변경하려면 코드 한 군데만 수정하면 됩니다: 다음으로 변경하세요 --device (그 길은 이미 닦여 있다) :

/verse_grip/0/config/basis?session=... # --device verse_grip/0
/*verse_grip/0/config/basis?session=... # --device '*verse_grip/0' — first grip, any kind
/inverse3/A14/config/mount?session=... # --device 'inverse3/#A14'
/*inverse:left/0/config/basis?session=... # --device '*inverse:left/0' — first left-handed inverse
/*inverse:right/0/config/basis?session=... # --device '*inverse:right/0' — first right-handed inverse

수업 :left / :right 적용됩니다 avant 색인화 — *inverse:left/0 “0차 역수”를 의미한다 고셰병 », “0차 역수, 바라건대 왼손잡이”가 아니라. 참조 선택자 — 클래스 필터는 인덱싱 전에 처리됩니다.

대상 기기를 찾을 수 없을 경우 튜토리얼 08이 종료됩니다.

이 튜토리얼은 GET …/config/basis 시작 시. 세션에 해당하는 기기가 하나도 없는 경우 --device (예: --device '*inverse:left/0' (오직 오른손잡이용 구성으로만 제공됨), 이에 대한 응답은 다음과 같습니다. 404 no device matches selector '<sel>' ; 튜토리얼에는 다음과 같이 표시됩니다 Device '<sel>' not found in session '<sel>'. 그리고 다음 상태로 끝납니다 1. 더 이상 사용되지 않는 기본값을 사용하는 키보드 루프는 실행되지 않습니다.

확인하기 선택기 — 기기 선택기 전체 문법을 보려면. 하드코딩 대신 기기 선택 메뉴를 만들려면, 기기를 다음과 같이 나열하십시오. GET /devices?session=<selector> (튜토리얼 00)를 추가하고 다음을 통합하십시오. device_id 구성 경로에서 선택합니다.

구성 POST — 기본, 사전 설정, 마운트​

세 가지 구성 키, 동일한 요청 구조, 서로 다른 본문 스키마. 각 POST 요청은 200 그 결과 값을 다음의 data, 또는 404 세션 선택기나 장치 선택기에 해당하는 항목이 없는 경우.

기초​

POST /*inverse/0/config/basis?session=:my_profile:0
Content-Type: application/json

{"permutation": "XZY"}

답변: {"ok": true, "data": {"permutation": "XZY"}}

def post_basis():
perm, _ = BASIS_OPTIONS[basis_index]
api_post(session_url("/inverse3/0/config/basis"), {"permutation": perm})

사전 설정​

POST /*inverse/0/config/preset?session=:my_profile:0
Content-Type: application/json

{"preset": "arm_front_centered"}

답변: {"ok": true, "data": {"preset": "arm_front_centered"}}

def post_preset():
preset = PRESET_OPTIONS[preset_index]
api_post(session_url("/inverse3/0/config/preset"), {"preset": preset})

마운트​

POST /*inverse/0/config/mount?session=:my_profile:0
Content-Type: application/json

{
"transform": {
"position": {"x": 0.02, "y": 0.0, "z": 0.0},
"rotation": {"w": 0.966, "x": 0.0, "y": 0.259, "z": 0.0},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0}
}
}

답변: {"ok": true, "data": {"transform": { ... }}} — 정규화 후의 실제 변환 결과를 반환합니다.

def post_mount():
body = {
"transform": {
"position": {"x": mount_pos[0], "y": mount_pos[1], "z": mount_pos[2]},
"rotation": quat_from_euler_deg(*mount_rot),
"scale": {"x": mount_scale[0], "y": mount_scale[1], "z": mount_scale[2]},
}
}
api_post(session_url("/inverse3/0/config/mount"), body)
mount et preset 서로 배타적이다

두 키 중 하나에 대한 POST 요청이 전송되면, 해당 장치에서 다른 키는 삭제됩니다. 이 튜토리얼에서는 이를 명시적으로 다루지 않습니다. 각 POST 요청은 독립적으로 처리되며, 서버 측에서 충돌을 해결합니다. WebSocket 측에서도 동일한 규칙이 적용되는 내용은 튜토리얼 07을 참조하십시오.

DELETE 재설정 — 세 번의 호출​

reset 구성 키별로 DELETE를 트리거합니다. 각각은 200 다음에서 기본값이 복원된 data.

def reset_all():
api_delete(session_url("/inverse3/0/config/basis"))
api_delete(session_url("/inverse3/0/config/preset"))
api_delete(session_url("/inverse3/0/config/mount"))

마운팅 회전 구성​

transform.rotation 네트워크를 통해 전송되는 유니터리 쿼터니언입니다. 이 튜토리얼은 회전을 Z-Y-X 내재 오일러 3원수(X축을 중심으로 한 피치, Z축을 중심으로 한 요, Y축을 중심으로 한 롤 — 모두 도 단위) 형태로 저장하고, POST 요청이 있을 때마다 쿼터니언을 재구성합니다.

def quat_from_euler_deg(pitch_x, yaw_z, roll_y):
"""Hamilton quaternion for q = q_z * q_y * q_x (apply X, then Y, then Z)."""
hx, hy, hz = (math.radians(a) * 0.5 for a in (pitch_x, roll_y, yaw_z))
cx, sx = math.cos(hx), math.sin(hx)
cy, sy = math.cos(hy), math.sin(hy)
cz, sz = math.cos(hz), math.sin(hz)
return {
"w": cz*cy*cx + sz*sy*sx,
"x": cz*cy*sx - sz*sy*cx,
"y": cz*sy*cx + sz*cy*sx,
"z": sz*cy*cx - cz*sy*sx,
}
쿼터니언의 약정

오른손 좌표계를 사용하는, 스칼라 성분이 먼저 오는 유니터리 해밀턴 쿼터니언 (w) — 나머지 서비스와 동일한 약관이 적용되며, 자세한 내용은 다음을 참조하십시오. quaternion. 구성 순서는 다음과 같습니다. Z-Y-X 내재적 (q = q_z * q_y * q_x) : 먼저 X축을 중심으로 피치 각도를 적용하고, 그 다음 Y축을 중심으로 롤 각도를, 마지막으로 Z축을 중심으로 요 각도를 적용합니다.

이 튜토리얼에서는 각 상태 행에 오일러 3원수 옆에 파생된 쿼터니언을 표시하므로, 장치가 회전하기 전에 합성 연산을 확인할 수 있습니다. 로컬 오일러 상태는 (0, 0, 0), 세션에 이미 어떤 상태가 있든 상관없이 — 첫 번째 mount POST 기존 값을 대체합니다.

입력 모델 (간략)​

HTTP 통합이 핵심이며, 키보드 사용 편의성은 부차적인 문제입니다. 의도적으로 선택한 두 가지 단축키:

  • 파이썬 패키지 활용하기 keyboard — 다양한 플랫폼에서 작동하며, 길게 누른 키의 반복 입력을 기본적으로 지원합니다. 방향키, Page Up / Page Down et = / - 고정되어 있는 동안 장착 축을 이동시키고; B et P 상승 에지 시 basis 및 preset 값을 순차적으로 훑어봅니다.
  • C++ 활용하다 std::getline(std::cin, ...) 그리고 간결한 토큰 문법 (x+20, sx-5, u+10) — 연속적인 조절에는 인체공학적으로 다소 떨어지지만, 별도의 도구를 사용하지 않고도 휴대할 수 있다. #ifdef 콘솔용 API의 경우 각 플랫폼에 맞는 것들입니다.

출처​

SDK 설치 프로그램에 포함되어 있습니다

튜토리얼 08도 SDK와 함께 로컬에 설치되어 있습니다 — 다음을 참조하세요 tutorials/08-haply-inverse-http-remote-config/ 서비스 설치 디렉터리에서.

참조: 세션 — 원격 제어 · 선택기 · 장치 구성 · 베이스 순서 변경 · 마운트 및 작업 공간 · JSON 규칙 · 튜토리얼 00 — 장치 목록 · 튜토리얼 07 — Basis 및 마운트 (WebSocket 버전)