05. Positionssteuerung
durch set_cursor_position Bewegen Sie den Cursor unter Inverse3 an die gewünschte Position.Die Interaktionsmodelle unterscheiden sich je nach Sprache.——C++ verwendet einmalige zufällige Ziele, Python nutzt eine kontinuierliche, tastaturgesteuerte Bewegung.
Lerninhalte:
- Verwendung
set_cursor_positionRealisierung der Positionsregelung - Zwei verschiedene Interaktionsmodelle, die demselben Basisbefehl entsprechen
- Das Ziel wird auf die Kugel im Arbeitsbereich beschränkt – der vonMinverse verwendete Radius ist kleiner als der von Inverse3
- Richten Sie eine Arbeitsbereichsvoreinstellung so ein, dass der Ursprung in der Mitte des Arbeitsbereichs liegt.
Arbeitsablauf
C++ (Modell mit zufälligen Zielen)
- Starte einen Hintergrund-Eingabethread, der zeilenweise gepufferte Tasteneingaben aus der Standardeingabe (stdin) liest (
n、+、-、q). - WebSocket aktivieren. Bei Eintreffen der ersten Frame-Statusdaten registrieren Sitzungsprofil, und legen Sie Folgendes fest:
configure.preset: arm_front_centered。Erzeuge das erste zufällige Ziel innerhalb der Kugel (Reject-Sampling, Radius 0,08 m). - In jedem Zyklus wird eine Nachricht an das aktuelle Ziel gesendet
set_cursor_positionBefehl. Der Cursor folgt dem Ziel sanft – der Server begrenzt die Geschwindigkeit und führt eine Interpolation durch. - Wenn der Benutzer Folgendes eingibt
nWenn Sie die ENTER-Taste drücken, generiert der Eingabe-Thread ein neues zufälliges Ziel.+/-Geschwindigkeit anpassen;qBeenden.
Python (Modell durch Gedrückthalten verschieben)
- WebSocket aktivieren. Bei Eintreffen der ersten Frame-Statusdaten prüfen, ob
status.calibrated——Wenn das Gerät noch nicht kalibriert wurde, wird der Benutzer darauf hingewiesen. - Lesen
config.type, um den Radius des Arbeitsbereichs auszuwählen (minversebeträgt 0,04 m, in allen anderen Fällen 0,10 m). - Registrieren Sitzungsprofil, und legen Sie Folgendes fest:
configure.preset: arm_front_centered。 - In jedem Zyklus: Abfrage des Tastaturstatus (
W/A/S/D/Q/E), entlang der Richtung jeder gedrückten Achse drückenSPEEDDie Zielposition aktualisieren, sie auf den Arbeitsbereichskugel begrenzen und sendenset_cursor_position。Drücken SieRDas Ziel kann auf den Ursprung zurückgesetzt werden.
Parameter
| Bezeichnung | Standardwert (C++) | Standardwert (Python) | Verwendungszweck |
|---|---|---|---|
workspace_radius / RADIUS_INVERSE3 | 0.08 m | 0.10 m (Inverse3) / 0.04 m (Minverse) | Radius der Zielkugel |
speed_step / SPEED | 0.01 / bei jedem Tastendruck | 0.00005 m / pro Zyklus | Schrittweite pro Interaktion |
PRINT_EVERY_MS | — | 100 | Fernsteuerung der Drosselung (Python) |
| Sitzungsprofil | co.haply.inverse.tutorials:position-control | gleich | Zur Identifizierung im „ Haply Hub“ |
Python-Instanzen werden beginnend mit den Zustandsdaten des ersten Frames überprüft. status.calibrated,Wenn das Gerät nicht kalibriert ist, wird der Benutzer darauf hingewiesen. Die C++-Variante geht davon aus, dass die Kalibrierung bereits abgeschlossen ist.
Gelesene Statusfelder
data.inverse3[0].device_id——Zur Erstellung von Befehlendata.inverse3[0].state.cursor_position——Fernmessdaten- (Python, nur das erste Bild)
data.inverse3[0].config.type——Zur Auswahl des Radius zwischen Inverse3 und Minverse - (Python, nur das erste Bild)
data.inverse3[0].status.calibrated——Wenn der Wert „false“ ist, wird der Benutzer darauf hingewiesen.
Senden / Empfangen
Kommunikationsablauf
- C++ Einen Hintergrund-stdin-Thread ausführen, der Folgendes schreibt:
std::atomic<float>Zielwerte vom Typ; der WebSocket-Thread liest diese Werte in jedem Zyklus aus. EingabenWenn Sie die ENTER-Taste drücken, generiert der Eingabethread ein neues zufälliges Ziel; EingabeqIn diesem Fall werden beide Threads beendet. - Python Eine single-threaded asynchrone Implementierung – Die WebSocket-Schleife fragt in jedem Zyklus den Tastaturstatus ab und aktualisiert ihn direkt.
position。
Die Datenlast der Inverse-API ist identisch: Im ersten Zyklus werden das Sitzungskonfigurationsprofil und configure.preset, in den folgenden Zyklen werden nur set_cursor_position。
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzelne asynchrone Schleife. Tastaturabfrage (handle_keys) wird in jedem Zyklus inline ausgeführt, ohne Threads zu verwenden.config.type und status.calibrated Wird nur einmal aus den Zustandsdaten des ersten Frames 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))
Zweithread-Modell: Der Hintergrund-Thread liest aus stdin und schreibt in stdin std::atomic<float> Zielwert des Typs; der libhv-I/O-Thread läuft in jedem Zyklus ws.onmessage, und diese atomaren Variablen 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);
Das gleiche Zwei-Thread-Modell. Der Befehl verwendet stark typisierte Strukturen –std::optional<device_configure> Speichern Sie für jedes Gerät eine einmalige Voreinstellung; in nachfolgenden Zyklen wird diese 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);
Tutorial 05 wird ebenfalls zusammen mit dem SDK lokal installiert – bitte im Installationsverzeichnis des Dienstes unter tutorials/05-haply-inverse-position-control/ dort suchen.
Quellcode: Python · C++ · C++ Glaze
Verwandte Themen: Steuerbefehle (set_cursor_position) · Einbindungen und Arbeitsbereiche (Voreinstellungen) · Typ (vec3) · Tutorial 06 (Kombinationen)