import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
VerseGrip 스타일러스 시작 가이드
VerseGrip은 Inverse3, Inverse3X 및 MinVerse 에 자세 추적 기능을 추가합니다. 이 글에서는 VerseGrip Stylus의 설정 방법과 기능을 소개합니다.
VerseGrip 스타일러스
VerseGrip 스타일러스는 독자적인 고속 무선 통신 기술을 채택하여 자세 추적 및 휴대용 입출력(I/O) 기능을 제공합니다. 다양한 사용 시나리오에 맞춰 설계되어 뛰어난 범용성을 갖추고 있습니다.
주요 기능은 다음과 같습니다:
- 사용자 입력 버튼 2개,
- 1개의 보정 버튼,
- 전원/대기 버튼 1개
- 2개의 상태 표시등
- USB-C 충전 포트
- 무선 통신 속도는 최대 1 KHz에 달합니다.
- 배터리 사용 시간 10~12시간
- 동일한 대역에서 최대 8개의 VerseGrip 스타일러스를 하나의 수신기(동글)와 페어링할 수 있습니다.

고객 지원
기기나 작동 방식에 대해 궁금한 점이나 우려 사항이 있으시면, 당사 지원팀에 문의하여 도움을 받으시기 바랍니다. VerseGrip Stylus에는 정교한 독자적인 무선 기술이 적용되어 있습니다. 기기를 개봉하면 보증이 무효화될 뿐만 아니라 기기가 작동하지 않을 수도 있습니다. 배송 기간이 길어지거나 높은 수리 비용이 발생하는 것을 방지하기 위해,기기를 개봉하지 마십시오.
VerseGrip 스타일러스 설정 및 보정
이 섹션에서는 리시버 삽입, VerseGrip Stylus 켜기 및 보정 절차를 설명합니다.
설정
- 수신기를 컴퓨터의 USB 포트에 연결하십시오.
- 컨트롤러를 평평하고 움직이지 않는 표면에 놓으세요. 이때 버튼은 위를 향하고, 구형 커넥터는 화면 쪽을 향해야 합니다.
전원 켜기 및 보정
- 전원 버튼을 한 번 누르십시오. 상태 표시등이 먼저 빨간색으로 변한 다음 노란색으로 바뀌면, 기기 내부의 IMU가 자체 보정 중임을 나타냅니다.
- 자동 보정이 완료되면 스타일러스가 컴퓨터에 연결됩니다.
- 스타일러스가 성공적으로 연결되어 무선으로 데이터를 전송할 때, 상태 표시등이 녹색으로 깜빡입니다.
- 배터리 잔량이 매우 부족할 경우, VerseGrip Stylus는 2분마다 한 번씩 자동으로 전원이 꺼집니다.즉시 충전해 주십시오.
재보정
보정 과정에서는 VerseGrip의 좌표계가 재설정되어, 보정 시 기기를 잡은 방향이 새로운 기준축이 됩니다.
- 재보정을 하려면 스타일러스를 보정하고자 하는 자세로 유지한 상태에서 보정 버튼을 3초 동안 누르거나, 시뮬레이션상의 자세가 변경될 때까지 누르고 계십시오.
아래 그림은 보정되지 않은 상태와 보정된 상태의 장치 동작을 비교한 것입니다. 시연 시, VerseGrip은 먼저 그림에 표시된 자세를 유지한 후 보정을 수행해야 합니다.

