01. Ausgabe von „ Inverse3 “
Stellt eine Verbindung zum Simulations-WebSockether und überträgt die vom Dienst gemeldeten Daten zur Cursorposition, Geschwindigkeit und Kraft des ersten Inverse3.
Lerninhalte:
- Eine WebSocket-Verbindung öffnen und die erste vollständige Statusmeldung empfangen
- Nullkraft, damit die Sitzung weiterläuft
set_cursor_force„Keep Alive“ senden - Haply Registrieren eines Sitzungsprofils, damit die Simulation vom Hub erkannt wird
- Handshake-Muster, das nur bei der ersten Nachricht ausgeführt wird – Entfernen von „session/configure“ nach der ersten Übertragung
- Teilaktualisierung des Arbeitsbereichs anhand von Semantik
transform(Position, Drehung, Skalierung) auslesen - Die Konsolenausgabe auf eine angenehme Geschwindigkeit drosseln
Workflow
ws://localhost:10001Öffnet eine WebSocket-Verbindung. Der Dienst listet die verbundenen Geräte auf. Gesamtzustandsrahmenwird sofort übermittelt.- Im ersten Frame des ersten Inverse3
device_idWählen Sie diese Option aus und erstellen Sie eine aus zwei Teilen bestehende Anforderungsnachricht:session.configure.profile.name— Haply HubRegistrieren Sie die Simulation.- Gerätespezifisch unter Verwendung des Nullvektors
set_cursor_forceBefehl. Der Dienst nutzt diesen als Keep-Alive und sendet so lange Status-Frames, wie Befehle eingehen.
- Die Nachricht wird erneut gesendet. Vor dem nächsten Tick
sessionDas Feld wird entfernt. — Da es sich bei dem Sitzungsprofil um einen einmaligen Handshake handelt, werden in den nachfolgenden Ticks nur noch Befehle übertragen. - Danach wird bei jedem Status-Frame der Cursor
vec3Die Felddaten (Position, Geschwindigkeit, Kraft) werden mit einer Frequenz von etwa 10 Hz ausgegeben, und das „Zero-Force-Keep-Alive“-Signal wird erneut 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-inverse3 | Haply Diese Simulation im Hub identifizieren |
Feld „Lesestatus“
data.inverse3[0].stateDort steht Folgendes:
cursor_position,cursor_velocity,current_cursor_force— jeweilsvec3transform— Unterobjekt „Arbeitsbereichskonvertierung“.position(vec3),rotation(quaternion),scale(vec3) enthält dieses Feld.
Unterfelder wie der Standardwert (position: {0,0,0}, rotation: {w:1,x:0,y:0,z:0}, scale: {1,1,1}) wird zur Einsparung von Bandbreite aus der Nutzlast wird weggelassen. Geben Sie beim Auslesen von Werten immer einen Standardwert an (z. B. in C++: .value("position", default_pos), Python's .get("position", default_pos)). Um stets alle Felder zu erhalten, serialization/explicit_fieldsAktivieren Sie dies.
Senden / Empfangen
WebSocket-Schleife: Empfangen eines Status-Frames und Befehlsrahmenwird erstellt und erneut gesendet. Der erste Befehlsrahmen enthält den Sitzungs-Handshake und Zero-Power set_cursor_force Es enthält „KeepAlive“, und in allen nachfolgenden Frames wird Nur „Keep Alive“ ist enthalten (die Sitzung wird entfernt).
- Python
- C++ (nlohmann)
- C++ (Glaze)
Einzelne asynchrone Schleife — recv() → Befehl 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 verarbeitet die WebSocket-Kommunikation in einem eigenen E/A-Thread – die Verarbeitung der einzelnen Frames erfolgt ws.onmessagebefindet sich dort. Der Hauptthread wird blockiert und wartet ausschließlich 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["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
Es wird dasselbe libhv-Callback-Modell wie bei der nlohmann-Variante verwendet; der einzige Unterschied besteht im Hauptteil. Glaze nutzt Reflection zur Kompilierungszeit. Das heißt, es werden Strukturen deklariert, die die JSON-Form genau widerspiegeln, und glz::read / glz::write_jsonruft auf. std::optional<session_cmd> Dieser Typ enthält einen einmaligen Handshake; wenn kein Wert festgelegt ist, lässt Glaze das entsprechende Feld 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
Befehlszeilenoptionen (Python)
Die Python-Variante unterstützt zwei Flags, mit denen sich die Ausgabe des Tutorials ändern lässt:
| Flagge | Wirkung |
|---|---|
--full | Anstelle einer einzeiligen Zusammenfassung werden die Rohdaten der JSON-Payloads aller Status-Frames übersichtlich ausgegeben. Dies ist nützlich, um zu überprüfen, welche Felder der Dienst ausgibt. |
--query-config | Bei jedem ausgehenden Tick session.force_render_full_state: {}durch erneutes Einfügen, damit der Dienst in jedem Frame config Stellt sicher, dass ein vollständiger Snapshot einschließlich aller Blöcke (Gerätetyp, Firmware, Voreinstellungen, Mounts, Filter usw.) erneut exportiert wird. Ohne dieses Flag configwird nur im ersten Frame übertragen, die nachfolgenden Frames sind Streaming-Deltas. |
Beide Flags können zusammen verwendet werden — python 01-haply-inverse-print-inverse3.py --full --query-configWenn man das ausführt, wird bei jedem Tick configDie gesamte JSON-Nutzlast, die mit gekennzeichnet ist, wird ausgegeben. Dies ist nützlich, um zu beobachten, wie sich die Einstellungen in Echtzeit über den „ Haply -Hub“ oder die HTTP-API ändern. Der zugrunde liegende WebSocket-Befehl lautet session.force_render_full_stateSiehe dazu.
Die C++-Variante bietet dieses Flag nicht an – es wird immer nur eine einzeilige Zusammenfassung ausgegeben, und configwird nur im ersten Frame empfangen.
Tutorial 01 ist zusammen mit dem SDK auch lokal installiert – im Installationsverzeichnis des Dienstes unter tutorials/01-haply-inverse-print-inverse3/können Sie dort nachlesen.
Quelle: Python · C++ · C++ Glaze
Siehe auch: WebSocket-Protokoll · Steuerbefehl (set_cursor_force) · Sitzung · Typ (vec3)