02. Ausgabe von VerseGrip
Von dem zunächst über Kabel angeschlossenen VerseGrip werden die Lage (Quaternion + Z-X-Y-Eulerwinkel), der Füllstand des Hall-Sensors sowie der Status der Tasten erfasst und ausgegeben.
Was Sie in diesem Tutorial lernen werden:
- Aus dem Zustandsrahmen
quaterniondie Haltung deuten - Quaternionen in Eulerwinkel in Grad (Z-X-Y: +X nach rechts, +Y nach vorne, +Z nach oben) umrechnen
- Als Keep-Alive für einen eigenständigen Observer
probe_orientationverwenden - Ein Handshake-Muster, das ausschließlich die erste Nachricht verwendet (wie inTutorial 01 )
Workflow
ws://localhost:10001Öffnen Sie eine WebSocket-Verbindung und warten Sie auf den ersten Status-Frame.verse_gripAus der Aufstellung geht hervor, dass der erste VerseGrip, der per Kabel angeschlossen wurde,device_idwird abgerufen.- Sitzungsprofil und für jedes Gerät
probe_orientationErstellen Sie eine Anfrage, die „Keep Alive“ enthält (ein Befehl für Objekte im Luftraum, der dafür sorgt, dass die Haltung des Grips kontinuierlich in den Status-Frames übertragen wird). - Nachdem Sie die Anfrage abgeschickt haben,
sessionDas Feld wird gelöscht. Der Grund dafür ist, dass es sich um einen einmaligen Handshake handelt. - In jedem folgenden Frame werden die Quaternionen in Euler-Winkel umgewandelt und Telemetriedaten mit begrenzter Übertragungsfrequenz ausgegeben. Bei jedem Tick wird ein Keep-Alive-Signal erneut gesendet.
Parameter
| Name | Standard | Verwendungszweck |
|---|---|---|
URI | ws://localhost:10001 | URL des Simulationskanals WebSocket |
PRINT_EVERY_MS | 100 | Begrenzung der Häufigkeit der Konsolenausgabe |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-verse-grip | Haply Identifizieren Sie diese Simulation innerhalb des Hubs |
Die Transformation erfolgt im Koordinatensystem der Anwendung +X right, +Y forward, +Z up die spezifischen Z-X-Y-Werte (Gieren → Neigen → Rollen) in diesem Zusammenhang.glm::eulerAngles Bitte nicht verwenden. Da diese Funktion anderen Konventionen folgt, wird hier ein falscher Wert ausgelesen. Alle drei Sprachversionen implementieren dieselbe Berechnung. Die Berechnungsformel finden Sie im Quellcode.
probe_orientation wenn dies tatsächlich erforderlich istprobe_orientation Das ist nützlich, weil die Sitzung in Inverse3Es werden keinerlei Befehle gesendetDies gilt nur in bestimmten Fällen. Sobald ein Befehl an die „Inverse3“ (Kraft, Position, Drehmoment usw.) gesendet wird, übermittelt der Dienst automatisch die Lage des VerseGrip-Paares in allen Zustandsrahmen. Daher ist keine Sonde erforderlich.probe_orientation Verwenden Sie es bitte ausschließlich in eigenständigen Tools zur Griffüberwachung, wie sie in diesem Tutorial vorgestellt werden.
Feld „Lesezustand“
data.verse_grip[0].state Aus dem Folgenden lässt sich Folgendes entnehmen:
orientation—quaternion(w, x, y, z)hall— Ganzzahlige Werte des Hall-Sensorsbutton— Wahr-Falsch-Werttransform.rotation— Drehen des Arbeitsbereichs (quaternion). Da Position und Skalierung für Geräte, die ausschließlich die Lage erfassen, nicht relevant sind, werden diese Daten nicht übertragen.
transform.rotation ist ein Einheitsquaternion {w:1,x:0,y:0,z:0} wird weggelassen, wenn es gleich ist. Bitte geben Sie beim Lesen einen Standardwert an. Um stets Daten zu empfangen,serialization/explicit_fields Bitte aktivieren Sie diese Funktion.
Senden und Empfangen
WebSocket-Schleife: Empfangen eines Status-Frames, Handshake und probe_orientation Es wird ein Keep-Alive-Frame aufgebaut und zurückgesendet. Die erste gesendete Nachricht enthält ein Sitzungsprofil, während die nachfolgenden Frames nur Keep-Alive-Daten enthalten.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Eine einzige asynchrone Schleife — recv() → Befehlsaufbau → 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 betreibt WebSockets auf einem E/A-Thread. Die Verarbeitung der einzelnen Frames erfolgt ws.onmessage ist beschrieben. Der Hauptthread wartet auf die Eingabe der ENTER-Taste.
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
Das Callback-Modell von libhv ist dasselbe. Typisierte Strukturen nlohmann::json wird anstelle von … verwendet.probe_orientation_cmd ist eine leere Struktur (Glaze bezeichnet dies als {} (wird wie folgt geschrieben).std::optional<session_cmd> behandelt einen einmaligen Handshake und nach der ersten Übertragung .reset() Wenn Sie dies aufrufen, wird es in der nachfolgenden 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 akzeptiert dieselben zwei Flags wie inTutorial 01:
--full— Es wird keine einzeilige Zusammenfassung ausgegeben, sondern die rohe JSON-Nutzlast aufbereitet und ausgegeben.--query-config— Bei jedem zu sendenden Ticksession.force_render_full_stateerneut senden und in jedem FrameconfigBezieht Blöcke (Gerätetyp, Firmware, Umwandlung der Einbindung usw.) mit ein. Es werden nicht nur die Statusänderungen, sondern jedes Mal die vollständigen Informationen übermittelt. Dies ist nützlich, um Änderungen an den Einstellungen in Echtzeit zu debuggen.
Beide Flags können kombiniert verwendet werden. In der C++-Variante stehen diese Flags nicht zur Verfügung.
Auch Tutorial 02 ist zusammen mit dem SDK lokal installiert. Im Installationsverzeichnis des Dienstes unter tutorials/02-haply-inverse-print-verse-grip/ Bitte überprüfen Sie dies.
Quelle: Python · C++ · C++ Glaze
Siehe auch: Typ (Quaternion) · Steuerbefehle (probe_orientation) · WebSocket-Protokoll · Tutorial 03 (Wireless VG)