03. Wireless VerseGrip ausgeben
Gleiches Muster wie Tutorial 02, jedoch für ein Wireless VerseGrip — ergänzt Tasten (A/B/C) und Akkustand im gestreamten Zustand.
Was Sie lernen:
- Auslesen der wireless-spezifischen Zustandsfelder:
buttons.{a,b,c},battery_level,hall - Verwendung von
probe_orientationals Keepalive für einen eigenständigen Beobachter - Dasselbe Handshake-Muster wie in Tutorial 02, bei dem nur die erste Nachricht die Sitzungskonfiguration enthält
Ablauf
- Öffnen Sie eine Verbindung über WebSocket zu
ws://localhost:10001und warten Sie auf die erste Zustands-Nachricht. - Wählen Sie die
device_iddes ersten Wireless VerseGrip aus dem Arraywireless_verse_grip. - Erstellen Sie eine Anfrage mit dem Sitzungsprofil und einem gerätespezifischen Keepalive per
probe_orientation. - Senden Sie die Anfrage und entfernen Sie danach das Feld
session— es ist ein einmaliger Handshake. - Konvertieren Sie bei jedem weiteren Frame das Quaternion in Euler-Winkel und geben Sie gedrosselte Telemetriedaten aus, einschließlich Tastenzuständen und Akkustand. Senden Sie den Keepalive bei jedem Takt erneut.
Parameter
| Name | Standard | Zweck |
|---|---|---|
URI | ws://localhost:10001 | URL des Simulationskanals WebSocket |
PRINT_EVERY_MS | 100 | Drosselung der Konsolenausgabe |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-wireless-verse-grip | Identifiziert diese Simulation in Haply Hub |
Die Umrechnung erfolgt intrinsisch in der Reihenfolge Z-X-Y (Gieren → Nicken → Rollen) im Anwendungskoordinatensystem +X right, +Y forward, +Z up. Verwenden Sie nicht glm::eulerAngles — es folgt einer anderen Konvention und liefert hier falsche Werte. Alle drei Sprachvarianten implementieren dieselbe Mathematik; die Formel finden Sie in den Quelltexten.
probe_orientation tatsächlich benötigt wirdprobe_orientation ist nur dann sinnvoll, wenn Ihre Sitzung keinen Befehl an Inverse3 sendet. Sobald Sie Inverse3 ansteuern (Kraft, Position, Drehmoment ...), streamt der Dienst die Orientierung des gekoppelten VerseGrip automatisch in jedem Zustands-Frame — ein Keepalive ist dann nicht nötig. Verwenden Sie probe_orientation nur für eigenständige Grip-Überwachungswerkzeuge wie dieses Tutorial.
Gelesene Zustandsfelder
Aus data.wireless_verse_grip[0].state:
orientation—quaternion(w, x, y, z)hall— ganzzahliger Messwert des Hall-Sensorsbuttons.a,buttons.b,buttons.c— Boolesche Wertebattery_level— Gleitkommazahl (0.0 – 1.0)transform.rotation— Rotation des Arbeitsbereichs (quaternion); Position und Skalierung entfallen bei einem Gerät, das nur die Orientierung liefert, und werden nie übertragen
transform.rotation wird weggelassen, wenn es dem Identitäts-Quaternion {w:1,x:0,y:0,z:0} entspricht. Hinterlegen Sie beim Auslesen einen Standardwert. Aktivieren Sie serialization/explicit_fields, um es immer zu erhalten.
Senden / Empfangen
Gleicher Aufbau wie in Tutorial 02, lediglich mit dem Geräte-Array wireless_verse_grip. Die Schleife von WebSocket empfängt einen Zustands-Frame und sendet den Handshake sowie den Keepalive per probe_orientation zurück; die erste ausgehende Nachricht enthält das Sitzungsprofil, jeder weitere Frame enthält nur den Keepalive.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzelne asynchrone Schleife.
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["wireless_verse_grip"][0]["device_id"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-wireless-verse-grip"}}},
"wireless_verse_grip": [{
"device_id": device_id,
"commands": {"probe_orientation": {}} # empty — keepalive
}]
}
await websocket.send(json.dumps(request_msg))
request_msg.pop("session", None) # one-shot handshake
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["wireless_verse_grip"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-wireless-verse-grip"}}}}}}},
{"wireless_verse_grip", json::array({
{{"device_id", device_id},
{"commands", {{"probe_orientation", json::object()}}}},
})},
};
}
ws.send(request_msg.dump());
request_msg.erase("session"); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Typisierte Structs. button_state wird bewusst ausdrücklich benannt (nicht buttons), um eine Verdeckung des Felds buttons in wvg_state zu vermeiden. Glaze ordnet Felder anhand ihres Namens zu, daher ist der Name des Struct-Typs frei wählbar.
// 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{};
float battery_level{};
};
struct wvg_device { std::string device_id; wvg_state state; };
struct devices_message { std::vector<wvg_device> wireless_verse_grip; };
struct probe_orientation_cmd {}; // empty object on the wire
struct commands_message {
std::optional<session_cmd> session; // one-shot — omitted when unset
std::vector<device_commands> wireless_verse_grip;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
if (first_message) {
first_message = false;
request_msg.session = session_cmd{ /* profile = print-wireless-verse-grip */ };
device_commands dc{ .device_id = data.wireless_verse_grip[0].device_id };
dc.commands.probe_orientation = probe_orientation_cmd{};
request_msg.wireless_verse_grip.push_back(std::move(dc));
}
std::string out_json;
(void)glz::write_json(request_msg, out_json);
ws.send(out_json);
request_msg.session.reset(); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Kommandozeilen-Flags (Python)
Die Variante Python akzeptiert dieselben zwei Flags wie Tutorial 01:
--full— gibt die rohe JSON-Nutzlast formatiert aus, statt der einzeiligen Zusammenfassung.--query-config— fügt bei jedem ausgehenden Ticksession.force_render_full_stateerneut hinzu, sodass jeder Frame denconfig-Block statt nur der Zustandsänderung enthält.
--query-config ist besonders nützlich in der drahtlosen Variante von VerseGrip, da der config-Block zusätzlich zu den regulären Konfigurationsfeldern das WVG-spezifische Feld sub_type (stylus / prototype / ruko / kingfisher, ab 3.5 hinzugekommen) enthält. Wenn Sie python 03-haply-inverse-print-wireless-verse-grip.py --full --query-config ausführen, können Sie live verfolgen, wie sich config.sub_type zusammen mit den übrigen Konfigurationswerten aktualisiert.
Die Varianten C++ stellen diese Flags nicht bereit.
Tutorial 03 wird außerdem lokal mit dem SDK installiert – zu finden unter tutorials/03-haply-inverse-print-wireless-verse-grip/ im Installationsverzeichnis des Dienstes.
Quelle: Python · C++ · C++ Glaze
Verwandte Themen: Tutorial 02 (kabelgebundenes VG) · Typen (Quaternion) · Steuerbefehle (probe_orientation) · WebSocket-Protokoll