02. Ausgabe von VerseGrip
Übertrage die Ausrichtung (Quaternion + Z-X-Y-Euler-Winkel), den Hall-Sensorwert und den Tastenzustand 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 unabhängigen Beobachter verwenden- Das Handshake-Muster, das nur in der ersten Nachricht verwendet wird (wie in Tutorial 01)
Ablauf
- Stellen 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-Ausrichtung fortlaufend in den State-Frames übermittelt wird). - Senden Sie die Anfrage und deaktivieren Sie anschließend das Kontrollkästchen
session— Es handelt sich um einen einmaligen Handshake. - Bei jedem weiteren Frame rechnen Sie das Quaternion in Euler-Winkel um und geben gedrosselte Telemetriedaten 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 im „ Haply “-Hub |
Die Umrechnung erfolgt als 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 im Quellcode.
probe_orientation tatsächlich benötigt wirdprobe_orientation ist nur dann sinnvoll, wenn Ihre Sitzung keinen Befehl an Inverse3 sendet. Sobald Sie Inverse3 mit einem Befehl ansteuern (Kraft, Position, Drehmoment …), überträgt der Dienst automatisch die Ausrichtung des gekoppelten VerseGrip in jedem State-Frame – eine Abfrage ist dann nicht erforderlich. Verwenden Sie probe_orientation nur für eigenständige Tools zur Griffüberwachung wie dieses Tutorial.
Ausgelesene Statusfelder
Aus data.verse_grip[0].state:
orientation—quaternion(w, x, y, z)hall— ganzzahliger Hall-Sensorwertbutton— boolescher Werttransform.rotation— Drehung des Arbeitsbereichs (quaternion); Position und Skalierung sind für ein Gerät, das nur die Ausrichtung erfasst, nicht anwendbar und werden niemals ausgegeben
transform.rotation wird weggelassen, 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 einen State-Frame, führt den Handshake durch 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)
Eine 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 über seinen I/O-Thread — die Arbeit pro Frame findet in ws.onmessage statt. Der Hauptthread bleibt blockiert, bis die ENTER-Taste 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 wird es in der weiteren Ausgabe von JSON weggelassen.
// 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
Befehlszeilenoptionen (Python)
Die Python-Variante akzeptiert dieselben zwei Flags wie Tutorial 01:
--full— gibt die rohe JSON-Nutzlast formatiert aus, anstelle der einzeiligen Zusammenfassung.--query-config— fügt bei jedem ausgehenden Taktsession.force_render_full_stateerneut hinzufügt, 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)