Simulation Kanal
Übersicht
Der Simulationskanal ist ein bidirektionaler WebSocket-Kanal mit hoher Übertragungsrate, der zum Austausch von Gerätestatusdaten und zum Senden von Sitzungs- oder Gerätebefehlen dient.
Standard-URL: ws://localhost:10001
Der Port kann in der Konfiguration geändert werden.
Kernvertrag (wichtig):
- Wenn sich ein Client verbindet, sendet der Dienst eine erste Bestandsmeldung, die die vollständige Liste der Geräte enthält.
- Danach sendet der Server für jede vom Client empfangene Nachricht genau eine Statusaktualisierungsnachricht.
- Jedes Status-Update enthält den Status (und den Zustand) aller Geräte.
Auf dieser Seite werden das Kernprotokoll und die Befehle des Simulationskanals beschrieben.
Zusätzliche Module/Funktionen können sich registrieren und das System um eigene Befehle und/oder Statusfelder erweitern. 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, die den Status aller Geräte enthält.
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 Kraftanwendung (Sondenbefehle)
Wenn Sie Zustandsänderungen beobachten möchten, ohne Kräfte anzuwenden oder Simulationsparameter zu ändern, verwenden Sie die Abfragebefehle:
probe_positionfürinverse3probe_orientationfür Verse-Griffe
Abfragebefehle enthalten keine Befehlsdaten, sondern lösen lediglich Abfragen von Geräteinformationen aus, damit die Positions- und Orientierungsdaten bei der nächsten Statusaktualisierung auf dem neuesten Stand sind.
Simulationssitzungen, die bereits Steuerbefehle senden (set_cursor_force, set_cursor_position, usw.) tun nicht Es ist nicht erforderlich, Abfragebefehle zu senden – der Gerätestatus ist immer im Antwortrahmen enthalten. Abfragen dienen dazu, nur zur Überwachung Sitzungen (z. B. Haply ), die den Gerätestatus abfragen müssen, ohne das Gerät anzusteuern.
Konfiguration vs. Zustand
- Die Ausgangsbestand Die Nachricht enthält das Gerät
config,stateundstatus. - Regulär Aktuelles aus dem Bundesstaat Nachrichten enthalten Gerät
stateundstatus.
Wenn Sie erneut einen Snapshot einschließlich der Konfiguration benötigen, verwenden Sie session.force_render_full_state.
configure vs commands
Gerätemeldungen unterstützen zwei separate Zuordnungen: configure und commands. Sie dienen unterschiedlichen Zwecken:
| Karte | Zweck | Häufigkeit |
|---|---|---|
configure | Einmalige dauerhafte Konfiguration: Voreinstellungen, Basis, Einbindung, Filter, Moduleinstellungen | Einmal senden oder bei Änderung |
commands | Pro Takt, 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 angewendet einmal pro Takt und wird nicht gespeichert -- Wenn Sie die Übertragung beenden, hört der Effekt sofort auf 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 lebt in commands aber ist anhaltend — Der Dienst behält
die zuletzt gesendete Transformation bei, bis Sie eine neue senden. Sie müssen nicht haben es bei jedem Tick zu übertragen. Da es jedoch für die Kamera- und Szenennavigation gedacht ist,
ist das Streaming mit Tick-Rate (z. B. eine Transformation pro Frame, wenn der Benutzer die
Szene schwenkt) das erwartete Nutzungsmuster für die Navigation mit kontinuierlicher Bewegung.
Koordinatensysteme
Haply standardmäßig ein rechtshändiges Koordinatensystem mit Z-Achse nach oben.
Zwei Konfigurations-Einträge beeinflussen, wie Koordinaten interpretiert und zurückgegeben werden:
session.configure.basis: Ändert die Zuordnung zwischen Haply und dem Koordinatensystem Ihrer Anwendung (siehe Grundlage (siehe unten).inverse3[*].configure.preset: Wählt eine benannte Fabrikkonfiguration aus, die den Ursprung des Arbeitsbereichs (z. B.arm_front_centeredsetzt(0, 0, 0)(im Workspace-Center). Siehe den Abschnitt „Konfigurieren“ für das jeweilige Gerät.
Nachrichtenformate
In diesem Abschnitt werden die allgemeinen Rahmen- und Nachrichtentypen beschrieben. Ausführliche Beispiele finden Sie weiter unten in diesem Dokument.
Gerätegruppen
Die Nachrichten werden auf der obersten Ebene nach Gerätetyp gruppiert (z. B. inverse3, verse_grip, wireless_verse_grip). Jeder Gerätetyp-Schlüssel ist einem Array von gerätespezifischen Objekten zugeordnet.
Anfänglicher Bestand (Server → Client)
Wird unmittelbar nach dem Aufbau einer WebSocket-Verbindung einmal gesendet.
Jeder Eintrag enthält:
device_idconfigstatestatus
Siehe: Beispiel: Payload für den Anfangsbestand
Statusaktualisierung (Server → Client)
Wird einmal pro Client-Nachricht gesendet.
Jeder Eintrag enthält:
device_idstatestatus
Siehe: Beispiel: Payload für Statusaktualisierung
Sitzungsbefehlshüllkurve (Client → Server)
Sitzungsbefehle sind Aktionen, die für die aktuelle Verbindung/Sitzung gelten und nicht gerätespezifisch sind.
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
Gerätebefehls-Envelope (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 gesendet werden können. Jeder Geräteeintrag unterstützt sowohl eine configure Karte und ein commands Karte.
{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}
Sie können mehrere Einträge hinzufügen, um mehrere Geräte desselben Typs in einer einzigen Nachricht anzusteuern.
Beachten Sie, dass commands ist ein Verzeichnis und kann mehrere Befehle für ein bestimmtes Gerät enthalten, jedoch nur einen pro Befehlstyp.
Die execute Feld
Jeder Befehl oder Konfigurations-Eintrag akzeptiert einen optionalen "execute" boolesch (Standard true). Einstellen "execute": false damit der Eintrag analysiert, aber nicht angewendet wird. Dies ist nützlich für reflektionsbasierte 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 } }
Befehlsübersicht
Sitzung
Vollständigen Status erzwingen
Fordern Sie eine Momentaufnahme aller Gerätestatus und -konfigurationen an.
{
"session": {
"force_render_full_state": {}
}
}
Sitzung konfigurieren
Die persistente Konfiguration auf Sitzungsebene wird über session.configure. Dies entspricht der Geräteebene configure Kartenmuster.
Profil
Legt den Namen des Sitzungsprofils fest – dieser wird von Haply verwendet, um die Simulation zu erkennen und die gerätespezifischen Anpassungen pro App zu speichern.
{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
Basis (auf Sitzungsebene)
Legt die Basiszuordnung für die gesamte Sitzung fest. Die Basiszuordnung beschreibt, wie Haply in das Koordinatensystem Ihrer Anwendung umgewandelt werden; nachdem sie festgelegt wurde, werden alle Gerätestatus in dieser Basis zurückgegeben und alle Werte, die Sie senden, werden in dieser Basis interpretiert.
Die Permutationsfolge wird relativ zu Haply rechtshändigem / Z-nach-oben-
Koordinatensystem ausgedrückt – eine Permutation von X, Y, Z, optional mit dem Präfix
+ oder -. Beispiele:
XYZ,ZYX,+Y-Z+X,X-ZYYZXbedeutet, dass deinYHat Haply vielleicht recht, deinZist Haply Stürmer, deinXIst Haply an der Reihe?
Beispiel für eine linkshändige Z-Achse (Unreal, X-YZ):
{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
session.set_basisDie Konvention für die Achsenbezeichnungen hat sich zwischen den älteren Versionen geändert session.set_basis und
session.configure.basis. Eine Permutation, die unter dem alten Befehl funktionierte,
kann unter dem neuen Befehl eine umgekehrte Transformation ergeben – kehren Sie die Vorzeichen der Achsen
bei Bedarf um (z. B. X-ZY wird XZ-Y).
Alle Gerätebefehle
Prüfen (alle Geräte)
Verwenden Sie Abfragebefehle, um aktuelle Positions- und Orientierungsdaten abzurufen, 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 festlegen (alle Geräte)
Lege die Arbeitsbereichstransformation (vom Geräteraum in den Anwendungsraum) für ein Gerät fest.
Im Gegensatz zu den anderen commands Einträge, set_transform ist anhaltend — Der
Dienst behält den zuletzt gesendeten Wert bei, bis Sie einen neuen senden. Sie müssen
ihn nicht bei jedem Tick übermitteln, aber da er für die Kamera- und Szenennavigation gedacht ist,
ist das Streamen mit Tick-Rate das erwartete Nutzungsmuster für kontinuierliche Bewegungen
(z. B. eine Transformation pro Frame, während der Benutzer die Szene schwenkt).
{
"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 Verse-Grip- und Wireless-Verse-Grip-Geräten gilt nur die rotation Komponente
der Transformation wirkt sich aus – der Griff gibt lediglich die Ausrichtung an, daher gibt es
nichts für position oder scale zu skalieren oder zu verschieben. Das position und
scale Felder werden (und im Snapshot wiedergegeben) ausschließlich aus Gründen der Schemakonsistenz
mit Inverse3 akzeptiert; sie bleiben auf ihren Standardwerten
(position = {0,0,0}, scale = {1,1,1}) ist gängige Praxis.
Inverse3 konfigurieren
Konfigurationseinträge werden in der configure map. Sie sind persistent – einmalig senden oder bei Änderung.
Voreinstellung
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
}
}
]
}
Mögliche Werte: device_defaults, arm_front, arm_front_centered, led_front, led_front_centered, custom.
Grundlage (auf Geräteebene)
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}
Halterung
Legt die Transformationsfunktion für die Geräte-Einbindung fest. Beachten Sie, dass transform Verpackung innen 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 Befehl Seite (Client zu Server) umschließt die Transformation: "mount": { "transform": { ... } }.
Die Momentaufnahme Die Verbindung (vom Server zum Client) ist unidirektional: "mount": { "position": {...}, "rotation": {...}, "scale": {...} }.
Dies ist beabsichtigt – Befehle verwenden einen einheitlichen Wrapper, während Snapshots die Transformation direkt serialisieren. Achten Sie darauf, die Form des Snapshots nicht in die Nutzdaten eines Befehls zu kopieren.
Dämpfung
Steuert die gleichmäßige und/oder gerichtete Dämpfung. Mindestens ein Feld muss ausgefüllt werden.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}
Sie können auch die Richtungsdämpfung einstellen oder beides gleichzeitig:
"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }
Krafttor
Legt den Dämpfungsfaktor des Force-Gates fest (0,0 bis 1,0).
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}
Inverse3
Um Befehle an einen inverse3 Gerät, fügen Sie einen Eintrag mit einer entsprechenden device_id unter dem inverse3 Taste. Befehle gelten nur für einen Takt und müssen bei jedem Takt erneut gesendet werden, um aktiv zu bleiben.
Ein Gerät steuern:
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Mehrere Geräte mit einer einzigen 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 einstellen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"position": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Cursorkraft einstellen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Winkellage einstellen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"angles": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Drehmomente einstellen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torques": {
"torques": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Verse Grip konfigurieren
Verse Grip und Wireless Verse Grip-Geräte unterstützen dasselbe configure Schlüssel als Inverse3 preset, basisund mount.
Erweiterter Vers-Griff
Die set_extension_data Der Befehl ist Teil des erweiterten Protokolls für Verse-Griffe und wird bei Griffversionen verwendet, die das Kommunikationsprotokoll für Board-Erweiterungen implementieren.
Daten zur Griffverlängerung einstellen
Unterstützte Datenlängen:
- Bis zu 20 Byte in Upstream-Richtung (Client → Gerät).
- Bis zu 12 Byte in Abwärtsrichtung (Gerät → Client), die in der Statusaktualisierungsnachricht als
state.extension_data.
Angaben zu den Daten:
- Array-Länge: 20 Byte
- Wertebereich: Jeder Wert liegt zwischen 0 und 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 Bestandsdaten
Der Dienst sendet eine Nachricht mit der vollständigen Geräteliste, sobald eine WebSocket-Verbindung hergestellt ist. 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: Nutzdaten für Statusaktualisierung
Der Dienst sendet für jede empfangene Nachricht eine Statusaktualisierungsnachricht, die den Status aller Geräte enthält.
Wenn Sie den Status der Maschine erfahren möchten, müssen Sie ihr zuvor eine Nachricht senden (zum Beispiel einen Abtastbefehl oder einen Kraftwert, auch wenn die Werte Null sind). Dies ist besonders wichtig, wenn Sie Geräte als Eingangsquellen (z. B. zur Positionserfassung) verwenden, ohne Kräfte auszuüben.
Die Statusmeldung enthält Folgendes 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
}
}
]
}