06. Integrationsbeispiel (Inverse3 + kabelloser VerseGrip)
Dies ist eine Anleitung für die Verwendung von zwei Geräten. Wenn Sie den Griff in die gewünschte Richtung ausrichten und die Taste gedrückt halten, bewegt sich der Cursor in diese Richtung Inverse3 . Der Cursor ist auf den Bereich innerhalb des kugelförmigen Arbeitsbereichs beschränkt.
Lerninhalte:
- Lesen von zwei Gerätetypen im selben Status-Frame (
inverse3sowiewireless_verse_grip) - QuaternionDie Richtung ermitteln, in die der Griff zeigt (lokal
+Y(Glückwunsch) set_cursor_positionDen Cursor auf den mithilfe von … berechneten Zielpunkt bewegen- Den Zielpunkt auf den sicheren Arbeitsbereich beschränken — Minverse verwendet einen kleineren Radius als Inverse3
- so, dass der Ursprung in der Mitte des Erfassungsbereichs liegt Arbeitsbereichsvoreinstellungen (
arm_front_centered) Einrichten
Arbeitsablauf
- Zwei Geräte suchen:
- C++ Die Version wird beim Start über HTTP abgerufen
GET /devicesruft diese ab, gibt anschließend eine Kalibrierungsanleitung aus und wartet auf die Eingabe der ENTER-Taste. - Pythonliest im ersten WebSocket-Status-Frame beide Geräte-IDs aus.
- C++ Die Version wird beim Start über HTTP abgerufen
- In der ersten Nachricht Sitzungsprofilregistrieren und
configure.preset: arm_front_centeredwird eingestellt (einmaliger Handshake). - Bei jedem Schlag der
orientationsowiebuttons.{a, b}Liest den Status aus. - Wenn die Bewegungstaste gedrückt gehalten wird, richtet sich die Ausrichtung des Griffs im Weltraum (
R(q) · ĵ— die gedrehte Einheit (um die Y-Achse) berechnen und dieseSPEEDauf die Zielposition skalieren und dort akkumulieren. - Nachdem der Zielpunkt auf den Bereich innerhalb des Arbeitsraums beschränkt wurde,
set_cursor_positionwird darüber übertragen. - (Gilt nur für Python) Das Gerät
config.typePassen Sie den Radius des Kreises entsprechend an —minversebeträgt 0,04 m, ansonsten 0,10 m.
Parameter
| Name | Standardwert | Verwendungszweck |
|---|---|---|
SPEED | 0.01 m/Tick | Bewegungsabstand, solange die Taste gedrückt gehalten wird |
RADIUS_INVERSE3 | 0.10 m | Inverse3 / Begrenzter Radius des Arbeitsbereichs von Inverse3x |
RADIUS_MINVERSE | 0.04 m | Der begrenzte Radius des Arbeitsbereichs von Minverse (gilt nur für Python – C++ ist 0.10(als Fixpunkt) |
PRINT_EVERY_MS | 200 | Begrenzung der Telemetrie-Ausgabeintervalle |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:combined | Haply Der Wert, der diese Simulation im Hub identifiziert |
- Lassen Sie Inverse3 die automatische Kalibrierung durchführen (oder legen Sie den Griff auf das Tintenfass und warten Sie, bis die LED aufleuchtet).
- Trennen Sie den Griff vom Tintenfass.
- Drehen Sie den Griff, während Sie die Taste A oder B gedrückt halten – der Cursor bewegt sich in die Richtung, in die der Griff zeigt.
Feld „Lesestatus“
Lesen Sie die folgenden Werte aus dem Status-Frame, der bei jedem Tick übertragen wird:
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 im ersten Frame)
data.inverse3[0].config.type— Wählen Sie einen der Radien „Inverse3“ und „ Minverse “ aus. - (Python, nur im ersten Frame)
data.inverse3[0].status.calibrated— Wenn der Wert „false“ ist, wird der Benutzer darüber informiert.
Senden / Empfangen
Berechnung der Richtung anhand von Quaternionen (R(q)로 +Y(Rotationsoperationen) und die Verarbeitung der internen Begrenzungen der Kugel sind typische lineare algebraische Operationen. Weitere Informationen finden Sie in der Quelldatei. Auf der Seite der Inverse-API werden bei jedem Handshake und jedem Tick set_cursor_positionbesteht aus.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Es besteht aus einer einzigen asynchronen Schleife. Python liest im ersten Status-Frame die beiden Geräte-IDs. Beim Handshake wird zunächst set_cursor_position Profil beim Aufruf und configure.preset: arm_front_centeredfüge ich hiermit bei.
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++ startet beim Start GET /devicesNach dem Abrufen der beiden Geräte-IDs über HTTP wird eine WebSocket-Verbindung hergestellt. onmessagewird im I/O-Thread von libhv ausgeführt, und der Hauptthread wird bis zur Eingabe der ENTER-Taste 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
Das Callback-Modell von libhv ist identisch. Sowohl der Status-Frame als auch der Sendebefehl werden als typisierte Struktur (typed struct) modelliert. devices_message Die beiden darin befindlichen std::vector<> Dank dessen ein einziges glz::read Mit diesem Aufruf 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
Tutorial 06 wird zusammen mit dem SDK auch lokal installiert – im Installationsverzeichnis des Dienstes unter tutorials/06-haply-inverse-combined/ Sie finden diese im Ordner.
Quelle: Python · C++ · C++ Glaze
Verwandte Themen: Steuerbefehl (set_cursor_position) · Typ (Quaternion, vec3) · Halterungen und Arbeitsbereiche (Voreinstellungen) · Tutorial 03 (Drahtloses VG) · Tutorial 05 (Positionssteuerung)