01. Drucken Inverse3
Stellen Sie eine Verbindungzum Simulations-WebSocketherundempfangen Sie fortlaufend die vom Dienst gemeldeten Daten zu Cursorposition, Geschwindigkeit und Kraft des ersten „ Inverse3 “.
Sie werden Folgendes lernen:
- Eine WebSocket-Verbindung herstellen und die erste vollständige Statusmeldung empfangen
- Senden mit null Kraft
set_cursor_forceDie Keep-Alive-Anweisung sorgt dafür, dass die Verbindung aufrechterhalten bleibt - RegistrierenSie das Sitzungsprofil, damit der „ Haply Hub“ Ihre Simulation erkennt
- Modus, bei dem der Handshake nur in der ersten Nachricht stattfindet – „session/configure“ wird nach dem ersten Versand entfernt
- Teilweise Aktualisierung der semantischen Lesung des Arbeitsbereichs
transform(Position, Drehung, Skalierung) - Die Konsolenausgabe auf eine lesbare Häufigkeit beschränken
Arbeitsablauf
- Öffnen bis
ws://localhost:10001WebSocket. Der Dienst sendet umgehend eineVollständiger Status-Frame, in der die verbundenen Geräte aufgelistet sind. - Beim Empfang des ersten Frames wird der erste „ Inverse3 “ ausgewählt, dessen
device_idund eine aus zwei Teilen bestehende Anforderungsnachricht erstellen:session.configure.profile.name—— in RichtungHaply HubRegistrieren Sie diese Simulation.- Für jedes Gerät
set_cursor_forceBefehl, Kraftvektor gleich Null. Der Dienst nutzt dies als Lebenszeichensignal – solange er weiterhin Befehle empfängt, sendet der Dienst kontinuierlich Statusrahmen.
- Die Nachricht zurücksenden.Vor dem nächsten Zyklus entfernen
sessionFeld——Das Sitzungsprofil dient lediglich dem einmaligen Handshake; in den nachfolgenden Zyklen werden ausschließlich Befehle gesendet. - Für jeden nachfolgenden Status-Frame: Den Cursor mit einer Frequenz von etwa 10 Hz drucken
vec3Felder (Position, Geschwindigkeit, Kraft) und senden Sie den Befehl zum Aufrechterhalten der Nullkraft erneut.
Parameter
| Bezeichnung | Standardwert | Verwendungszweck |
|---|---|---|
URI | ws://localhost:10001 | URL des WebSocket-Simulationskanals |
PRINT_EVERY_MS | 100 | Ausgabe der Geschwindigkeitsbegrenzung in der Konsole |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-inverse3 | Diese Simulation im „ Haply -Hub“ kennzeichnen |
Gelesene Statusfelder
Quelle: data.inverse3[0].state:
cursor_position、cursor_velocity、current_cursor_force—— alle sindvec3transform—— Arbeitsraumtransformation; als Unterobjekt, enthältposition(vec3),rotation(quaternion) undscale(vec3)
Unterfelder, deren Wert dem Standardwert der Einheit entspricht (position: {0,0,0}、rotation: {w:1,x:0,y:0,z:0}、scale: {1,1,1}) Um Bandbreite zu sparen, wird aus der LastAuslassung。Beim Auslesen muss unbedingt ein Standardwert angegeben werden (z. B. in C++ mit .value("position", default_pos), Verwendung in Python .get("position", default_pos)). Um stets jedes Feld zu empfangen, aktivieren Sie serialization/explicit_fields。
Senden / Empfangen
WebSocket-Schleife: Einen Status-Frame empfangen, einen erstellen und zurücksendenBefehlsrahmen。Der erste Befehlsrahmen enthält Informationen zum Verbindungsaufbau sowie eine Nullkraft von set_cursor_force Befehl „Speichern“; danach bei jedem EinzelbildnurMit Keep-Alive-Befehl (Sitzungsinformationen wurden entfernt).
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzelne asynchrone Schleife –recv() → Anweisungen zum Erstellen → 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["inverse3"][0]["device_id"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-inverse3"}}},
"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_force":
{"vector": {"x": 0.0, "y": 0.0, "z": 0.0}}},
}]
}
await websocket.send(json.dumps(request_msg))
request_msg.pop("session", None) # one-shot handshake
libhv steuert WebSockets in einem eigenen E/A-Thread – die Verarbeitung der einzelnen Frames erfolgt in ws.onmessage . Der Hauptthread muss lediglich blockieren und darauf warten, dass der Benutzer die ENTER-Taste drückt.
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["inverse3"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-inverse3"}}}}}}},
{"inverse3", json::array({
{{"device_id", device_id},
{"commands", {{"set_cursor_force",
{{"vector", {{"x", 0.0}, {"y", 0.0}, {"z", 0.0}}}}}}}},
})},
};
}
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 libhv-Callback-Modell entspricht der nlohmann-Variante – lediglich der Funktionskörper unterscheidet sich. Glaze nutzt Reflection zur Kompilierungszeit: Es werden Strukturen deklariert, die mit der JSON-Struktur übereinstimmen, und es wird glz::read / glz::write_json。std::optional<session_cmd> Dient zur Übertragung von Einmal-Handshake-Informationen; wenn dieses Feld nicht festgelegt ist, lässt Glaze es in der serialisierten JSON-Datei weg.
// Struct models
struct vec3 { float x{}, y{}, z{}; };
struct inverse_state {
vec3 cursor_position{}, cursor_velocity{}, current_cursor_force{};
/* + body_orientation, angular_position, angular_velocity */
};
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message { std::vector<inverse_device> inverse3; };
struct set_cursor_force_cmd { vec3 vector; };
struct commands_message {
std::optional<session_cmd> session; // omitted from JSON when unset
std::vector<device_commands> inverse3;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
commands_message out_cmds{};
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = print-inverse3 */ };
}
// ... populate out_cmds.inverse3 with zero-force keepalive ...
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Befehlszeilenargumente (Python)
Die Python-Variante akzeptiert zwei Parameter, mit denen die Ausgabe des Tutorials geändert werden kann:
| Parameter | Wirkung |
|---|---|
--full | Gibt die ursprünglichen JSON-Nutzdaten jedes Status-Frames in formatierter Form aus, anstatt sie als einzeilige Zusammenfassung darzustellen. Dies erleichtert das Verständnis, welche Felder der Dienst ausgibt. |
--query-config | Bei jedem Ausgabzyklus erneut einspeisen session.force_render_full_state: {}, sodass der Dienst in jedem Einzelbild erneut einen vollständigen Snapshot ausgibt (einschließlich config Blöcke – Gerätetyp, Firmware, Voreinstellungen, Installationsart, Filter usw.). Wird dieser Parameter nicht verwendet,config Erscheint nur im ersten Bild, alle nachfolgenden Bilder bestehen aus inkrementellen Streaming-Daten. |
Die beiden Parameter können kombiniert verwendet werden –python 01-haply-inverse-print-inverse3.py --full --query-config In jedem Zyklus wird eine Ausgabe generiert, die sichtbare config Die vollständigen JSON-Lastdaten sind besonders nützlich, wenn Sie über den Haply -Hub oder die HTTP-API Änderungen an den Einstellungen in Echtzeit beobachten. Informationen zu den zugrunde liegenden WebSocket-Befehlen finden Sie unter session.force_render_full_state。
Die C++-Varianten unterstützen diese Parameter nicht – sie geben stets nur eine einzeilige Zusammenfassung aus, und config Wird nur im ersten Bild empfangen.
Das Tutorial 01 wird ebenfalls zusammen mit dem SDK lokal installiert – bitte im Installationsverzeichnis des Dienstes unter tutorials/01-haply-inverse-print-inverse3/ dort suchen.
Quelle: Python · C++ · C++ Glaze
Verwandte Themen: WebSocket-Protokoll · Steuerbefehle (set_cursor_force) · Gespräch · Typ (vec3)