Simulationskanal
Überblick
Der Simulationskanal ist ein hochfrequenter, bidirektionaler WebSocket-Kanal, der zum Austausch des Gerätezustands sowie zum Senden von Session- 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 initialen Inventar, die die vollständige Geräteliste enthält.
- Danach sendet der Server für jede vom Client empfangene Nachricht genau eine State-Update-Nachricht.
- Jedes State Update enthält den Zustand (und Status) aller Geräte.
Diese Seite beschreibt das Kernprotokoll des Simulationskanals und dessen Befehle.
Zusätzliche Module bzw. Funktionen können das System um eigene Befehle und/oder Zustandsfelder erweitern und 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 State-Update-Nachricht mit dem Zustand aller Geräte.
Das bedeutet:
- Wenn Sie einen aktuellen Snapshot des Gerätezustands benötigen, müssen Sie etwas senden (einen Session-Befehl, einen Abfragebefehl oder einen Gerätebefehl).
- Der Kanal verhält sich wie eine von Client-Nachrichten gesteuerte „Tick“-Schleife.
Polling ohne Anwendung von Kräften (Abfragebefehle)
Wenn Sie Zustandsänderungen ohne Anwendung von Kräften oder Änderung von Simulationsparametern beobachten möchten, verwenden Sie Abfragebefehle:
probe_positionfürinverse3probe_orientationfür Verse Grips
Abfragebefehle enthalten keine Befehlsdaten und veranlassen lediglich die Abfrage von Geräteinformationen, damit Positions- und Orientierungsdaten bei der nächsten State-Update-Nachricht aktuell 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 sind für reine Überwachungssitzungen gedacht (z. B. Haply Hub), die den Gerätezustand abfragen müssen, ohne das Gerät anzusteuern.
Konfiguration vs. Zustand
- Die Nachricht mit dem initialen Inventar enthält
config,stateundstatusder Geräte. - Reguläre State-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: Preset, Basis, Montage, Filter, Moduleinstellungen | Einmal senden oder bei Änderungen |
commands | Pro Tick, nicht persistent: Kraft, Position, Drehmomente | Muss in jedem Tick gesendet werden |
Einträge in commands (set_cursor_force, set_cursor_position, set_angular_torques, set_angular_position) werden einmal pro Tick angewendet und nicht gespeichert – wenn Sie sie 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 persistent – der Dienst behält
die zuletzt gesendete Transformation, bis Sie eine neue senden. Sie müssen sie nicht
in 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 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 Haply-Koordinaten und dem Koordinatensystem Ihrer Anwendung (siehe Basis weiter unten).inverse3[*].configure.preset: wählt eine benannte Werkskonfiguration 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
Dieser Abschnitt beschreibt das übergeordnete Envelope und die Nachrichtentypen. Vollständige 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.
Initiales Inventar (Server → Client)
Wird einmal unmittelbar nach dem Herstellen einer WebSocket-Verbindung gesendet.
Jeder Eintrag enthält:
device_idconfigstatestatus
Siehe: Beispiel: Payload des initialen Inventars
State Update (Server → Client)
Wird für jede Client-Nachricht einmal gesendet.
Jeder Eintrag enthält:
device_idstatestatus
Siehe: Beispiel: State-Update-Payload
Envelope 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 als auch 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 Dictionary 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 immer jedes Feld ausgeben.
"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
Befehlsreferenz
Session
Vollständiges Rendering des Zustands erzwingen
Fordern Sie eine Momentaufnahme aller Gerätezustände und Konfigurationen an.
{
"session": {
"force_render_full_state": {}
}
}
Session konfigurieren
Die sitzungsbezogene, persistente Konfiguration wird über session.configure gesendet. Dies entspricht dem Muster der configure-Map auf Geräteebene.
Profil
Legt den Namen des Sitzungsprofils fest, der von 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 Basis-Zuordnung für die gesamte Sitzung fest. Die Basis-Zuordnung beschreibt, wie Haply-Koordinaten in das Koordinatensystem Ihrer Anwendung umgewandelt werden; nachdem sie gesetzt wurde, werden alle Gerätezustände in dieser Basis zurückgegeben und alle gesendeten Werte darin interpretiert.
Die Permutationszeichenkette wird relativ zum rechtshändigen Koordinatensystem mit Z-Achse nach oben von Haply angegeben — eine Permutation aus X, Y, Z, optional mit vorangestelltem + oder -. Beispiele:
XYZ,ZYX,+Y-Z+X,X-ZYYZXbedeutet, dass IhrYder Rechtsrichtung von Haply entspricht, IhrZder Vorwärtsrichtung von Haply und IhrXder Aufwärtsrichtung von Haply.
Beispiel für ein linkshändiges System mit Z-Achse nach oben (Unreal, X-YZ):
{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
session.set_basisDie Konvention für Achsenvorzeichen hat sich zwischen dem 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
Probing (alle Geräte)
Verwenden Sie Probing-Befehle, um aktuelle Positions- und Orientierungsinformationen anzufordern, ohne Kräfte oder andere Simulationsänderungen anzuwenden.
- inverse3:
probe_position - Verse grips (
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 setzen (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 ist 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, ist beim kontinuierlichen Bewegen das Streamen mit Tick-Rate das erwartete Nutzungsmuster (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 aus – der Grip meldet ausschließlich die Orientierung, daher gibt es nichts, was position oder scale skalieren oder verschieben könnten. Die Felder position und scale werden allein aus Gründen der Schema-Konsistenz mit Inverse3 akzeptiert (und im Snapshot zurückgegeben). Üblich ist es, sie auf den Identitätswerten zu belassen (position = {0,0,0}, scale = {1,1,1}).
configure für Inverse3-Geräte
Konfigurationseinträge werden in der configure-Map gesendet. Sie sind persistent -- einmal senden oder bei Änderungen.
Preset
{
"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.
Basis (auf Geräteebene)
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}
Mount
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) umschließt 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. Mindestens ein Feld muss vorhanden sein.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}
Sie können auch eine richtungsabhängige Dämpfung oder beide Varianten gleichzeitig setzen:
"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }
Force Gate
Legt die Dämpfungsverstärkung 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-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
}
}
}
}
]
}
Cursor-Position setzen
{
"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 setzen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"angles": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Winkeldrehmomente setzen
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torques": {
"torques": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
configure 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 setzen
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:
- Arraylänge: 20 Byte
- Wertebereich: jeder Wert liegt im Bereich 0–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: Initiale Geräteliste (Payload)
Der Dienst sendet eine Nachricht mit der vollständigen Geräteliste, sobald ein WebSocket verbunden wird. Die initiale Nachricht hat 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 State-Update-Nachricht mit dem Zustand aller Geräte.
Wenn Sie den Zustand des Systems erfahren möchten, müssen Sie ihm vorab eine Nachricht senden (zum Beispiel einen Probing-Befehl oder einen Kraftwert, auch wenn die Werte null sind). Dies ist besonders wichtig, wenn Geräte als Eingabequellen verwendet werden (z. B. zur Positionsverfolgung), ohne Kräfte anzuwenden.
Die State-Update-Nachricht hat 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
}
}
]
}