01. Anzeigen Inverse3
Stellt eine Verbindung zur WebSocket-Simulation her und überträgt die Cursorposition, die Geschwindigkeit und die Kraft anhand des ersten vom Dienst gemeldeten „ Inverse3 “-Werts.
Was Sie lernen werden:
- Eine WebSocket-Verbindung herstellen und die erste Nachricht mit dem vollständigen Status empfangen
- Ein Keepalive-Signal mit Nullkraft senden mit
set_cursor_forcedamit die Sitzung aktiv bleibt - Speichern Sie ein Sitzungsprofil, damit „ Haply Hub“ Ihre Simulation erkennt
- Das Modell für den Handshake, das auf die erste Nachricht beschränkt ist: „session/configure“ nach dem ersten Versand entfernen
- Lesen Sie den
transformdes Arbeitsraums (Position, Drehung, Skalierung) mit der Semantik der partiellen Aktualisierung - Die Anzeigefrequenz in der Konsole auf eine lesbare Rate begrenzen
Ablauf
- Eine WebSocket-Verbindung zu öffnen
ws://localhost:10001. Der Dienst sendet umgehend eine vollständiger Status-Frame die die verbundenen Geräte auflistet. - Wählen Sie im ersten Raster die
device_idAus dem ersten Inverse3 und erstellen Sie eine zweiteilige Anfrage:session.configure.profile.name: Speichert die Simulation bei Haply Hub.- Eine Bestellung
set_cursor_forcepro Gerät, mit einem Nullvektor. Der Dienst nutzt dies als Halte-Signal: Er sendet so lange Status-Frames, wie Befehle eingehen.
- Die Nachricht weiterleiten. Feld entfernen
sessionvor dem nächsten Zyklus: Das Sitzungsprofil ist ein einmaliger Handshake, und in den folgenden Zyklen wird nur noch der Befehl gesendet. - Für jeden folgenden Statusrahmen: Felder anzeigen
vec3des Cursors (Position, Geschwindigkeit, Kraft) mit einer auf etwa 10 Hz begrenzten Abtastrate und anschließend das Signal zur Beibehaltung der Kraft auf Null zurücksenden.
Einstellungen
| Nom | Fehler | Rolle |
|---|---|---|
URI | ws://localhost:10001 | URL des WebSocket-Simulationskanals |
PRINT_EVERY_MS | 100 | Anzeigebegrenzung in der Konsole |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-inverse3 | Identifiziere diese Simulation im „ Haply “-Hub |
Meister der Les-États
Seit data.inverse3[0].state :
cursor_position,cursor_velocity,current_cursor_force: vom Typvec3jedertransform: Transformation des Arbeitsraums; Unterobjekt mitposition(vec3),rotation(quaternion) undscale(vec3)
Die Teilfelder, deren Wert dem Identitätswert entspricht (position: {0,0,0}, rotation: {w:1,x:0,y:0,z:0}, scale: {1,1,1}) sind omis der Nutzlast, um Bandbreite zu sparen. Geben Sie beim Lesen immer einen Standardwert an (z. B. .value("position", default_pos) in C++, .get("position", default_pos) (in Python). Aktivieren Sie serialization/explicit_fields um weiterhin jedes Feld zu erhalten.
Versand und Empfang
Der WebSocket-Zyklus: einen Status-Frame empfangen, einen neuen erstellen und zurücksenden Steuerungsstruktur. Der erste Steuer-Frame enthält den Sitzungs-Handshake und ein Signal für den Null-Kraft-Modus mit set_cursor_force; jeder nachfolgende Rahmen überträgt que das Halte-Signal (die Sitzung wird beendet).
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzigartige asynchrone Schleife: recv() → Bestellung zusammenstellen → 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["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 den WebSocket über einen eigenen E/A-Thread; die Verarbeitung der einzelnen Frames erfolgt in ws.onmessage. Der Hauptthread wartet einfach auf die Eingabetaste.
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
Dasselbe libhv-Callback-Modell wie bei der nlohmann-Variante: Nur der Hauptteil ändert sich. Glaze nutzt die Reflexion bei der Kompilierung: Deklarieren Sie Strukturen, die die Form von JSON widerspiegeln, und rufen Sie dann glz::read / glz::write_json. std::optional<session_cmd> Übergeben Sie den Einmal-Handschlag; wenn dieser nicht definiert ist, lässt Glaze dieses Feld bei der JSON-Serialisierung 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
Befehlszeilenoptionen (Python)
Die Python-Variante akzeptiert zwei Optionen, die die Anzeige des Tutorials verändern:
| Option | Effet |
|---|---|
--full | Zeigt die JSON-Bruttodaten jedes Status-Frames in formatierter Form anstelle einer einzeiligen Zusammenfassung an. Nützlich, um die vom Dienst gesendeten Felder zu ermitteln. |
--query-config | Wieder einspritzen session.force_render_full_state: {} bei jedem ausgehenden Zyklus, damit der Dienst erneut einen vollständigen Snapshot (einschließlich des Blocks config : Gerätetyp, Firmware, Voreinstellung, Montage, Filter, …) bei jedem Bild. Ohne diese Option, config Es wird nur der erste Frame übertragen, und die folgenden Frames enthalten lediglich fortlaufend übertragene Differenzaktualisierungen. |
Beide Optionen lassen sich kombinieren: python 01-haply-inverse-print-inverse3.py --full --query-config zeigt eine vollständige JSON-Nutzlast mit dem Block an config in jedem Zyklus sichtbar, was praktisch ist, um Parameteränderungen in Echtzeit über den „ Haply -Hub“ oder über die HTTP-API zu beobachten. Siehe session.force_render_full_state pour la commande WebSocket sous-jacente.
Die C++-Varianten bieten diese Optionen nicht an: Sie zeigen immer die Zusammenfassung einer Zeile an und nehmen keine config wie auf dem ersten Raster.
Das Tutorial 01 wird ebenfalls lokal mit dem SDK installiert: Siehe tutorials/01-haply-inverse-print-inverse3/ im Installationsverzeichnis des Dienstes.
Quelle: Python · C++ · C++ Glaze
Siehe auch: WebSocket-Protokoll · Steuerbefehle (set_cursor_force) · Sitzungen · Typen (vec3)