본문으로 바로 가기

07. 베이스 & 마운트 플레이그라운드

디바이스의 마운트 변환을 대화형으로 제어하고, configure.basis, configure.preset, configure.mount와이어 프로토콜 수준에서 이 두 요소가 어떻게 함께 작동하는지 보여줍니다. 대화형 포커스가 configure 명령에 머물도록 수평 바닥을 고정합니다.

학습 내용:

  • 기초 순열 설정 ("XZY" → Y축 위쪽 방향 (애플리케이션 프레임)
  • 프리셋(arm_front) 선택 및 해당 프리셋이 구성하는 내용을 이해하기
  • 회전 쿼터니언을 사용하여 런타임에 마운트 재정의 (키보드 입력 기반)
  • 상호 배타 규칙: mount와 preset은과 같은 장치 구성 동일한 블록에 함께 존재할 수 없습니다
  • 일회성 configure 동작: 키를 누를 때마다 configure 메시지가 정확히 한 번 전송됩니다.
  • (C++ 글레이즈) std::optional을 이용한 상호 배타적 필드 모델링

워크플로우​

  1. 첫 메시지를 받으면 세션 프로필, configure.basis: "XZY", configure.preset: arm_front를 전송합니다. 고정 바닥용으로 set_cursor_force 전송을 시작합니다.
  2. 매 틱마다 커서의 Y 값을 읽고 force_y = max(0, (floor_pos - y) * stiffness)를 계산하여 전송합니다.
  3. 사용자가 마운트 회전 키를 누르면 pending_configure = true로 설정합니다.
  4. 다음 틱에서 rotation이 단위 쿼터니언(현재 피치/요의 Z-then-X 합성)인 변환을 포함하는 configure.mount 블록을 구성합니다. preset은 생략합니다. 이 두 필드는 와이어 상에서 상호 배타적입니다.
  5. 재설정 키(R)는 오버라이드를 해제합니다. 다음 configure 단계에서는 다시 preset을 사용합니다.

매개변수​

이름기본값용도
BASIS"XZY"축 순열 — Y축 위쪽 방향 애플리케이션 프레임
DEVICE_PRESET / DEVICE_CONFIG_PRESET"arm_front"이름이 지정된 프리셋 — 원점은 장치 베이스
FLOOR_POS_Y0.0 m고정 바닥 평면 (애플리케이션 Y)
STIFFNESS1000 해당 없음바닥 스프링 상수
MOUNT_STEP_DEG10°키 입력당 회전 각도
PRINT_EVERY_MS200텔레메트리 스로틀

제어​

키동작
W / S장치 + X축(피치) 기준 마운트를 ±10° 회전
A / D장치를 Z축(요) 기준으로 ±10° 회전시켜 장착
R마운트 재설정 — 사전 설정으로 되돌리기
H제어 키 표시
Q종료
mount와 preset은 서로 배타적입니다

이 서비스는 두 필드를 모두 포함하는 장치 구성 블록을 거부합니다. 사용자가 마운트를 재정의하면 튜토리얼은 이후의 모든 configure에서 preset을 생략합니다. R을 누르면 다음 configure에서 preset이것이 다시 사용되고 mount는 제외됩니다.

입력 모델의 차이

C++ 변형은 백그라운드 stdin 스레드에서 줄 단위로 입력을 읽습니다(문자를 입력할 때마다 Enter 키를 누릅니다). Python은 메인 비동기 루프에서 keyboard 이 패키지는 실시간 키 입력을 폴링하므로 ENTER 키를 누를 필요가 없습니다. 키와 명령은 동일합니다.

읽기 상태 필드​

data.inverse3[i].state에서 읽은 값:

  • cursor_position.y — vec3. 바닥 관통량을 계산하는 데 사용됩니다.
  • current_cursor_force — 텔레메트리용으로 보고됩니다.

송신 / 수신​

페이로드 구조는 모든 변형에서 동일합니다. 차이점은 각 변형이 서로 배타적이라는 점입니다. mount / preset 분기를 구성하는 방식과, 입력 스레드가 WebSocket 스레드에 신호를 보내는 방식입니다.

단일 비동기 루프에서 keyboard 패키지를 통해 실시간 키 입력을 폴링합니다. pending_configure는 키 핸들러가 설정하는 전역 플래그이며, configure 블록이 전송될 때마다 초기화됩니다.

async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)

if first_message:
first_message = False
device_id = data["inverse3"][0]["device_id"]
# Handshake: profile + basis + preset
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": device_id,
"configure": build_configure_block(first_handshake=True),
# -> {"basis": {"permutation": "XZY"},
# "preset": {"preset": "arm_front"}}
}],
}
else:
handle_key_inputs() # may set pending_configure = True (classic, not shown)

y = data["inverse3"][0]["state"]["cursor_position"]["y"]
force_y = 0.0 if y > FLOOR_POS_Y else (FLOOR_POS_Y - y) * STIFFNESS

entry = {
"device_id": device_id,
"commands": {"set_cursor_force":
{"vector": {"x": 0.0, "y": force_y, "z": 0.0}}},
}
if pending_configure:
entry["configure"] = build_configure_block(first_handshake=False)
# -> {"mount": {...}} OR {"preset": {...}} (never both)
pending_configure = False

request_msg = {"inverse3": [entry]}

await websocket.send(json.dumps(request_msg))
SDK 설치 프로그램과 함께 제공됨

튜토리얼 07은 SDK와 함께 로컬에도 설치됩니다. 서비스 설치 디렉터리 아래의 tutorials/07-haply-inverse-basis-and-mount/에서 확인할 수 있습니다.

출처: Python · C++ · C++ Glaze

관련 항목: Basis 순열 · 마운트 및 작업 공간 · 장치 구성 · 제어 명령 (set_cursor_force) · 유형 (변환) · 튜토리얼 04 (Hello Floor)