대기 모드 및 전원 끄기
- 전원 버튼을 한 번 누르면 대기 모드로 돌아갑니다. 상태 표시등이 흰색으로 깜빡이기 시작하며, 데이터 전송이 중지됩니다.
- VerseGrip Stylus를 끄려면 전원 버튼을 5초 동안 길게 누른 후 손을 떼십시오. 손을 떼면 상태 표시등이 꺼지고 더 이상 어떤 표시도 나타나지 않습니다. 기기는 데이터 전송을 중단합니다.
충전
- USB-C 충전 포트를 통해 VerseGrip Stylus를 충전하십시오.
- 전원을 연결하면 충전 상태 표시등이 파란색으로 계속 켜져 있다가, 충전이 완료되면 꺼집니다.
- 전원을 켤 때 스타일러스가 꺼져 있으면 자동으로 켜집니다.
경고: 2024년 8월부터 12월 사이에 출고된 VerseGrip Stylus의 경우, 배터리가 완전히 방전되지 않도록 주의하십시오. 배터리가 완전히 방전되면 더 이상 충전 상태를 유지하지 못할 수 있습니다. 이러한 상황이 발생하더라도 전원이 연결된 상태에서는 VerseGrip Stylus를 계속 사용할 수 있습니다. 이 문제를 해결하기 위한 펌웨어 업데이트 파일을 받으려면 Haply 지원팀에 문의하십시오.
상태 및 표시등
| 색상 | 조명 모드 | 설명 |
|---|---|---|
| 노란색 | 빠른 밝아짐/어두워짐 | 초기 자동 센서 기준 보정 (VerseGrip을 책상 위에 놓아두기) |
| 빠르게 깜빡임 | 자세 보정 버튼을 눌렀으며, 보정 과정이 시작되었습니다. | |
| 상시 점등 | 자세 보정 버튼을 눌렀으며, 보정이 완료되었습니다. | |
| 빨간색 | 빠른 밝아짐/어두워짐 | 어댑터가 분리되어 호스트에 신호가 없습니다. |
| 빠르게 깜빡임 | 전원 버튼을 눌렀으며, 종료 절차가 시작되었습니다. | |
| 녹색 | 점차 밝아짐/점차 어두워짐 | 어댑터가 연결되었으며, VerseGrip은 활성 데이터 스트림 모드에 있습니다. |
| 상시 점등 | 어댑터가 연결되었으며, VerseGrip이 활성 데이터 스트림 모드에 있고, USB 전원 공급이 연결되어 있습니다. | |
| 흰색 | 점차 밝아짐/점차 어두워짐 | 어댑터가 연결되어 있으며, VerseGrip은 유휴/대기 모드에 있고 데이터 전송이 이루어지지 않고 있습니다. |
| 상시 점등 | 어댑터가 연결되어 있으며, VerseGrip은 유휴/대기 상태이고, 데이터 전송이 없으며, USB 전원 공급이 연결되어 있습니다. | |
| 주황색 | 깜빡임 (교차) | 배터리 잔량 부족 경고 알림이 현재 상태에서 교대로 표시됩니다. |
| 비상등(교차 점멸) | 배터리 잔량이 심각하게 부족한 경고 메시지가 현재 상태에서 교대로 표시됩니다. | |
| 파란색 | 점차 밝아짐/점차 어두워짐 | 장비 펌웨어 업데이트 모드 |
| (더 밝게) | 상시 점등 | 사용자가 버튼을 누르면, 현재 표시 색상이 계속 켜진 상태로 더 밝아집니다. |
| 닫기 | 어둠 | 전원 끄기, 전원 차단 |
| 충전 표시등 색상 | 조명 모드 | 설명 |
|---|---|---|
| 파란색 | 상시 점등 | 충전 중 |
| 빠르게 깜빡임 | 곧 가득 찰 예정입니다 | |
| 닫기 | 어둠 | 충전 완료 |
구형 모델의 상태 및 표시등
다음 상태 표시등은 구형 상태 및 색상으로, 초기 버전의 VerseGrip Stylus(펌웨어 1.10 이하)에서 볼 수 있습니다.
| 색상 | 조명 모드 | 설명 |
|---|---|---|
| 빨간색 | 상시 점등 | 컴포넌트 초기화 대기 중 |
| 반짝임 | 대기 모드, 데이터 전송 없음 | |
| 파란색 | 상시 점등 | 초기 자동 센서 기준 보정 (VerseGrip을 책상 위에 놓아두기) |
| 녹색 | 반짝임 | 어댑터가 연결되었으며, VerseGrip은 활성 데이터 스트림 모드에 있습니다. |
| 닫기 | 어둠 | 전원 끄기, 전원 차단 |
| 충전 표시등 색상 | 조명 모드 | 설명 |
|---|---|---|
| 파란색 | 상시 점등 | 충전 중 |
| 빠르게 깜빡임 | 곧 가득 찰 예정입니다 | |
| 닫기 | 어둠 | 충전 완료 |
예제 코드
다음은 C++에서 VerseGrip Stylus를 사용하는 간단한 예제입니다.
#include <external/libhv.h>
#include <nlohmann/json.hpp>
#include <chrono>
#include <cstdio>
#include <string>
using namespace hv;
using json = nlohmann::json;
// Procedure to get the first detected and available Wired VerseGrip Stylus device id
std::string get_first_verse_grip_device_id(const json &data) {
const auto& vgs = data["wireless_verse_grip"];
if (vgs.empty()) {
return "";
}
return vgs.items().begin().key();
}
int main() {
const auto print_delay = std::chrono::milliseconds(100);
auto current = std::chrono::high_resolution_clock::now();
bool first_message = true;
std::string device_id;
WebSocketClient ws;
ws.onmessage = [&](const std::string &msg) {
json data = json::parse(msg);
if (first_message) {
first_message = false;
const std::string first_id = get_first_verse_grip_device_id(data);
if (first_id.empty()) {
printf("no Wireless VerseGrip found.\n");
ws.close();
return;
}
device_id = first_id;
}
if (device_id.empty() || !data["wireless_verse_grip"].contains(device_id)) {
return;
}
const auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::high_resolution_clock::now() > current + print_delay) {
current = now;
const json state = data["wireless_verse_grip"][device_id];
printf("Rotation : { x:%f, y:%f, z:%f, w:%f }, Hall:%i, Button : [%d, %d, %d], Battery: {%f}\n",
state["orientation"]["x"].get<float>(),
state["orientation"]["y"].get<float>(),
state["orientation"]["z"].get<float>(),
state["orientation"]["w"].get<float>(),
state["hall"].get<int8_t>(),
state["buttons"]["a"].get<bool>(),
state["buttons"]["b"].get<bool>(),
state["buttons"]["c"].get<bool>(),
state["battery_level"].get<float>());
}
};
ws.open("ws://localhost:10000");
printf("Press ENTER to stop...\n\n");
while (std::cin.get() != '\n') {
}
if (ws.isConnected()) {
ws.close();
}
return 0;
}
#include <string.h>
#include <chrono>
#include <iostream>
#include <iterator>
#include <string>
#include <thread>
#include "HardwareAPI.h"
int main(int argc, char* argv[])
{
char* portName;
if (argc < 2)
{
std::printf("Usage: %s <port>\n", argv[0]);
}
else
{
#if defined(_WIN32) || defined(_WIN64)
portName = _strdup(argv[1]); // argv1;
#endif
#if defined(__linux__)
portName = strdup(argv[1]); // argv1;
#endif
}
Haply::HardwareAPI::IO::SerialStream serial_stream(portName);
Haply::HardwareAPI::Devices::Handle handle(&serial_stream);
while (true)
{
Haply::HardwareAPI::Devices::Handle::VersegripStatusResponse data;
data = handle.GetVersegripStatus();
std::printf(
"device_id: %d battery_level: %f quaternion: %f %f %f %f buttons: "
"%d error_flags: %d\n",
data.device_id, data.battery_level, data.quaternion[0],
data.quaternion[1], data.quaternion[2], data.quaternion[3],
data.buttons, data.error_flag);
}
}