06. Kombiniert (Inverse3 Wireless VerseGrip)
Anleitung für zwei Geräte: Richten Sie den Griff aus und halten Sie eine Taste gedrückt, um den Inverse3 in diese Richtung zu bewegen. Der Cursor ist auf einen kugelförmigen Arbeitsbereich beschränkt.
Was Sie lernen werden:
- Das Auslesen von zwei Gerätetypen im selben Statusrahmen (
inverse3undwireless_verse_grip) - Ermitteln der Ausrichtungsrichtung des Griffs anhand seiner Quaternion (lokal
+YAchse) - Verwendung von
set_cursor_positionden Cursor auf ein berechnetes Ziel zu bewegen - Das Ziel in einen sicheren Arbeitsbereich einbetten – Minverse einen kleineren Radius als Inverse3
- Festlegen einer Arbeitsbereichsvoreinstellung (
arm_front_centered), sodass der Ursprung in der Mitte der Strecke liegt
Arbeitsablauf
- Entdecken Sie beide Geräte:
- C++ Abfrage nach Varianten
GET /devicesbeim Start über HTTP, dann eine Kalibrierungsaufforderung anzeigen und auf die Eingabetaste warten. - Python liest beide Geräte-IDs aus dem ersten WebSocket-Status-Frame aus.
- C++ Abfrage nach Varianten
- Registrieren Sie die Sitzungsprofil und festlegen
configure.preset: arm_front_centeredbei der ersten Nachricht (Einmal-Handshake). - Bei jedem Ticken: den Griff lesen
orientationundbuttons.{a, b}Zustand. - Wenn eine Bewegungs-Taste gedrückt gehalten wird, berechne die Richtung des Griffs im Weltraum (
R(q) · ĵ— die gedrehte Einheit um die Y-Achse) und summieren sie an der Zielposition, skaliert umSPEED. - Klemmen Sie das Ziel innerhalb der Arbeitsbereichskugel fest und senden Sie es über
set_cursor_position. - (Nur Python) Passe den Radius der Kugel an die Geräteeinstellungen an
config.type—minverse= 0,04 m, alles andere = 0,10 m.
Parameter
| Name | Standard | Zweck |
|---|---|---|
SPEED | 0.01 m/Tick | Bewegungsschritt, während eine Taste gedrückt gehalten wird |
RADIUS_INVERSE3 | 0.10 m | Arbeitsbereichsklemmradius für Inverse3 Inverse3x |
RADIUS_MINVERSE | 0.04 m | Arbeitsbereichsklammerradius für Minverse nur Python – in C++ fest codiert) 0.10) |
PRINT_EVERY_MS | 200 | Telemetrie-Drosselklappe |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:combined | Identifiziert diese Simulation im Haply |
- Lassen Sie den Inverse3 (oder setzen Sie den Griff auf das Tintenfass und warten Sie, bis die LED dauerhaft leuchtet).
- Nehmen Sie den Griff vom Tintenfass ab.
- Halten Sie A oder B gedrückt und drehen Sie den Griff – der Cursor bewegt sich in die Richtung, in die der Griff zeigt.
Statusfelder gelesen
Aus dem Zustandsrahmen pro Takt:
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 das erste Bild)
data.inverse3[0].config.type— wählt Minverse von Inverse3 Minverse aus - (Python, nur das erste Bild)
data.inverse3[0].status.calibrated— fordert den Benutzer auf, wenn der Wert „false“ ist
Senden / Empfangen
Die mathematische Umwandlung von Quaternionen in Richtungen (Drehen +Y von R(q)) und die Sphärenklemme sind klassische Elemente der linearen Algebra – siehe die Quelldateien. Die Inverse-API-Seite umfasst den Handshake sowie die pro Tick set_cursor_position.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzelne asynchrone Schleife. Python liest beide Geräte-IDs aus dem ersten Status-Frame; der Handshake fügt das Profil hinzu + configure.preset: arm_front_centered zum ersten 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 beide Geräte-IDs über GET /devices beim Start (HTTP) und öffnet anschließend den WebSocket. onmessage wird auf dem E/A-Thread von libhv ausgeführt; der Hauptthread wird bei ENTER blockiert.
// 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-Callback-Modell. Typisierte Strukturen modellieren sowohl Status-Frames als auch den ausgehenden Befehl – zwei std::vector<> in devices_message einen einzigen glz::read beide Gerätetypen liefern.
// 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
Quelle: Python · C++ · C++ Glaze
Siehe auch: Steuerbefehle (set_cursor_position) · Typen (Quaternion, vec3) · Mount & Arbeitsbereich (Voreinstellungen) · Tutorial 03 (Wireless VG) · Lernprogramm 05 (Positionssteuerung)