06. Integration (Inverse3 + kabelloser VerseGrip)
Dies ist ein Tutorial für zwei Geräte. Wenn Sie den Griff in eine Richtung ausrichten und die Taste gedrückt halten, bewegt sich der Cursor von „ Inverse3 “ in diese Richtung. Der Cursor ist auf den kugelförmigen Arbeitsbereich beschränkt.
Lerninhalte:
- Zwei Arten von Geräten innerhalb desselben State-Frames (
inverse3undwireless_verse_grip) auslesen - Quaternion Ausgehend von der Ausrichtung des Griffs (lokal)
+Y(Welle) herausnehmen set_cursor_positionden Cursor auf das berechnete Ziel bewegen- Das Ziel auf den sicheren Arbeitsbereich beschränken. „Minverse “ verwendet einen Radius, der kleiner ist als „ Inverse3 “.
- Voreinstellungen für den Arbeitsbereich(
arm_front_centered) einstellen, sodass der Ursprung in der Mitte des Erreichbarkeitsbereichs liegt
Workflow
- Beide Geräte werden erkannt:
- C++ Die Version wird beim Start über HTTP
GET /devicesruft diese ab, zeigt die Kalibrierungsaufforderung an und wartet auf die Eingabe von ENTER. - Python liest die IDs beider Geräte aus dem ersten WebSocket-Status-Frame aus.
- C++ Die Version wird beim Start über HTTP
- In der ersten Nachricht Sitzungsprofil registrieren und
configure.preset: arm_front_centeredEinrichten (Einmaliger Handshake). - Bei jedem Tick wird der Griff
orientationundbuttons.{a, b}Liest den Status aus. - Wenn die Verschiebungstaste gedrückt ist, die Richtung im Weltraum des Griffs (
R(q) · ĵ, die Einheit nach der Drehung (+Y-Achse) berechnen und,SPEEDDer skalierte Wert wird zur Zielposition addiert. - Das Ziel auf den Arbeitsbereichsbereich beschränken und,
set_cursor_positionund sende es ab. - (nur Python) Das Gerät
config.typePassen Sie den Radius der Kugel entsprechend an.minverseIn diesem Fall beträgt sie 0,04 m, ansonsten 0,10 m.
Parameter
| Name | Standard | Verwendungszweck |
|---|---|---|
SPEED | 0.01 m/Tick | Bewegungsschritte während des Tastendrucks |
RADIUS_INVERSE3 | 0.10 m | Inverse3 / Begrenzungsradius des Arbeitsbereichs für Inverse3x |
RADIUS_MINVERSE | 0.04 m | Minverse Begrenzungsradius des Arbeitsbereichs (nur Python – C++ ist 0.10 (fest) |
PRINT_EVERY_MS | 200 | Begrenzung der Übertragungshäufigkeit von Telemetriedaten |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:combined | Haply Diese Simulation im Hub identifizieren |
- Inverse3 Führen Sie eine Selbstkalibrierung durch (oder legen Sie den Griff in das Tintenfass und warten Sie, bis die LED aufleuchtet).
- Entfernen 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.
Das Feld „state“ auslesen
Aus dem „state“-Frame jedes Ticks wird Folgendes ausgelesen:
data.inverse3[0].state.cursor_position—vec3data.wireless_verse_grip[0].state.orientation—quaterniondata.wireless_verse_grip[0].state.buttons.{a, b, c}— Wahr-Falsch-Wert- (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 dem Benutzer eine Eingabeaufforderung angezeigt
Senden / Empfangen
Berechnung der Richtung anhand von Quaternionen (R(q) de +Y (Drehung) und die Klemmung mittels Kugeln sind klassische Berechnungen der linearen Algebra. Einzelheiten finden Sie in der Quelldatei. Auf der Seite der Inverse-API sind der Handshake und bei jedem Tick set_cursor_position ruft auf.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Es handelt sich um eine einzelne asynchrone Schleife. Python liest die IDs beider Geräte aus dem ersten State-Frame aus. Beim Handshake wird zunächst der set_cursor_position mit Profil und configure.preset: arm_front_centered wird gewährt.
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++ wird beim Start GET /devices Dabei werden die IDs beider Geräte abgerufen (über HTTP) und anschließend ein WebSocket geöffnet.onmessage Es wird auf dem E/A-Thread von libhv ausgeführt, und der Hauptthread wird blockiert, während er auf die Eingabe von „ENTER“ wartet.
// 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
Das Callback-Modell von libhv ist dasselbe. Es handelt sich um eine typisierte Struktur, die sowohl den State-Frame als auch den zu sendenden Befehl darstellt.devices_message die beiden darin std::vector<> dadurch wird ein einziges glz::read Damit können Sie beide Gerätetypen abrufen.
// 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 ist ebenfalls zusammen mit dem SDK lokal installiert. Im Installationsverzeichnis des Dienstes unter tutorials/06-haply-inverse-combined/ Siehe dazu.
Quelle: Python · C++ · C++ Glaze
Siehe auch: Steuerbefehle (set_cursor_position) · Typ (Quaternion, vec3) · Halterung und Arbeitsbereich (Voreinstellung) · Tutorial 03 (Wireless VG) · Tutorial 05 (Positionssteuerung)