05. Positionssteuerung
set_cursor_position Bewegen Sie den Cursor überInverse3 an die gewünschte Position.Das Interaktionsmodell variiert je nach Sprache. — C++ verwendet einmalig zufällig ausgewählte Ziele, während Python die Bewegung durch fortlaufende Tastatureingaben steuert.
Lerninhalte:
- Zur Positionsmodussteuerung
set_cursor_positionAnleitung zur Verwendung - Zwei unterschiedliche Interaktionsmodelle für denselben Basisbefehl
- Festklemmen an der Arbeitsbereichskugel an der Zielposition — „ Minverse “ verwendet einen kleineren Radius als „ Inverse3 “
- Die Voreinstellungen fürden Arbeitsbereich so festlegen, dass der Ursprung in der Mitte des Arbeitsbereichs liegt
Workflow
C++ (Random-Target-Modell)
- Zeilenweise gepufferte Tastatureingaben aus der Standardeingabe (
n、+、-、q) ausliest und einen Eingabe-Thread im Hintergrund startet. - WebSocket wird geöffnet. Im ersten Status-Frame wirdSitzungsprofil registrieren und
configure.preset: arm_front_centeredEinstellungen vornehmen. Erzeugen Sie das erste zufällige Ziel innerhalb der Kugel (Ausschluss-Sampling, Radius 0,08 m). - Bei jedem Tick im Verhältnis zum aktuellen Ziel
set_cursor_positionDer Befehl wird gesendet. Der Cursor folgt diesem reibungslos – die Ratenbegrenzung und Interpolation erfolgen auf der Serviceseite. - Der Benutzer
nWenn Sie „ und ENTER“ eingeben, generiert der Eingabe-Thread ein neues zufälliges Ziel.+/-und die Geschwindigkeit anzupassen,qund endet damit.
Python (Modell mit Verschiebung durch langes Drücken)
- WebSocket wird geöffnet. Im ersten Status-Frame
status.calibratedDies wird überprüft, und der Benutzer wird benachrichtigt, falls das Gerät noch nicht kalibriert wurde. config.typeauslesen und den Radius des Arbeitsbereichs bestimmen (minverse(in diesem Fall 0,04 m, ansonsten 0,10 m).- Sitzungsprofil registrieren und
configure.preset: arm_front_centeredeinstellen. - Bei jedem Tick wird der Zustand der Tastatur (
W/A/S/D/Q/E) abfragen und die Zielposition entlang jeder gedrückten AchseSPEEDNur so weit aktualisieren, im Arbeitsbereich einklemmen undset_cursor_positionsenden.RDamit setzen wir das Ziel wieder auf den Ausgangspunkt zurück.
Parameter
| Name | Standardwert (C++) | Standardwert (Python) | Verwendungszweck |
|---|---|---|---|
workspace_radius / RADIUS_INVERSE3 | 0.08 m | 0.10 m (Inverse3) / 0.04 m (Minverse) | Radius des Zielkörpers |
speed_step / SPEED | 0.01 / Drücken | 0.00005 m / Tick | Verschiebung pro Vorgang |
PRINT_EVERY_MS | — | 100 | Begrenzung des Telemetrie-Ausgabeintervalls (Python) |
| Sitzungsprofil | co.haply.inverse.tutorials:position-control | dasselbe | Haply Zur Identifizierung am Hub |
Die Varianten von Python stammen aus dem ersten Zustandsrahmen status.calibrated Es wird überprüft, ob das Gerät kalibriert ist, und der Benutzer wird benachrichtigt, falls dies nicht der Fall ist. Die C++-Variante geht davon aus, dass die Kalibrierung bereits abgeschlossen ist.
Feld „Lesezustand“
data.inverse3[0].device_id— Zur Erstellung von Befehlendata.inverse3[0].state.cursor_position— Telemetrie- (Python, nur der erste Frame)
data.inverse3[0].config.type— Wählen Sie aus, ob der Radius von „ Inverse3 “ oder „ Minverse “ verwendet werden soll - (Python, nur der erste Frame)
data.inverse3[0].status.calibrated— Wenn „false“, wird der Benutzer benachrichtigt
Senden und Empfangen
Kommunikations-Workflow
- C++ ist,
std::atomic<float>Führt einen Standard-Eingabe-Thread im Hintergrund aus, der die Zielwerte schreibt. Der WebSocket-Thread liest diese bei jedem Tick aus.nDurch Drücken von „ENTER“ generiert der Eingabe-Thread ein neues zufälliges Ziel, undqDamit sind beide Threads beendet. - Python Es handelt sich um eine single-threaded asynchrone Verarbeitung. Die WebSocket-Schleife fragt bei jedem Tick den Tastaturstatus ab und
positionwird direkt aktualisiert.
Die Nutzdaten der Inverse-API sind identisch. Beim ersten Tick werden das Sitzungsprofil und configure.preset senden und bei den folgenden Ticks set_cursor_position Ich sende nur das.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Es handelt sich um eine einzelne asynchrone Schleife. Abfrage der Tastatur (handle_keys) wird bei jedem Tick inline ausgeführt und verwendet keine Threads.config.type und status.calibrated wird nur einmal aus dem ersten Zustandsrahmen gelesen.
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))
Es handelt sich um ein Zwei-Thread-Modell. Ein Hintergrund-Thread liest die Standardeingabe ein, undstd::atomic<float> schreibt den Zielwert. Der I/O-Thread von libhv führt bei jedem Takt ws.onmessage ausführen und die atomare Variable auslesen.
// 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 Das Thread-Modell ist dasselbe. In den Befehlen werden typisierte Strukturen verwendet.std::optional<device_configure> wird jedoch als einmalige Voreinstellung pro Gerät gespeichert und bei nachfolgenden Ticks aus der JSON-Datei weggelassen.
// 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);
Das Tutorial 05 wurde ebenfalls zusammen mit dem SDK lokal installiert. Im Installationsverzeichnis des Dienstes unter tutorials/05-haply-inverse-position-control/ Siehe dazu.
Quelle: Python · C++ · C++ Glaze
Siehe auch: Steuerbefehle (set_cursor_position) · Halterung und Arbeitsbereich (Voreinstellung) · Typ (vec3) · Tutorial 06 (Kombiniert)