06. Kombipaket (Inverse3 + Wireless VerseGrip)
Anleitung für zwei Geräte: Richten Sie den Griff aus und halten Sie eine Taste gedrückt, um den Cursor Inverse3 in diese Richtung zu bewegen. Der Cursor ist auf einen kugelförmigen Arbeitsbereich beschränkt.
Was Sie lernen werden:
- Zwei Gerätetypen im selben Statusrahmen auslesen (
inverse3etwireless_verse_grip) - Die Ausrichtungsrichtung des Griffs aus dessen Position ermitteln Quaternion (Axe lokal
+Y) - Verwenden
set_cursor_positionum den Cursor auf ein berechnetes Ziel zu bewegen - Das Ziel auf einen sicheren Arbeitsbereich beschränken — Minverse verwendet einen kleineren Radius als Inverse3
- Ein Voreinstellung des Arbeitsbereichs (
arm_front_centered), damit der Ursprung in der Mitte des Notensystems liegt
Ablauf
- Beide Geräte erkennen:
- Die Varianten C++ Fragender
GET /devicesbeim Start über HTTP, zeigen anschließend eine Kalibrierungsaufforderung an und warten darauf, dass der Benutzer die Eingabetaste drückt. - Python liest die Identifikatoren der beiden Geräte aus dem ersten WebSocket-Status-Frame aus.
- Die Varianten C++ Fragender
- Speichern Sie das Sitzungsprofil und legen Sie fest
configure.preset: arm_front_centeredin der ersten Nachricht (einmalige Kontaktaufnahme). - Lesen Sie bei jedem Zyklus die
orientationdes Griffs und der Zustand vonbuttons.{a, b}. - Wenn eine Bewegungstaste gedrückt wird, berechnen Sie die Richtung des Griffs im Weltraum (
R(q) · ĵ— die gedrehte +Y-Einheitsachse), multiplizieren Sie diese mitSPEEDund fügen Sie sie an der Zielposition ein. - Beschränken Sie das Ziel auf den Arbeitsbereich und senden Sie es über
set_cursor_position. - (Nur Python) Passen Sie den Radius der Kugel entsprechend dem
config.typedes Geräts —minverse= 0,04 m, alles andere = 0,10 m.
Einstellungen
| Nom | Standardwert | Rolle |
|---|---|---|
SPEED | 0.01 Motorrad | Keine Bewegung, solange eine Taste gedrückt wird |
RADIUS_INVERSE3 | 0.10 m | Einschränkungsradius des Arbeitsbereichs für „ Inverse3 “ / „Inverse3x“ |
RADIUS_MINVERSE | 0.04 m | Einschränkungsradius des Arbeitsbereichs für „ Minverse “ (nur Python – C++ behoben) 0.10 (in harter Ausführung) |
PRINT_EVERY_MS | 200 | Begrenzung der Telemetriedatenrate |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:combined | Identifiziere diese Simulation im „ Haply “-Hub |
- Lassen Sie den „ Inverse3 “ sich automatisch kalibrieren (oder legen Sie den Griff auf das Tintenfass und warten Sie, bis die LED dauerhaft leuchtet).
- Lösen Sie den Griff vom Tintenfass.
- Halten Sie die Taste A oder B gedrückt und drehen Sie den Griff – der Cursor bewegt sich in die Richtung, in die der Griff zeigt.
Meister der Les-États
Ausgehend vom Zustandsdiagramm jedes Zyklus:
data.inverse3[0].state.cursor_position—vec3data.wireless_verse_grip[0].state.orientation—quaterniondata.wireless_verse_grip[0].state.buttons.{a, b, c}— Boolesche Werte- (Python, nur erster Rahmen)
data.inverse3[0].config.type— wählt den Radius von „ Inverse3 “ oder den von Minverse - (Python, nur erster Rahmen)
data.inverse3[0].status.calibrated— fordert den Benutzer auf, eine Kalibrierung durchzuführen, wenn der Wert falsch ist
Versand und Empfang
Die Berechnung der Richtung anhand des Quaternions (Drehung von +Y par R(q)) und die Sphärenbeschränkung gehören zur klassischen linearen Algebra – siehe Quelldateien. Was die Inverse-API betrifft, handelt es sich um die erste Kontaktaufnahme und um set_cursor_position in jedem Zyklus.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzigartige asynchrone Schleife. Python liest die Identifikatoren beider Geräte aus dem ersten Status-Frame aus; bei der Kontaktaufnahme wird das Profil angehängt und configure.preset: arm_front_centered zu Beginn set_cursor_position.
async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)
if first_message:
first_message = False
inverse3_id = data["inverse3"][0]["device_id"]
grip_id = data["wireless_verse_grip"][0]["device_id"]
radius = get_workspace_radius(data["inverse3"][0].get("config", {}))
# Handshake: profile + preset + first position command
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": inverse3_id,
"configure": {"preset": {"preset": "arm_front_centered"}},
"commands": {"set_cursor_position": {"position": position}},
}],
}
else:
# Per tick: update position from grip pointing direction (classic math, not shown), send
request_msg = {
"inverse3": [{
"device_id": inverse3_id,
"commands": {"set_cursor_position": {"position": position}},
}],
}
await websocket.send(json.dumps(request_msg))
C++ ermittelt die Kennungen der beiden Geräte über GET /devices beim Start (HTTP) und öffnet anschließend WebSocket. onmessage wird auf dem E/A-Thread von libhv ausgeführt; der Hauptthread bleibt in der Warteschlange für EINGABE hängen.
// Startup (synchronous):
const std::string inv3_device_id = get_first_device_id("inverse3");
const std::string grip_device_id = get_first_device_id("wireless_verse_grip");
// Per tick:
ws.onmessage = [&](const std::string &msg) {
json data = json::parse(msg);
// ... classic math (not shown): update local Inverse3State + WirelessVerseGripState,
// compute new position from grip orientation, clamp to sphere ...
json command;
command["inverse3"] = json::array();
command["inverse3"].push_back({
{"device_id", inv3_device_id},
{"commands", {{"set_cursor_position",
{{"position", {{"x", pos.x}, {"y", pos.y}, {"z", pos.z}}}}}}}
});
if (first_message) {
first_message = false;
command["session"] = {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:combined"}}}}}};
command["inverse3"][0]["configure"] = {
{"preset", {{"preset", "arm_front_centered"}}}};
}
ws.send(command.dump());
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Dasselbe libhv-Rufmuster. Typisierte Strukturen modellieren sowohl die Zustandsrahmen als auch die ausgehende Steuerung – beides std::vector<> dans devices_message ermöglichen es einem einzigen glz::read beide Gerätetypen herzustellen.
// 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{}; };
struct wvg_device { std::string device_id; wvg_state state; };
struct inverse_state { vec3 cursor_position{}, cursor_velocity{}; };
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message {
std::vector<inverse_device> inverse3;
std::vector<wvg_device> wireless_verse_grip;
};
struct set_cursor_position_cmd { vec3 position; };
struct commands_message {
std::optional<session_cmd> session;
std::vector<device_commands> inverse3;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
const auto &wvg = data.wireless_verse_grip[0].state;
cursor_pos = data.inverse3[0].state.cursor_position;
// ... classic math (not shown): if (wvg.buttons.a || wvg.buttons.b)
// move in pointing dir; clamp to sphere ...
commands_message out_cmds{};
device_commands dc{ .device_id = inv3_device_id };
dc.commands.set_cursor_position = set_cursor_position_cmd{cursor_pos};
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = combined */ };
dc.configure = device_configure{ .preset = preset_cfg{"arm_front_centered"} };
}
out_cmds.inverse3.push_back(std::move(dc));
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Das Tutorial 06 wird ebenfalls lokal mit dem SDK installiert – siehe tutorials/06-haply-inverse-combined/ im Installationsverzeichnis des Dienstes.
Quelle: Python · C++ · C++ Glaze
Siehe auch: Steuerbefehle (set_cursor_position) · Typen (Quaternion, vec3) · Montage und Arbeitsbereich (Voreinstellungen) · Tutorial 03 (Wireless VG) · Tutorial 05 (Positionssteuerung)