Simulationskanal
Überblick
Der Simulationskanal ist ein hochfrequenter, bidirektionaler WebSocket-Kanal, der zum Austausch des Gerätestatus sowie zum Senden von Sitzungs- oder Gerätebefehlen dient.
Standard-URL: ws://localhost:10001
Der Port kann in der Konfiguration geändert werden.
Grundlegendes Verhalten (wichtig):
- Sobald sich ein Client verbindet, sendet der Dienst eine Nachricht mit dem anfänglichen Inventar, das die vollständige Geräteliste enthält.
- Danach sendet der Server für jede vom Client empfangene Nachricht genau eine Statusaktualisierungsnachricht.
- Jedes State-Update enthält den Zustand (und Status) aller Geräte.
Auf dieser Seite werden das Kernprotokoll des Simulationskanals und dessen Befehle beschrieben.
Zusätzliche Module bzw. Funktionen können das System um eigene Befehle und/oder Statusfelder erweitern und diese registrieren. Diese sind separat im Abschnitt „Module“ dokumentiert.
Protokollregeln
Eine Antwort pro Client-Nachricht
Der Dienst sendet für jede vom Client empfangene Nachricht eine Statusaktualisierungsnachricht mit dem Status aller Geräte.
Das bedeutet:
- Wenn Sie einen aktuellen Überblick über den Gerätestatus benötigen, müssen Sie einen Befehl senden (einen Sitzungsbefehl, einen Abfragebefehl oder einen Gerätebefehl).
- Der Kanal verhält sich wie eine durch Client-Nachrichten gesteuerte „Tick“-Schleife.
Abfrage ohne Krafteinwirkung (Abfragebefehle)
Wenn Sie Zustandsänderungen beobachten möchten, ohne Kräfte anzuwenden oder Simulationsparameter zu ändern, verwenden Sie Abfragebefehle:
probe_positionfürinverse3probe_orientationfür Verse-Griffe
Abfragebefehle enthalten keine Befehlsdaten und bewirken lediglich die Abfrage von Geräteinformationen, damit die Positions- und Orientierungsdaten bei der nächsten State-Update-Nachricht auf dem neuesten Stand sind.
Simulationssitzungen, die bereits Steuerbefehle senden (set_cursor_force, set_cursor_position usw.), müssen keine Abfragebefehle senden – der Gerätezustand ist im Antwort-Frame immer enthalten. Abfragebefehle dienen dazu, reine Überwachungssitzungen gedacht (z. B. Haply Hub), die den Gerätezustand abfragen müssen, ohne das Gerät anzusteuern.
Konfiguration vs. Zustand
- Die Mitteilung mit dem ersten Bestandsverzeichnis enthält
config,stateundstatusder Geräte. - Reguläre Landes-Update-Nachrichten enthalten
stateundstatusder Geräte.
Wenn Sie erneut einen Snapshot einschließlich der Konfiguration benötigen, verwenden Sie session.force_render_full_state.
configure vs. commands
Gerätenachrichten unterstützen zwei getrennte Zuordnungen: configure und commands. Sie dienen unterschiedlichen Zwecken:
| Zuordnung | Zweck | Häufigkeit |
|---|---|---|
configure | Einmalige, dauerhafte Konfiguration: Voreinstellung, Basis, Montage, Filter, Moduleinstellungen | Einmal senden oder bei Änderungen |
commands | Pro Tick, nicht persistent: Kraft, Position, Drehmomente | Muss bei jedem Tick gesendet werden |
Einträge in commands (set_cursor_force, set_cursor_position, set_angular_torques, set_angular_position) werden wird einmal pro Tick angewendet und nicht gespeichert – Wenn Sie diese nicht mehr senden, endet die Wirkung sofort und das Gerät kehrt in den Ruhezustand zurück. Verwenden Sie configure für alles, was über mehrere Ticks hinweg bestehen bleiben soll, ohne erneut gesendet zu werden.
set_transform ist ein Sonderfallset_transform steht unter commands, ist aber anhaltend – Der Dienst behält
die zuletzt gesendete Transformation bei, bis Sie eine neue senden. Sie müssen sie nicht
bei jedem Tick senden. Da ihr Zweck jedoch die Kamera- bzw. Szenennavigation ist,
ist das Streamen mit der Tick-Rate (z. B. eine Transformation pro Frame, wenn der Nutzer durch die
Szene schwenkt) das erwartete Nutzungsmuster für die kontinuierliche Bewegungsnavigation.
Koordinatensysteme
Haply verwendet standardmäßig ein rechtshändiges Koordinatensystem mit Z-Up.
Zwei Konfigurationseinträge beeinflussen, wie Koordinaten interpretiert und zurückgegeben werden:
session.configure.basis: Ändert die Zuordnung zwischen den Koordinaten von „ Haply “ und dem Koordinatensystem Ihrer Anwendung (siehe Grundlage weiter unten).inverse3[*].configure.preset: Wählt eine benannte Werkkonfiguration aus, die den Ursprung des Arbeitsbereichs festlegt (z. B. setztarm_front_centeredden Punkt(0, 0, 0)in die Mitte des Arbeitsbereichs). Siehe den gerätespezifischen Abschnitt „Configure“.
Nachrichtenformate
In diesem Abschnitt werden der übergeordnete Envelope und die Nachrichtentypen beschrieben. Ausführliche Beispiele folgen später in diesem Dokument.
Gerätegruppen
Nachrichten werden auf oberster Ebene nach Gerätetyp gruppiert (z. B. inverse3, verse_grip, wireless_verse_grip). Jeder Gerätetyp-Schlüssel ist einem Array mit gerätespezifischen Objekten zugeordnet.
Erstes Inventar (Server → Client)
Wird einmal unmittelbar nach dem Aufbau einer WebSocket-Verbindung gesendet.
Jeder Eintrag enthält:
device_idconfigstatestatus
Siehe: Beispiel: Nutzdaten des anfänglichen Inventars
Statusaktualisierung (Server → Client)
Wird für jede Client-Nachricht einmal gesendet.
Jeder Eintrag enthält:
device_idstatestatus
Siehe: Beispiel: State-Update-Payload
Umschlag für Session-Befehle (Client → Server)
Session-Befehle sind Aktionen, die für die aktuelle Verbindung bzw. Session gelten und nicht gerätespezifisch sind.
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
Envelope für Gerätebefehle (Client → Server)
Gerätebefehle werden unter dem Gerätetyp-Schlüssel als Array gesendet, sodass Befehle in einer einzigen Nachricht an ein oder mehrere Geräte übermittelt werden können. Jeder Geräteeintrag unterstützt sowohl eine configure-Zuordnung sowie eine commands-Zuordnung.
{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}
Sie können mehrere Einträge angeben, um mehrere Geräte desselben Typs in einer einzigen Nachricht anzusteuern.
Beachten Sie, dass commands ein Wörterbuch ist und für ein bestimmtes Gerät mehrere Befehle enthalten kann, jedoch nur einen pro Befehlstyp.
Das Feld execute
Jeder Befehls- oder `configure`-Eintrag akzeptiert einen optionalen booleschen Wert "execute" (Standard: true). Setzen Sie "execute": false, damit der Eintrag geparst, aber nicht angewendet wird. Dies ist nützlich für reflexionsbasierte Serializer (z. B. Unity JsonUtility), die stets jedes Feld ausgeben.
"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
Befehlsreferenz
Sitzung
Vollständige Darstellung des Zustands erzwingen
Fordern Sie eine Momentaufnahme aller Gerätestatus und Konfigurationen an.
{
"session": {
"force_render_full_state": {}
}
}
Sitzung konfigurieren
Die sitzungsbezogene, dauerhafte Konfiguration wird über session.configure gesendet. Dies entspricht dem Muster der configure-Zuordnung auf Geräteebene.
Profil
Legt den Namen des Sitzungsprofils fest, das vom „ Haply -Hub“ verwendet wird, um die Simulation zu erkennen und gerätespezifische Anpassungen pro App zu speichern.
{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
Basis (sitzungsweit)
Legt die Basiszuordnung für die gesamte Sitzung fest. Die Basiszuordnung beschreibt, wieHaply-Koordinaten in das Koordinatensystem Ihrer Anwendung umgewandelt werden; nachdem sie festgelegt wurde, werden alle Gerätestatuswerte in dieser Basis zurückgegeben und alle gesendeten Werte darin interpretiert.
Die Permutationszeichenfolge wird relativ zum rechtshändigen Koordinatensystem mit der Z-Achse nach oben durch Haply angegeben — eine Permutation aus X, Y, Z, optional mit vorangestelltem + oder -. Beispiele:
XYZ,ZYX,+Y-Z+X,X-ZYYZXbedeutet, dass SieYder Rechtsrichtung von Haply entspricht, IhrZin Vorwärtsrichtung von Haply und IhrXin Aufwärtsrichtung von Haply.
Beispiel für ein linkshändiges System mit der Z-Achse nach oben (Unreal, X-YZ):
{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
session.set_basisDie Konvention für Achsenvorzeichen hat sich zwischen der veralteten session.set_basis und
session.configure.basis geändert. Eine Permutation, die mit dem alten Befehl funktioniert hat, kann mit dem neuen eine invertierte Transformation erzeugen. Kehren Sie bei Bedarf die Vorzeichen der Achsen um (z. B. wird X-ZY zu XZ-Y).
Befehle für alle Geräte
Prüfen (alle Geräte)
Verwenden Sie Probing-Befehle, um aktuelle Positions- und Orientierungsdaten abzufragen, ohne Kräfte oder andere Simulationsänderungen anzuwenden.
- inverse3:
probe_position - Verse-Griffe (
verse_grip,wireless_verse_grip):probe_orientation
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"probe_position": {}
}
}
],
"verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
],
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
]
}
Transformation anwenden (alle Geräte)
Legt die Workspace-Transformation (vom Gerätebereich in den Anwendungsbereich) für ein Gerät fest.
Anders als die übrigen commands-Einträge sind set_transform dauerhaft – Der Dienst behält den zuletzt gesendeten Wert bei, bis Sie einen neuen senden. Sie müssen ihn nicht bei jedem Tick übertragen. Da sein Zweck jedoch die Kamera- bzw. Szenennavigation ist, entspricht das Streamen mit Tick-Rate dem erwarteten Nutzungsmuster bei kontinuierlicher Bewegung (z. B. eine Transformation pro Frame, während der Benutzer die Szene verschiebt).
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_transform": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
Bei den Geräten „Verse Grip“ und „Wireless Verse Grip“ wirkt sich nur die Komponente rotation der Transformation – der Grip meldet ausschließlich die Ausrichtung, daher gibt es nichts, was position oder scale skalieren oder verschieben könnten. Die Felder position und scale werden allein aus Gründen der Schemakonsistenz mit „ Inverse3 “ akzeptiert (und im Snapshot zurückgegeben). Üblicherweise belässt man sie auf den Identitätswerten (position = {0,0,0}, scale = {1,1,1}).
Konfiguration für „ Inverse3 “-Geräte
Konfigurationseinträge werden in der configure-Karte gesendet. Sie werden dauerhaft gespeichert – entweder einmalig oder bei Änderungen.
Voreinstellung
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
}
}
]
}
Verfügbare Werte: device_defaults, arm_front, arm_front_centered, led_front, led_front_centered, custom.
Grundlagen (auf Geräteebene)
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}
Halterung
Legt die Montage-Transformation des Geräts fest. Beachten Sie den transform-Wrapper innerhalb von mount.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"mount": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
Die Befehlsseite (Client zu Server) umfasst die Transformation: "mount": { "transform": { ... } }.
Die Snapshot-Seite (Server zu Client) ist flach aufgebaut: "mount": { "position": {...}, "rotation": {...}, "scale": {...} }.
Das ist beabsichtigt – Befehle verwenden einen einheitlichen Wrapper, während Snapshots die Transformation direkt serialisieren. Achten Sie darauf, die Snapshot-Struktur nicht in eine Befehlsnutzlast zu kopieren.
Dämpfung
Steuert die gleichmäßige und/oder richtungsabhängige Dämpfung. Es muss mindestens ein Feld vorhanden sein.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}
Sie können auch eine richtungsabhängige Dämpfung oder beide Varianten gleichzeitig einstellen:
"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }
Krafttor
Legt den Dämpfungsfaktor des Force Gate fest (0,0 bis 1,0).
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}
Inverse3-Befehle
Um Befehle an ein inverse3-Um das Gerät zu senden, fügen Sie einen Eintrag mit einer passenden device_id unter dem Schlüssel inverse3 hinzu. Befehle gelten pro Tick und müssen bei jedem Tick erneut gesendet werden, um aktiv zu bleiben.
Ein einzelnes Gerät steuern:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Mehrere Geräte in einer Nachricht steuern:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
},
{
"device_id": "049E",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Cursorposition festlegen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"position": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Cursor-Kraft setzen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Winkelposition festlegen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"angles": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Winkeldrehmomente einstellen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torques": {
"torques": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Konfiguration für Verse Grip
Die Geräte „Verse Grip“ und „Wireless Verse Grip“ unterstützen dieselben configure-Schlüssel wie „ Inverse3 “ für preset, basis und mount.
Erweitertes Protokoll für Verse Grip
Der Befehl set_extension_data ist Teil des erweiterten Protokolls für Verse Grips und wird mit Grip-Versionen verwendet, die das Protokoll für Board-Erweiterungen implementieren.
Grip-Erweiterungsdaten festlegen
Unterstützte Datenlängen:
- Bis zu 20 Byte in Upstream-Richtung (Client → Gerät).
- Bis zu 12 Byte in Downstream-Richtung (Gerät → Client), zurückgegeben in der Nachricht „State Update“ als
state.extension_data.
Datenspezifikation:
- Array-Länge: 20 Byte
- Wertebereich: Jeder Wert liegt im Bereich von 0 bis 255
{
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"set_extension_data": {
"extension_data": [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
}
}
}
]
}
Beispiele
Beispiel: Anfängliche Geräteliste (Payload)
Der Dienst sendet eine Nachricht mit der vollständigen Geräteliste, sobald eine WebSocket-Verbindung hergestellt wird. Die erste Nachricht enthält Folgendes: JSON-Format:
{
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"port": "COM13",
"device_info": {
"major_version": 7,
"minor_version": 1,
"id": "04BA",
"device_type": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 1
},
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": {
"enabled": true
},
"home_return": {
"enabled": false
},
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": 0 }
},
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"config": {
"type": "verse_grip",
"port": "COM3",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"config": {
"type": "wireless_verse_grip",
"port": "COM6",
"major_version": 1,
"minor_version": 4,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
]
}
Beispiel: State-Update-Nutzlast
Der Dienst sendet für jede empfangene Nachricht eine Statusaktualisierungsnachricht mit dem Status aller Geräte.
Wenn Sie den Zustand des Systems abfragen möchten, müssen Sie ihm zuvor eine Nachricht senden (beispielsweise einen Probing-Befehl oder einen Kraftwert, auch wenn die Werte null sind). Dies ist besonders wichtig, wenn Geräte als Eingangsquellen verwendet werden (z. B. zur Positionsverfolgung), ohne dass Kräfte ausgeübt werden.
Die Status-Update-Meldung lautet wie folgt JSON-Format:
{
"inverse3": [
{
"device_id": "04BA",
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
],
"custom_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"extension_data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"ready": true
}
}
]
}