Chaîne de simulation
Présentation générale
Le canal de simulation est un canal WebSocket bidirectionnel à haute fréquence utilisé pour échanger des informations sur l'état des appareilset pour transmettre des commandes de session ou d'appareil.
URL par défaut : ws://localhost:10001
Vous pouvez modifier le port dans les paramètres.
Règles essentielles (important) :
- Lorsqu'un client se connecte, le service envoie un message d'inventaire initialcontenant la liste complète des appareils.
- Par la suite, le serveur envoie exactement un message de mise à jour d'état pour chaque message reçu du client.
- Chaque mise à jour d'état comprend l'état (et les informations d'état) de tous les appareils.
Cette page présente les protocoles et commandes essentiels du canal de simulation.
Les modules ou fonctionnalités supplémentaires peuvent étendre le système en enregistrant leurs propres commandes et/ou champs d'état. Ceux-ci sont documentés séparément dans la section consacrée aux modules.
Règles de protocole
Une réponse par message client
Le service envoie, pour chaque message reçu du client, un message de mise à jour d'état contenant l'état de tous les appareils.
Cela signifie que :
- Si vous avez besoin d'un instantané récent de l'état de l'appareil, vous devez impérativement envoyer une commande(commande de session, commande de sonde ou commande de l'appareil).
- Ce canal fonctionne comme une boucle « tick » déclenchée par les messages du client.
Interrogation sans application de force (commande « probe »)
Pour observer les changements d'état sans appliquer de force ni modifier les paramètres de simulation, utilisez la commande « probe » :
inverse3dans le cas oùprobe_position- En ce qui concerne la prise « Verse grip »
probe_orientation
La commande « probe » ne contient pas de données de commande ; elle force la consultation des informations sur l'appareil afin que les données de position et d'orientation soient à jour lors de la prochaine mise à jour d'état.
Une commande de contrôle (set_cursor_force, set_cursor_position Les sessions de simulation qui transmettent (etc.) peuvent envoyer des commandes de sonde Ce n'est pas nécessaire -- L'état de l'appareil est toujours inclus dans la trame de réponse. La sonde doit interroger l'état de l'appareil sans le mettre sous tension. Réservé à la surveillance Il est destiné à une session (par exemple, « Haply Hub »).
Composition et état
- Inventaire initial Le message indique que l'appareil
config,state,statusest inclus. - Généralités Mise à jour du statut Le message indique que l'appareil
stateWastatusest inclus.
Si vous avez à nouveau besoin d'un instantané incluant la configuration, session.force_render_full_stateVeuillez utiliser .
configureWa commands
Les messages du périphérique sont configureWa commandsprend en charge deux cartes distinctes. Ces deux cartes ont des objectifs différents :
| Carte | Utilisation | Fréquence de transmission |
|---|---|---|
configure | Paramètres permanents ponctuels : préréglages, base, montage, filtres, paramètres des modules | À chaque envoi ou modification |
commands | Paramètres non permanents appliqués à chaque tick : force, position, couples | Il faut impérativement l'envoyer à chaque transaction. |
commandsles éléments (set_cursor_force, set_cursor_position, set_angular_torques, set_angular_position) est S'applique une fois par clic et n'est pas enregistrée. -- Si la transmission est interrompue, l'effet disparaît immédiatement et l'appareil revient à l'état de veille. Parmi les éléments qui doivent être conservés d'un tick à l'autre sans qu'il soit nécessaire de les retransmettre, on trouve : configureVeuillez utiliser .
set_transformIl s'agit d'un cas exceptionnel.set_transform은 commandsbien qu'il fasse partie de… permanent-- Le service conserve la dernière transformation envoyée jusqu’à ce qu’une nouvelle transformation lui soit transmise.
Il n’est pas nécessaire d’en envoyer une à chaque tick. Toutefois, comme l’objectif est la navigation dans la caméra ou la scène, dans le cadre d’une navigation en continu, le modèle d’utilisation prévu consiste à diffuser en continu à chaque tick (par exemple, envoyer une transformation par image lorsque l’utilisateur se déplace dans la scène).
Système de coordonnées
Le système de coordonnées par défaut de Haply est un système de coordonnées de type « main droite »avec l'axe Z vers le haut.
Deux paramètres de configuration influencent la manière dont les coordonnées sont interprétées et renvoyées :
session.configure.basis: Modifie la correspondance entre les coordonnées de l'Haply et le système de coordonnées de l'application (voir ci-dessous Base (cf.).inverse3[*].configure.preset: Sélectionnez une configuration d'usine nommée permettant de définir l'origine de l'espace de travail (par exemple :arm_front_centeredest(0, 0, 0)(Placez-le au centre de l'espace de travail). Reportez-vous à la section « Configure » correspondant à chaque appareil.
Format du message
Cette section décrit les enveloppes de haut niveau et les types de messages. Des exemples complets sont fournis plus loin dans ce document.
Groupe de périphériques
Les messages sont regroupés par type d'appareil au niveau le plus élevé (par exemple : inverse3, verse_grip, wireless_verse_grip). Chaque clé de type d'appareil est mappée à un tableau d'objets spécifiques à cet appareil.
Inventaire initial (serveur → client)
Elle est envoyée une seule fois, immédiatement après l'établissement de la connexion WebSocket.
Chaque rubrique comprend les éléments suivants :
device_idconfigstatestatus
Référence : Exemple : charge utile de l'inventaire initial
Mise à jour de l'état (serveur → client)
Il est envoyé une fois par message client.
Chaque rubrique comprend les éléments suivants :
device_idstatestatus
Référence : Exemple : charge utile de mise à jour d'état
Enveloppe de commande de session (client → serveur)
Les commandes de session s'appliquent à la connexion/session en cours et ne dépendent pas de l'appareil utilisé.
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
Enveloppe de commande de périphérique (client → serveur)
Les commandes destinées aux périphériques étant transmises sous forme de tableau sous la clé « type de périphérique », il est possible d'envoyer une commande à un ou plusieurs périphériques dans un seul message. Chaque entrée de périphérique est configure Carte et commands Toutes les cartes sont prises en charge.
{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}
Il est possible d'inclure plusieurs éléments dans un même message afin d'envoyer des commandes à plusieurs appareils du même type.
commandsIl s'agit d'un dictionnaire qui peut contenir plusieurs commandes pour un appareil donné, mais une seule par type de commande.
execute Champ
Toutes les commandes ou options de configuration sont facultatives. "execute" Booléen (valeur par défaut true) est reçu. "execute": falseSi vous sélectionnez cette option, les éléments sont simplement analysés, mais ne sont pas appliqués. Il s'agit d'un sérialiseur basé sur la réflexion qui affiche toujours tous les champs (par exemple, Unity JsonUtility) est utile.
"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
Référence des commandes
session
Rendu forcé de l'état complet
Demandez un instantané de l'état et de la configuration de tous les appareils.
{
"session": {
"force_render_full_state": {}
}
}
Session « Configurer »
La configuration permanente au niveau de la session est session.configureest transmis via. Il s'agit d'un niveau de périphérique configure Il suit à la lettre le schéma de la carte.
Profil
Définissez le nom du profil de session. Ce nom est utilisé par Haply Hub pour identifier la simulation et conserver les paramètres de configuration spécifiques à l'application.
{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
Base (au niveau de la session)
Définit le mappage de base pour l'ensemble de la session. Le mappage de base décrit la manière dont les coordonnées Haply sont converties dans le système de coordonnées de l'application. Une fois ce mappage défini, tous les états ultérieurs de l'appareil sont renvoyés selon cette base, et toutes les valeurs transmises sont également interprétées par rapport à cette base.
Les chaînes de permutations sont représentées selon le système de coordonnées de la main droite de Haply / le système de coordonnées Z-up. X, Y, Zest une permutation de, et éventuellement + ou bien - Vous pouvez ajouter un symbole au début. Voici quelques exemples :
XYZ,ZYX,+Y-Z+X,X-ZYYZXest celui de l'utilisateurYÀ droite de Haply,ZL'axe se trouve à l'avant de Haply,XCela signifie que l'axe pointe vers le haut de Haply.
Exemple de système de coordonnées « Z-up » pour la main gauche (Unreal, X-YZ) :
{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
session.set_basisMigration depuisLes règles de signe des axes sont des commandes héritées session.set_basisNouvelle commande dans session.configure.basisa été modifiée. Les permutations qui fonctionnaient avec la commande d'origine peuvent produire une transformation inversée avec la nouvelle commande. Si nécessaire, inversez le signe de l'axe (par exemple : X-ZYest XZ-Y(cela devient).
Toutes les commandes de périphériques
Sondage (tous les appareils)
Pour demander les informations de position et d'orientation les plus récentes sans appliquer de force ni d'autres modifications de simulation, utilisez la commande de palpage.
- inverse3:
probe_position - Prise « Verse » (
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": {}
}
}
]
}
Paramètres de conversion (tous les appareils)
Configurez la conversion de l'espace de travail de l'appareil (de l'espace de l'appareil vers l'espace de l'application).
Autre commands Contrairement aux éléments set_transform은 permanentEn effet, le service conserve la dernière valeur envoyée jusqu’à ce qu’une nouvelle valeur lui soit transmise. Il n’est pas nécessaire d’envoyer des données à chaque tick, mais comme l’objectif est la navigation dans la caméra ou la scène, il est courant, dans le cas de mouvements continus, de diffuser les données à la fréquence des ticks (par exemple : envoyer une transformation par image lorsque l’utilisateur se déplace dans la scène).
{
"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 }
}
}
}
}
]
}
Pour les poignées Verse et les poignées Verse sans fil, la conversion de rotation Seuls les composants ont un effet. Comme cette poignée ne tient compte que des informations d'orientation, positionIna scaleIl n'y a aucun élément à ajuster ou à déplacer. position et scale Le champ n'est autorisé que pour garantir la cohérence avec le schéma Inverse3 (il est également renvoyé tel quel dans les instantanés) et les valeurs identiques (position = {0,0,0}, scale = {1,1,1}) est la méthode standard.
Inverse3 Configuration de l'appareil
Les éléments constitutifs sont les suivants : configure Envoyer vers la carte. Cet élément étant permanent, envoyez-le une seule fois ou lorsque sa valeur change.
Préréglages
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
}
}
]
}
Valeurs disponibles : device_defaults, arm_front, arm_front_centered, led_front, led_front_centered, custom.
Base (au niveau de l'appareil)
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}
mont
Configurez la conversion du montage des périphériques. mount À l'intérieur de transform Faites attention au rappeur.
{
"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 }
}
}
}
}
]
}
commande Le côté client (du client vers le serveur) encapsule la conversion : "mount": { "transform": { ... } }.
instantané La structure (du serveur vers le client) est plate : "mount": { "position": {...}, "rotation": {...}, "scale": {...} }.
Il s'agit d'une conception délibérée. En effet, la commande « command » utilise un wrapper intégré, tandis que « snapshot » sérialise directement la transformation. Veillez à ne pas copier le format « snapshot » dans la charge utile de la commande « command ».
amortissement
Permet de contrôler l'amortissement uniforme et/ou l'amortissement directionnel. Au moins un champ doit obligatoirement être renseigné.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}
Vous pouvez configurer l'amortissement directionnel ou les deux à la fois :
"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }
Him Gate
Réglez le gain d'atténuation de la porte de puissance (0,0 à 1,0).
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}
Inverse3 commande
inverse3 Pour envoyer une commande à l'appareil, inverse3 correspondant à la clé ci-dessous device_idcontient des éléments comportant . La commande s'applique à chaque tick ; pour rester active, elle doit être renvoyée à chaque tick.
Envoyer une commande à un appareil :
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Envoyer des commandes à plusieurs appareils via un seul message :
{
"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
}
}
}
}
]
}
Définition de la position du curseur
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"position": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Réglage de la puissance du curseur
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Réglage de la position angulaire
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"angles": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Réglage du couple angulaire
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torques": {
"torques": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Configuration de Verse Grip
Les appareils Verse Grip et Wireless Verse Grip sont preset, basis, mountà propos de celui utilisé par Inverse3 configure Prend en charge les touches.
Extension Verse Grip
set_extension_data La commande fait partie du protocole d'extension Verse Grip et est utilisée dans les versions de Grip qui implémentent le protocole de communication d'extension de carte.
Configuration des données d'extension de la poignée
Longueur des données prise en charge :
- En amont (client → appareil) : 20 octets maximum.
- En aval (appareil → client) : 12 octets maximum, et
state.extension_dataOn revient alors au message « State Update ».
Spécifications des données :
- Longueur du tableau : 20 octets
- Plage de valeurs : chaque valeur est comprise entre 0 et 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
]
}
}
}
]
}
Exemple
Exemple : charge utile de la liste initiale des appareils
Lors de la connexion via WebSocket, le service envoie un message contenant la liste complète des appareils. Le message initial est le suivant : JSON Respectez le format suivant :
{
"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
}
}
]
}
Exemple : charge utile de mise à jour d'état
Le service envoie, pour chaque message reçu, un message de mise à jour d'état contenant l'état de tous les appareils.
Si vous souhaitez connaître l'état de l'appareil, vous devez envoyer un message au préalable (par exemple, une commande de sondage ou une valeur de force, même si celle-ci est égale à 0). Cela est particulièrement important lorsque vous utilisez l'appareil comme source d'entrée (par exemple, pour le suivi de position) sans lui appliquer de force.
Les messages de mise à jour d'état sont les suivants : JSON Respectez le format suivant :
{
"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
}
}
]
}