03. 무선 VerseGrip 배포
튜토리얼 02와 동일한 패턴이지만, 무선 VerseGrip용입니다. 스트리밍 상태에서는 버튼(A/B/C)과 배터리 잔량이 추가됩니다.
학습 내용:
- 무선 통신 관련 상태 필드 읽기:
buttons.{a,b,c},battery_level,hall - 사용법
probe_orientation독립적인 관찰자를 위한 Keepalive - 튜토리얼 02와 동일한 핸드셰이크 패턴으로, 첫 번째 메시지에만 세션 구성이 포함되어 있습니다.
진행 절차
- WebSocket을 통해 다음 주소로 연결을 설정하십시오.
ws://localhost:10001그리고 첫 번째 상태 메시지를 기다리십시오. - 다음 중 하나를 선택하십시오.
device_idArray에서 출시된 최초의 무선 VerseGripwireless_verse_grip. - 다음으로 문의 사항을 작성해 주세요. 회의 개요 그리고 장치별 Keepalive를 통해
probe_orientation. - 요청을 보내신 후 해당 필드를 삭제해 주세요
session— 이는 단 한 번뿐인 악수입니다. - 매 프레임마다 쿼터니언을 오일러 각도로 변환하고, 버튼 상태 및 배터리 잔량을 포함한 제한된 텔레메트리 데이터를 출력하십시오. 매 클럭마다 키프얼라이브를 다시 전송하십시오.
매개변수
| 이름 | 표준 | Zweck |
|---|---|---|
URI | ws://localhost:10001 | 시뮬레이션 채널의 WebSocket URL |
PRINT_EVERY_MS | 100 | 콘솔 출력 제한 |
| 세션 프로필 이름 | co.haply.inverse.tutorials:print-wireless-verse-grip | Haply 허브에서 이 시뮬레이션을 식별합니다. |
변환은 응용 좌표계 내에서 Z-X-Y 순서(요동 → 피치 → 롤)로 내재적으로 수행됩니다. +X right, +Y forward, +Z up. 다음은 사용하지 마십시오 glm::eulerAngles — 이는 다른 규칙을 따르기 때문에 여기서는 잘못된 값을 반환합니다. 세 가지 언어 변형 모두 동일한 수학적 계산을 구현하고 있으며, 해당 공식은 소스 코드에서 확인하실 수 있습니다.
probe_orientation 실제로 필요한probe_orientation 다음과 같은 경우에만 의미가 있습니다. 귀하의 세션이 keinen Inverse3 로 명령을 전송합니다. Inverse3 로 접속하면(출력, 위치, 토크 등), 해당 서비스는 페어링된 VerseGrip의 방향 정보를 각 상태 프레임마다 자동으로 스트리밍하므로, Keepalive가 필요하지 않습니다. 다음을 사용하십시오. probe_orientation 이 튜토리얼과 같은 독립형 그립 모니터링 도구에만 해당됩니다.
읽은 상태 필드
Aus data.wireless_verse_grip[0].state:
orientation—quaternion(w, x, y, z)hall— 홀 센서의 정수형 측정값buttons.a,buttons.b,buttons.c— 볼레 값battery_level— 부동소수점 수 (0.0 – 1.0)transform.rotation— 작업 영역 회전 (quaternion); 방향 정보만 제공하는 기기의 경우 위치와 크기는 적용되지 않으며, 절대 전송되지 않습니다.
transform.rotation 정체 사원수인 경우 생략됩니다. {w:1,x:0,y:0,z:0} 에 해당합니다. 데이터를 읽어올 때 기본값을 지정하십시오. 다음을 활성화하십시오. serialization/explicit_fields, 이를 항상 유지하기 위해서입니다.
보내기 / 받기
다음과 동일한 구조입니다. 튜토리얼 02, 단지 장치 어레이를 사용하여 wireless_verse_grip. WebSocket 루프는 상태 프레임을 수신하고, 핸드셰이크 및 키프얼라이브를 probe_orientation 뒤로; 첫 번째 송신 메시지에는 세션 프로필이 포함되며, 그 이후의 각 프레임에는 키프얼라이브만 포함됩니다.
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
단일 비동기 루프.
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["wireless_verse_grip"][0]["device_id"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-wireless-verse-grip"}}},
"wireless_verse_grip": [{
"device_id": device_id,
"commands": {"probe_orientation": {}} # empty — keepalive
}]
}
await websocket.send(json.dumps(request_msg))
request_msg.pop("session", None) # one-shot handshake
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["wireless_verse_grip"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-wireless-verse-grip"}}}}}}},
{"wireless_verse_grip", json::array({
{{"device_id", device_id},
{"commands", {{"probe_orientation", json::object()}}}},
})},
};
}
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
타입이 지정된 구조체. button_state 의도적으로 명시적으로 언급된다 (아니면 buttons), 필드가 가려지는 것을 방지하기 위해 buttons 에서 wvg_state 피하기 위해. Glaze는 필드의 이름을 기준으로 필드를 매핑하므로, 구조체 유형의 이름은 자유롭게 지정할 수 있습니다.
// Struct models
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct button_state { bool a{}, b{}, c{}; };
struct wvg_state {
quat orientation{};
uint8_t hall{};
button_state buttons{};
float battery_level{};
};
struct wvg_device { std::string device_id; wvg_state state; };
struct devices_message { std::vector<wvg_device> wireless_verse_grip; };
struct probe_orientation_cmd {}; // empty object on the wire
struct commands_message {
std::optional<session_cmd> session; // one-shot — omitted when unset
std::vector<device_commands> wireless_verse_grip;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
if (first_message) {
first_message = false;
request_msg.session = session_cmd{ /* profile = print-wireless-verse-grip */ };
device_commands dc{ .device_id = data.wireless_verse_grip[0].device_id };
dc.commands.probe_orientation = probe_orientation_cmd{};
request_msg.wireless_verse_grip.push_back(std::move(dc));
}
std::string out_json;
(void)glz::write_json(request_msg, out_json);
ws.send(out_json);
request_msg.session.reset(); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
명령줄 옵션 (Python)
Python 버전은 튜토리얼 01과 동일한 두 가지 플래그를 지원합니다:
--full— 한 줄로 요약된 내용 대신, 원시 JSON 페이로드를 형식화하여 출력합니다.--query-config— 매 틱이 발생할 때마다 추가됩니다session.force_render_full_state다시 추가하여, 각 프레임이config-단순히 상태 변화만 포함하는 것이 아니라 블록을 포함합니다.
--query-config VerseGrip의 무선 버전에서 특히 유용합니다. 왜냐하면 config-일반 구성 필드 외에도 WVG 전용 필드를 차단하십시오 sub_type (stylus / prototype / ruko / kingfisher(3.5부터 추가됨)을 포함합니다. 만약 귀하가 python 03-haply-inverse-print-wireless-verse-grip.py --full --query-config 실행하면, 다음과 같이 실시간으로 확인할 수 있습니다. config.sub_type 나머지 구성 값들과 함께 업데이트됩니다.
C++의 변형 버전들은 이러한 플래그를 제공하지 않습니다.
또한 튜토리얼 03은 SDK와 함께 로컬에 설치되며, 다음 위치에서 찾을 수 있습니다. tutorials/03-haply-inverse-print-wireless-verse-grip/ 서비스의 설치 디렉터리에서.
관련 주제: 튜토리얼 02 (유선 VG) · 유형 (쿼터니언) · 제어 명령 (probe_orientation) · WebSocket 프로토콜