02. Ausgabe von VerseGrip
Streamt die Orientierung (Quaternion + Z-X-Y-Euler-Winkel), den Hall-Sensorwert und den Tastenstatus des ersten kabelgebundenen VerseGrip.
Was Sie lernen werden:
- Die
quaternion-Orientierung aus dem State-Frame auslesen - Ein Quaternion in Z-X-Y-Euler-Winkel in Grad umrechnen (+X rechts, +Y vorne, +Z oben)
probe_orientationals Keepalive für einen eigenständigen Beobachter verwenden- Das Handshake-Muster, das nur in der ersten Nachricht angewendet wird (wie in Tutorial 01)
Ablauf
- Öffnen Sie eine WebSocket-Verbindung zu
ws://localhost:10001und warten Sie auf den ersten State-Frame. - Wählen Sie die
device_iddes ersten kabelgebundenen VerseGrip aus dem Arrayverse_grip. - Erstellen Sie eine Anfrage mit dem Sitzungsprofil und einem gerätespezifischen
probe_orientation-Keepalive (ein Befehl mit leerem Objekt, der dafür sorgt, dass die Griff-Orientierung fortlaufend in den State-Frames übermittelt wird). - Senden Sie die Anfrage und entfernen Sie anschließend das Feld
session— es handelt sich um einen einmaligen Handshake. - Bei jedem weiteren Frame rechnen Sie das Quaternion in Euler-Winkel um und geben gedrosselte Telemetrie aus. Senden Sie den Keepalive in jedem Zyklus erneut.
Parameter
| Name | Standardwert | Zweck |
|---|---|---|
URI | ws://localhost:10001 | URL des Simulationskanals (WebSocket) |
PRINT_EVERY_MS | 100 | Drosselung der Konsolenausgabe |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-verse-grip | Kennzeichnet diese Simulation in Haply Hub |
Die Umrechnung ist eine intrinsische Z-X-Y-Rotation (Gieren → Nicken → Rollen) im Koordinatensystem der Anwendung +X right, +Y forward, +Z up. Verwenden Sie nicht glm::eulerAngles — sie folgt einer anderen Konvention und liefert hier falsche Werte. Alle drei Sprachvarianten implementieren dieselbe Mathematik; die Formel finden Sie in den Quellen.
probe_orientation tatsächlich benötigt wirdprobe_orientation ist nur sinnvoll, wenn Ihre Sitzung keinen Befehl an Inverse3 sendet. Sobald Sie Inverse3 mit einem Befehl ansteuern (Kraft, Position, Drehmoment ...), streamt der Dienst die Orientierung des gekoppelten VerseGrip automatisch in jedem State-Frame — eine Probe ist dann nicht nötig. Verwenden Sie probe_orientation nur für eigenständige Werkzeuge zur Griffüberwachung wie dieses Tutorial.
Gelesene State-Felder
Aus data.verse_grip[0].state:
orientation—quaternion(w, x, y, z)hall— ganzzahliger Hall-Sensorwertbutton— boolescher Werttransform.rotation— Rotation des Arbeitsbereichs (quaternion); Position und Skalierung sind für ein Gerät, das nur die Orientierung erfasst, nicht anwendbar und werden nie ausgegeben
transform.rotation wird ausgelassen, wenn es dem Identitäts-Quaternion {w:1,x:0,y:0,z:0} entspricht. Legen Sie beim Lesen einen Standardwert fest. Aktivieren Sie serialization/explicit_fields, damit es immer übermittelt wird.
Senden / Empfangen
Die WebSocket-Schleife empfängt ein State-Frame, erstellt den Handshake und den probe_orientation-Keepalive und sendet sie zurück. Die erste ausgehende Nachricht enthält das Sitzungsprofil; jedes weitere Frame enthält nur den Keepalive.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzelne asynchrone Schleife — recv() → Befehl erstellen → send() → wiederholen.
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["verse_grip"][0]["device_id"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-verse-grip"}}},
"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
libhv steuert WebSocket auf seinem I/O-Thread — die Arbeit pro Frame findet in ws.onmessage statt. Der Hauptthread blockiert, bis ENTER gedrückt wird.
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["verse_grip"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-versegrip"}}}}}}},
{"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
Dasselbe libhv-Callback-Modell. Typisierte Strukturen ersetzen nlohmann::json — probe_orientation_cmd ist eine leere Struktur (Glaze schreibt sie als {}). std::optional<session_cmd> übernimmt den einmaligen Handshake: .reset() nach dem ersten Senden lässt ihn in der weiteren Ausgabe von JSON weg.
// Struct models
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct grip_state { quat orientation{}; bool button{}; uint8_t hall{}; };
struct grip_device { std::string device_id; grip_state state; };
struct devices_message { std::vector<grip_device> 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> 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;
out_cmds.session = session_cmd{ /* profile = print-versegrip */ };
device_commands dc{ .device_id = data.verse_grip[0].device_id };
dc.commands.probe_orientation = probe_orientation_cmd{};
out_cmds.verse_grip.push_back(std::move(dc));
}
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
out_cmds.session.reset(); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Kommandozeilen-Flags (Python)
Die Python-Variante 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 Taktsession.force_render_full_stateerneut hinzu, sodass jedes Frame denconfig-Block (Gerätetyp, Firmware, Montagetransformation …) enthält und nicht nur die Zustandsänderung. Nützlich zum Debuggen von Einstellungsänderungen im laufenden Betrieb.
Beide Flags lassen sich kombinieren. Die C++-Varianten bieten diese Flags nicht an.
Tutorial 02 wird ebenfalls lokal mit dem SDK installiert — Sie finden es unter tutorials/02-haply-inverse-print-verse-grip/ im Installationsverzeichnis des Dienstes.
Quelle: Python · C++ · C++ Glaze
Siehe auch: Typen (Quaternion) · Steuerbefehle (probe_orientation) · WebSocket-Protokoll · Tutorial 03 (Wireless VG)