05. 위치 제어
Inverse3 의 커서를 다음 방법을 사용하여 목표 위치로 이동합니다. set_cursor_position. 언어에 따라 상호작용 방식이 달라집니다. — C++는 일회용 무작위 타겟을 사용하는 반면, 파이썬은 키보드로 제어되는 연속 이동을 사용합니다.
이 강좌에서 배우게 될 내용:
- 사용하기
set_cursor_position위치 제어 모드의 경우 - 동일한 기본 명령에 대한 두 가지 서로 다른 상호작용 모델
- 타깃을 작업 공간의 특정 영역으로 제한 — Minverse 는 다음과 같은 반경보다 더 작은 반경을 사용합니다. Inverse3
- 작업 영역의 중심이 원점이 되도록 작업 영역 사전 설정을 정의합니다.
업무 흐름
C++ (무작위 표적 모델)
- 백그라운드에서 키 입력을 읽으며, 한 줄씩 버퍼링되는 실행 스레드를 시작합니다 (
n,+,-,q), 표준 입력(stdin)에서. - WebSocket을 엽니다. 첫 번째 상태 프레임에서 다음을 기록합니다. 세션 프로필 그리고 다음을 정의하십시오
configure.preset: arm_front_centered. 구 내부에서 첫 번째 무작위 표적을 생성합니다(배제 표본 추출, 반경 0.08 m). - 각 주기마다 명령을 보내십시오
set_cursor_position현재 타깃을 향해. 커서는 타깃을 부드럽게 따라갑니다. 이 서비스는 데이터 전송 속도를 제한하고 위치를 보간합니다. - 사용자가 입력하면
n그런 다음 ENTRÉE를 누르면, 입력 스레드가 새로운 무작위 대상을 생성합니다.+/-속도를 조절하십시오;q종료.
파이썬 (이동 모델 유지)
- WebSocket을 엽니다. 첫 번째 상태 프레임에서 다음을 확인하십시오.
status.calibrated— 기기가 아직 보정되지 않은 경우 사용자에게 안내 메시지를 표시합니다. - 읽기
config.type작업 영역의 반경을 선택하려면 (minverse= 0.04 m, 그 외의 모든 값 = 0.10 m). - 저장하세요 세션 프로필 그리고 다음을 정의하십시오
configure.preset: arm_front_centered. - 매 주기마다: 키보드 상태를 확인하십시오 (
W/A/S/D/Q/E), 다음 위치로 이동하십시오.SPEED각 축을 따라 작업 영역의 구형 영역으로만 제한한 다음, 다음을 전송하십시오.set_cursor_position.R목표값을 초기값으로 재설정합니다.
매개변수
| Nom | 기본값 (C++) | 기본값 (Python) | 역할 |
|---|---|---|---|
workspace_radius / RADIUS_INVERSE3 | 0.08 m | 0.10 m (Inverse3) / 0.04 m (Minverse) | 표적 구의 반경 |
speed_step / SPEED | 0.01 / 지원 | 0.00005 m / 주기 | 상호작용을 통한 것이 아님 |
PRINT_EVERY_MS | — | 100 | 텔레메트리 제한 (Python) |
| 세션 프로필 | co.haply.inverse.tutorials:position-control | 동일한 | Haply Hub에서 사용자 인증을 지원합니다. |
Python 버전은 다음을 확인합니다. status.calibrated 첫 번째 상태 프레임부터 시작하며, 기기가 보정되지 않은 경우 사용자에게 메시지를 표시합니다. C++ 버전은 보정이 이미 완료된 것으로 가정합니다.
가장 많이 읽힌 주 챔피언십 기사
data.inverse3[0].device_id— 명령어를 구성하기 위해data.inverse3[0].state.cursor_position— 원격 측정- (파이썬, 첫 번째 프레임만)
data.inverse3[0].config.type— 사용할 반지름을 지정합니다: Inverse3 또는 Minverse - (파이썬, 첫 번째 프레임만)
data.inverse3[0].status.calibrated— 값이 false인 경우 사용자에게 메시지가 표시됩니다
발송 및 수신
발표 일정
- C++ 표준 입력을 읽고 대상에 데이터를 쓰는 백그라운드 스레드를 실행합니다
std::atomic<float>; WebSocket 스레드는 매 사이클마다 이를 읽습니다.n그런 다음 ENTRÉE를 누르면, 입력 스트림이 새로운 무작위 대상을 생성합니다.q, 두 아들이 걸음을 멈췄다. - 파이썬 비동기적이고 단일 스레드 방식입니다. WebSocket 루프는 매 사이클마다 키보드 상태를 조회하고 업데이트합니다.
position바로.
Inverse-API의 유효 부하는 동일합니다. 첫 번째 루프는 세션 프로필을 전달하고 configure.preset, 그리고 그 이후의 주기에서는 오직 set_cursor_position.
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
비동기식 고유 부클. 키보드 조회 (handle_keys)는 각 사이클마다 루프 내에서 직접 실행되며, 별도의 실행 스레드가 필요하지 않습니다. config.type et status.calibrated 첫 번째 상태 프레임부터 단 한 번만 읽힙니다.
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"]
radius = get_workspace_radius(data["inverse3"][0].get("config", {}))
# Handshake: profile + preset (one-shot)
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": device_id,
"configure": {"preset": {"preset": "arm_front_centered"}},
}],
}
else:
# Per tick: update position from keyboard (classic polling, not shown), send command
position = handle_keys(position, radius)
request_msg = {
"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_position": {"position": position}},
}],
}
await websocket.send(json.dumps(request_msg))
2선식 모델: 백그라운드에서 실행되는 한 스레드가 표준 입력(stdin)을 읽고 대상에 데이터를 기록합니다. std::atomic<float> ; libhv I/O 스레드가 실행됩니다 ws.onmessage 매 사이클마다 원자 변수를 읽어들입니다.
// Shared state written by the stdin thread, read by the ws thread
static std::atomic<float> target_x{0.0f}, target_y{0.0f}, target_z{0.0f};
ws.onmessage = [&](const std::string &message) {
const json data = json::parse(message);
if (data["inverse3"].empty()) return;
json request = {};
const bool do_handshake = first_message.exchange(false);
if (do_handshake) {
request["session"] = {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:position-control"}}}}}};
generate_random_target();
}
request["inverse3"] = json::array();
for (auto &el : data["inverse3"].items()) {
json dev = {
{"device_id", el.value()["device_id"]},
{"commands", {{"set_cursor_position",
{{"position", {{"x", target_x.load()},
{"y", target_y.load()},
{"z", target_z.load()}}}}}}},
};
if (do_handshake)
dev["configure"] = {{"preset", {{"preset", "arm_front_centered"}}}};
request["inverse3"].push_back(dev);
}
ws.send(request.dump());
};
ws.open("ws://localhost:10001");
std::thread input_thr(input_thread_func); // stdin reader — writes target atomics
while (running) std::this_thread::sleep_for(100ms);
동일한 2선식 모델. 제어용 유형별 구조 — std::optional<device_configure> 각 기기에 고유한 일회용 사전 설정을 전송하며, 이후 반복 시에는 JSON에서 이 항목이 제외됩니다.
// Struct models
struct vec3 { float x{}, y{}, z{}; };
struct set_cursor_position_cmd { vec3 position; };
struct preset_cfg { std::string preset; };
struct device_configure { std::optional<preset_cfg> preset; };
struct device_commands {
std::string device_id;
std::optional<device_configure> configure; // one-shot
struct commands_t {
std::optional<set_cursor_position_cmd> set_cursor_position;
} commands;
};
struct commands_message {
std::optional<session_cmd> session; // one-shot
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 request;
const bool do_handshake = first_message.exchange(false);
if (do_handshake) {
request.session = session_cmd{ /* profile = position-control */ };
}
for (const auto &dev : data.inverse3) {
device_commands dc{ .device_id = dev.device_id };
dc.commands.set_cursor_position = set_cursor_position_cmd{
.position = {target_x.load(), target_y.load(), target_z.load()}};
if (do_handshake)
dc.configure = device_configure{ .preset = preset_cfg{"arm_front_centered"} };
request.inverse3.push_back(std::move(dc));
}
std::string out;
(void)glz::write_json(request, out);
ws.send(out);
};
ws.open("ws://localhost:10001");
std::thread input_thr(input_thread_func);
while (running) std::this_thread::sleep_for(100ms);
튜토리얼 05도 SDK와 함께 로컬에 설치되어 있습니다 — 다음을 참조하세요 tutorials/05-haply-inverse-position-control/ 서비스 설치 디렉터리에서.
관련 항목: 제어 명령 (set_cursor_position) · 몽타주 및 작업 공간 (사전 설정) · 형식 (vec3) · 튜토리얼 06 (통합)