03. Wireless VerseGrip ausgeben
Gleiches Muster wie in Tutorial 02, jedoch für einen Wireless VerseGrip – ergänzt um Tasten (A/B/C) und Akkustand im gestreamten Zustand.
Was Sie lernen:
- Auslesen der drahtlos-spezifischen Zustandsfelder:
buttons.{a,b,c},battery_level,hall - Verwendung von
probe_orientationals Keepalive für einen unabhängigen Beobachter - Das gleiche Handshake-Muster wie in Tutorial 02, bei dem nur die erste Nachricht die Sitzungskonfiguration enthält
Ablauf
- Stellen Sie eine Verbindung über WebSocket zu
ws://localhost:10001und warten Sie auf die erste Statusmeldung. - 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 über
probe_orientation. - Senden Sie die Anfrage und entfernen Sie anschließend das Feld
session— Es ist ein einmaliger Handschlag. - 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 WebSocket-Simulationskanals |
PRINT_EVERY_MS | 100 | Drosselung der Konsolenausgabe |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-wireless-verse-grip | Diese Simulation ist im „ Haply Hub“ zu finden. |
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 — 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 ...), überträgt der Dienst automatisch die Ausrichtung des gekoppelten VerseGrip in jedem Status-Frame – ein Keepalive ist dann nicht erforderlich. Verwenden Sie probe_orientation nur für eigenständige Grip-Überwachungstools wie dieses Tutorial.
Ausgelesene 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— Drehung des Arbeitsbereichs (quaternion); Position und Skalierung spielen bei einem Gerät, das nur die Ausrichtung liefert, keine Rolle und werden niemals übertragen
transform.rotation wird weggelassen, wenn es dem Identitäts-Quaternion {w:1,x:0,y:0,z:0} entspricht. Legen Sie beim Auslesen einen Standardwert fest. Aktivieren Sie serialization/explicit_fields, um es immer zu behalten.
Senden / Empfangen
Gleicher Aufbau wie in Anleitung 02, lediglich mit dem Geräte-Array wireless_verse_grip. Die WebSocket-Schleife empfängt einen Status-Frame und sendet den Handshake sowie das 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)
Eine 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 genannt (nicht buttons), um eine Verdeckung des Feldes 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
Befehlszeilenoptionen (Python)
Die Python-Variante akzeptiert dieselben beiden Flags wie Tutorial 01:
--full— gibt die rohe JSON-Nutzlast formatiert aus, anstelle der einzeiligen Zusammenfassung.--query-config— fügt bei jedem ausgehenden Ticksession.force_render_full_stateerneut hinzufügt, sodass jedes Bild denconfig-Block enthält statt nur der Zustandsänderung.
--query-config ist besonders nützlich in der kabellosen Variante von VerseGrip, da der config- Blockieren Sie zusätzlich zu den regulären Konfigurationsfeldern das WVG-spezifische Feld sub_type (stylus / prototype / ruko / kingfisher, ab Version 3.5 hinzugefügt) 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 C++-Varianten stellen diese Flags nicht zur Verfügung.
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