01. Ausgabe von Inverse3
Stellen Sie eine Verbindung zum Simulations-WebSockether und streamen Sie die Cursorposition, -geschwindigkeit und -kraft ab dem ersten vom Dienst gemeldeten „Inverse3“.
Lerninhalte:
- Eine WebSocket-Verbindung herstellen und die erste vollständige Statusmeldung empfangen
- Nullkraft
set_cursor_forceEin „Keep-Alive“-Paket senden, um die Sitzung aufrechtzuerhalten - Haply Registrieren Siedas Sitzungsprofil, damit der Hub die Simulation erkennen kann.
- Muster, bei dem der Handshake ausschließlich über die erste Nachricht erfolgt – Nach dem ersten Versand sind „session/configure“ zu entfernen.
- Im Rahmen der Semantik der Teilaktualisierung des Arbeitsbereichs
transform(Position, Drehung, Skalierung) auslesen - Die Konsolenausgabe auf ein überschaubares Maß zu beschränken
Workflow
ws://localhost:10001Eröffnet eine WebSocket-Verbindung. Der Dienst zeigt sofort eine Liste der verbundenen Geräte an.Rahmen in einwandfreiem Zustandsenden.- Im ersten Frame, das erste Inverse3
device_idund erstellt eine aus zwei Teilen bestehende Anforderungsnachricht:session.configure.profile.name— Die SimulationHaply HubRegistrieren.- Gerätespezifisch, mit Angabe des Nullvektors
set_cursor_forceBefehl. Der Dienst verwendet diesen als Keep-Alive. Solange der Befehl weiterhin eingeht, sendet er fortlaufend Status-Frames.
- Ich sende die Nachricht zurück.vor dem nächsten Tick
sessionBitte entfernen Sie das Feld.。Ein Sitzungsprofil ist ein einmaliger Handshake; bei den nachfolgenden Ticks werden nur noch Befehle gesendet. - In jedem folgenden Status-Frame wird der Cursor
vec3Die Feldwerte (Position, Geschwindigkeit, Kraft) werden mit einer Frequenz von ca. 10 Hz ausgegeben, und das „Zero-Force“-Keep-Alive-Signal wird erneut gesendet.
Parameter
| Name | Standard | Verwendungszweck |
|---|---|---|
URI | ws://localhost:10001 | URL des WebSocket-Simulationskanals |
PRINT_EVERY_MS | 100 | Intervall für die Unterdrückung der Konsolenausgabe |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:print-inverse3 | Haply Diese Simulation wird innerhalb des Hubs identifiziert. |
Feld „Lesezustand“
data.inverse3[0].state Aus dem Folgenden lässt sich Folgendes entnehmen:
cursor_position、cursor_velocity、current_cursor_force— in allen Fällenvec3transform— Umwandlung des Arbeitsbereichs. Als Unterobjektposition(vec3),rotation(quaternion),scale(vec3) besitzt
Standardwert der Identitätsfunktion (Einheitselement) (position: {0,0,0}、rotation: {w:1,x:0,y:0,z:0}、scale: {1,1,1}) entsprechen, werden zur Einsparung von Bandbreite aus der Nutzlast entferntAuslassungwird durchgeführt. Geben Sie beim Lesen unbedingt den Standardwert an (Beispiel: In C++ lautet dieser .value("position", default_pos)、In Python gilt: .get("position", default_pos)). Um stets alle Felder zu empfangen,serialization/explicit_fieldsBitte aktivieren Sie diese Funktion.
Senden und Empfangen
In der WebSocket-Schleife wird ein Status-Frame empfangen, undBefehlsrahmenerstellt und zurücksendet. Der erste Befehlsrahmen enthält den Handshake der Sitzung und die Nullkraft.set_cursor_forceEnthält „Keep-Alive“. In den folgenden FramesNur „Keep Alive“ist enthalten (die Sitzung wird entfernt).
- Python
- C++ (nlohmann)
- C++ (Glaze)
Es handelt sich um eine 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 steuert WebSockets über einen eigenen E/A-Thread. Die Verarbeitung der einzelnen Frames erfolgt ws.onmessage ist darin implementiert. Der Hauptthread wird lediglich so lange blockiert, bis die ENTER-Taste gedrückt wird.
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 handelt sich um dasselbe Callback-Modell von libhv wie bei der nlohmann-Variante. Der einzige Unterschied besteht im Hauptteil. Glaze nutzt Reflection zur Kompilierungszeit. Es werden Strukturen deklariert, die die JSON-Struktur widerspiegeln, undglz::read / glz::write_json ruft auf.std::optional<session_cmd> behält einen einmaligen Handshake bei. Ist dieser nicht festgelegt, lässt Glaze dieses 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)
In der Python-Variante können Sie zwei Flags angeben, mit denen Sie die Ausgabe des Tutorials ändern können.
| Flag | Wirkung |
|---|---|
--full | Es wird keine einzeilige Zusammenfassung ausgegeben, sondern die rohen JSON-Payloads der einzelnen Status-Frames werden aufbereitet und ausgegeben. Dies ist nützlich, um die vom Dienst gesendeten Felder zu überprüfen. |
--query-config | Bei jedem zu sendenden Tick session.force_render_full_state: {} hinzufügen, damit der Dienst bei jedem Frame einen vollständigen Snapshot (config Block – (einschließlich Gerätetyp, Firmware, Voreinstellungen, Mount-Punkte, Filter usw.) erneut zu senden. Fehlt dieses Flag,config wird nur im ersten Frame übertragen, während die nachfolgenden Frames aus den Differenzen des Streams bestehen. |
Beide Flags können kombiniert verwendet werden.python 01-haply-inverse-print-inverse3.py --full --query-config Wenn man dies ausführt, wird bei jedem Tick config Es wird die vollständige JSON-Nutzlast ausgegeben. Dies ist nützlich, um Änderungen an den Einstellungen in Echtzeit über denHaply -Hub oder die HTTP-API zu überprüfen. Informationen zu den zugrunde liegenden WebSocket-Befehlen finden Sie unter:session.force_render_full_state Siehe dazu.
In der C++-Variante stehen diese Flags nicht zur Verfügung. Es wird stets eine einzeilige Zusammenfassung ausgegeben, undconfig wird nur im ersten Frame empfangen.
Auch Tutorial 01 ist zusammen mit dem SDK lokal installiert. Im Installationsverzeichnis des Dienstes unter tutorials/01-haply-inverse-print-inverse3/ Bitte überprüfen Sie dies.
Quelle: Python · C++ · C++ Glaze
Siehe auch: WebSocket-Protokoll · Steuerbefehle (set_cursor_force) · Sitzung · Typ (vec3)