02. VerseGrip-Ausgabe
Der erste kabelgebundene VerseGrip überträgt die Ausrichtung (Quaternion + Euler-Winkel Z-X-Y), den Füllstand des Hall-Sensors und den Status der Tasten.
Lerninhalte:
- Im Status-Frame
quaternionOrientierung - Umwandlung von Quaternionen in Eulerwinkel in Grad als Z-X-Y-Winkel (+X nach rechts, +Y nach vorne, +Z nach oben)
probe_orientationals Keepalive für einen einzelnen Beobachter verwenden- Handshake-Muster, das nur bei der ersten Nachricht gesendet wird (wiein Tutorial 01)
Workflow
ws://localhost:10001Es wird ein WebSocket geöffnet und auf den ersten Status-Frame gewartet.verse_gripDer erste VerseGrip mit Kabel in der Reihedevice_idWählen Sie … aus.- Sitzungsprofilund nach Gerät
probe_orientationErstellt eine Anfrage einschließlich „keepalive“ (ein Befehl für ein leeres Objekt, der sicherstellt, dass die Daten zur Griffrichtung im Status-Frame weiterhin übertragen werden). - Nachdem die Anfrage gesendet wurde
sessionDas Feld wird entfernt. Dieses Feld dient für einen einmaligen Handshake. - Anschließend werden in jedem Frame die Quaternionen in Euler-Winkel umgewandelt und die gedrosselte Telemetrie ausgegeben. Bei jedem Tick wird erneut ein Keepalive gesendet.
Parameter
| Name | Standardwert | Verwendungszweck |
|---|---|---|
URI | ws://localhost:10001 | WebSocket-URL des Simulationskanals |
PRINT_EVERY_MS | 100 | Drosselung der Konsolenausgabe |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-verse-grip | Haply Diese Simulation im Hub identifizieren |
Die Konvertierung erfolgt im Anwendungsframe +X right, +Y forward, +Z upEs handelt sich um ein intrinsisches Z-X-Y-System (Gieren → Nicken → Rollen). glm::eulerAnglesBitte verwenden Sie dies nicht. Da diese Funktion einer anderen Konvention folgt, wird sie hier falsch interpretiert. Da alle drei Sprachversionen dieselbe Formel implementieren, sehen Sie sich bitte den Quellcode an.
probe_orientationFalls dies tatsächlich erforderlich istprobe_orientationDie Sitzung hat Inverse3 den Befehl nicht versenden Dies ist nur in bestimmten Fällen nützlich. Sobald Befehle an den „ Inverse3 “ (Kraft, Position, Drehmoment ...) gesendet werden, überträgt der Dienst automatisch die Orientierungsdaten des zugehörigen VerseGrip in jeden Status-Frame, sodass keine Sonde erforderlich ist. probe_orientationVerwenden Sie es ausschließlich für Monitoring-Tools mit Einzelgriff, wie in diesem Tutorial beschrieben.
Feld „Lesestatus“
data.verse_grip[0].stateIch lese dort:
orientation—quaternion(w, x, y, z)hall— Ganzzahlige Hall-Sensor-Wertebutton— Booleantransform.rotation— Arbeitsbereich drehen (quaternion). Bei gerichtsorientierten Geräten werden Position und Skalierung nicht übereinstimmen, weshalb sie grundsätzlich nicht ausgegeben werden.
transform.rotationSilberne identische Quaternionen {w:1,x:0,y:0,z:0}wird weggelassen, wenn dies der Fall ist. Geben Sie beim Lesen den Standardwert an. Um immer einen Wert zu erhalten, serialization/explicit_fieldsAktivieren Sie dies.
Senden / Empfangen
WebSocket-Schleife: Empfangen von Status-Frames, Handshake und probe_orientation Es wird ein Keepalive konfiguriert und erneut gesendet. Die erste gesendete Nachricht enthält das Sitzungsprofil, alle nachfolgenden Frames enthalten nur noch das Keepalive.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzelne asynchrone Schleife — recv() → Befehlskonfiguration → send() → Wiederholung.
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 führt WebSockets in einem E/A-Thread aus – die Verarbeitung erfolgt frameweise ws.onmessagewird dort verarbeitet. Der Hauptthread wird blockiert, während er auf die ENTER-Taste wartet.
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
Es wird dasselbe libhv-Callback-Modell verwendet. Eine typisierte Struktur nlohmann::jsonersetzt — probe_orientation_cmdist eine leere Struktur (Glaze bezeichnet dies als {}(wird so notiert). std::optional<session_cmd>verarbeitet den einmalig durchgeführten Handshake und nach der ersten Übertragung .reset()Wenn Sie diese Funktion aufrufen, wird dieser Eintrag in der anschließenden JSON-Ausgabe 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 unterstützt dieselben zwei Flags wie in Tutorial 01:
--full— Anstelle einer einzeiligen Zusammenfassung wird die JSON-Rohdaten-Payload übersichtlich ausgegeben.--query-config— bei jedem ausgehenden Ticksession.force_render_full_statedurch erneutes Einfügen, sodass jedes Frame nicht mehr nur das Zustandsdelta überträgt, sondernconfigStellen Sie sicher, dass Blöcke (Gerätetyp, Firmware, Mount-Konvertierung usw.) enthalten sind. Dies ist nützlich, wenn Sie Konfigurationsänderungen in Echtzeit debuggen.
Beide Flags können zusammen verwendet werden. Die C++-Variante bietet dieses Flag nicht.
Auch Tutorial 02 ist zusammen mit dem SDK lokal installiert – im Installationsverzeichnis des Dienstes unter tutorials/02-haply-inverse-print-verse-grip/können Sie dort nachlesen.
Quelle: Python · C++ · C++ Glaze
Siehe auch: Typ (Quaternion) · Steuerbefehl (probe_orientation) · WebSocket-Protokoll · Tutorial 03 (Wireless VG)