01. 표시 Inverse3
WebSocket 시뮬레이션에 연결하여, 서비스에서 보고한 첫 번째 Inverse3 의 커서 위치, 속도 및 힘을 스트리밍합니다.
이 강좌에서 배우게 될 내용:
- WebSocket 연결을 열고 전체 상태가 포함된 초기 메시지를 수신합니다.
- 다음과 같이 힘 값이 0인 유지 신호(keepalive)를 전송하려면
set_cursor_force세션이 활성화된 상태를 유지하기 위해 - Haply Hub가 시뮬레이션을 인식할 수 있도록 세션 프로필을 저장하세요
- 첫 번째 메시지로 제한되는 악수 모델: 첫 번째 전송 후 session/configure 제거
- 읽기
transform작업 공간(위치, 회전, 크기)과 부분 업데이트 의미론 - 콘솔의 표시 속도를 읽기 쉬운 수준으로 제한하기
진행 과정
- 다음으로 WebSocket 연결을 열기
ws://localhost:10001. 해당 서비스는 즉시 전체 상태 트림 연결된 기기를 나열합니다. - 첫 번째 프레임에서 다음을 선택하십시오.
device_id첫 번째 ` Inverse3 `를 사용하여 두 부분으로 구성된 요청 메시지를 구성합니다:session.configure.profile.name: 시뮬레이션 결과를 다음 위치에 저장합니다. Haply Hub.- 주문
set_cursor_force장치별로, 벡터 값이 0인 신호를 사용합니다. 이 서비스는 이를 유지 신호로 활용하며, 명령이 도착하는 한 상태 프레임을 계속 전송합니다.
- 메시지를 다시 보내기. 필드 제거
session다음 주기 전에: 세션 프로필은 일회성 핸드셰이크이며, 이후 주기에서는 명령만 전송됩니다. - 다음 각 상태 프레임에 대해: 필드를 표시합니다
vec3커서의 위치, 속도, 힘 등의 신호를 약 10 Hz로 제한된 주파수로 전송한 다음, 힘을 0으로 유지하는 신호를 다시 전송한다.
매개변수
| Nom | 결함 | 역할 |
|---|---|---|
URI | ws://localhost:10001 | WebSocket 시뮬레이션 채널의 URL |
PRINT_EVERY_MS | 100 | 콘솔의 표시 제한 |
| 세션 프로필 이름 | co.haply.inverse.tutorials:print-inverse3 | Haply Hub에서 이 시뮬레이션을 식별하세요. |
가장 많이 읽힌 주 챔피언십 기사
~부터 data.inverse3[0].state :
cursor_position,cursor_velocity,current_cursor_force: 유형vec3각각transform: 작업 공간 변환; 다음을 포함하는 하위 객체:position(vec3),rotation(quaternion) 및scale(vec3)
정체값과 같은 하위 필드(position: {0,0,0}, rotation: {w:1,x:0,y:0,z:0}, scale: {1,1,1})는 omis 대역폭을 절약하기 위해 페이로드에서 제외합니다. 읽기 시에는 항상 기본값을 지정하십시오(예: .value("position", default_pos) C++에서는, .get("position", default_pos) (Python에서). 다음을 활성화하세요 serialization/explicit_fields 모든 필드를 계속 수신하려면.
발송 및 수신
WebSocket 루프: 상태 프레임을 수신하고, 데이터를 구성한 뒤 다시 전송하기 제어 프레임. 첫 번째 제어 프레임은 세션 핸드셰이크와 강제 값이 0인 유지 신호를 전달하며, set_cursor_force; 그 이후의 각 프레임은 que 유지 신호 (세션이 종료됨).
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
고유 비동기 부클 : recv() → 주문 작성 → send() → 다시 하기.
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"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-inverse3"}}},
"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_force":
{"vector": {"x": 0.0, "y": 0.0, "z": 0.0}}},
}]
}
await websocket.send(json.dumps(request_msg))
request_msg.pop("session", None) # one-shot handshake
libhv는 WebSocket을 자체 I/O 스레드에서 처리하며, 프레임별 처리는 다음 위치에 있습니다. ws.onmessage. 메인 스레드는 단순히 엔터 키를 기다리고 있을 뿐입니다.
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["inverse3"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-inverse3"}}}}}}},
{"inverse3", json::array({
{{"device_id", device_id},
{"commands", {{"set_cursor_force",
{{"vector", {{"x", 0.0}, {"y", 0.0}, {"z", 0.0}}}}}}}},
})},
};
}
ws.send(request_msg.dump());
request_msg.erase("session"); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
nlohmann 변형과 동일한 libhv 콜백(callback) 모델입니다. 본문만 다를 뿐입니다. Glaze는 컴파일 시 반사 기법을 사용합니다. JSON 형식을 반영하는 구조체를 선언한 다음, 다음을 호출하면 됩니다. glz::read / glz::write_json. std::optional<session_cmd> 일회용 핸드셰이크를 전달하십시오. 이 필드가 정의되지 않은 경우, Glaze는 JSON 직렬화 과정에서 이 필드를 생략합니다.
// Struct models
struct vec3 { float x{}, y{}, z{}; };
struct inverse_state {
vec3 cursor_position{}, cursor_velocity{}, current_cursor_force{};
/* + body_orientation, angular_position, angular_velocity */
};
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message { std::vector<inverse_device> inverse3; };
struct set_cursor_force_cmd { vec3 vector; };
struct commands_message {
std::optional<session_cmd> session; // omitted from JSON when unset
std::vector<device_commands> inverse3;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
commands_message out_cmds{};
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = print-inverse3 */ };
}
// ... populate out_cmds.inverse3 with zero-force keepalive ...
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
명령줄 옵션 (Python)
Python 버전은 튜토리얼에 표시되는 내용을 변경하는 두 가지 옵션을 지원합니다:
| 옵션 | Effet |
|---|---|
--full | 한 줄로 요약된 정보 대신, 각 상태 프레임의 총 JSON 페이로드를 서식이 적용된 형태로 표시합니다. 서비스에서 전송하는 필드를 파악하는 데 유용합니다. |
--query-config | 재주입 session.force_render_full_state: {} 각 출력 사이클마다 서비스가 전체 스냅샷(블록 포함)을 다시 전송하도록 하기 위해 config : 기기 유형, 펌웨어, 사전 설정, 구성, 필터 등)을 각 프레임마다 적용합니다. 이 옵션을 사용하지 않으면, config 첫 번째 프레임에만 적용되며, 그 이후의 프레임에는 지속적으로 전송되는 증분 업데이트만 포함됩니다. |
두 가지 옵션은 서로 결합됩니다: python 01-haply-inverse-print-inverse3.py --full --query-config 블록과 함께 완전한 JSON 유효 부하를 표시합니다 config 각 사이클마다 표시되므로, Haply Hub 또는 HTTP API를 통해 매개변수의 변화를 실시간으로 관찰하기에 편리합니다. 참조: session.force_render_full_state pour la commande WebSocket sous-jacente.
C++ 버전은 이러한 옵션을 제공하지 않습니다. 이 버전들은 항상 한 줄 분량의 요약을 표시하며, config 첫 번째 프레임에서.
튜토리얼 01도 SDK와 함께 로컬에 설치되어 있습니다: 다음을 참조하세요 tutorials/01-haply-inverse-print-inverse3/ 서비스 설치 디렉터리에서.
관련 항목: WebSocket 프로토콜 · 제어 명령 (set_cursor_force) · 세션 · 형식 (vec3)