Canal de simulation
Présentation générale
Un canal de simulation est un canal WebSocket bidirectionnel à haute fréquence, utilisé pour échangerdes informations sur l'état des appareilset envoyer des commandesde session oudes commandes aux appareils.
URL par défaut : ws://localhost:10001
Les ports peuvent être modifiés dans la configuration.
Accords fondamentaux (importants) :
- Lorsqu'un client se connecte, le service envoie un message « Initial Inventory » contenant la liste complète des appareils.
- Par la suite, pourchaque messageenvoyé par le client,le serveurenverra exactementun message « State Update ».
- Chaque « State Update » contient l'état (state et status) detousles appareils.
Cette page présente les protocoles et commandesessentielsdu canal de simulation.
D'autres modules ou fonctionnalités peuvent être enregistrés et permettre d'étendre le système via leurs commandes et/ou champs d'état respectifs. Ces éléments sont décrits séparément dans la section « Modules ».
Règles du protocole
Chaque message client correspond à une réponse.
Pourchaque messageenvoyé par le client,le serviceenvoie un message « State Update » contenant l'état detousles appareils.
Cela signifie que :
- Si vous souhaitez obtenir un aperçu actualisé de l'état de l'appareil, vous devez envoyerl'une des commandes suivantes: une commande de session, une commande de diagnostic ou une commande relative à l'appareil.
- Ce canal fonctionne de manière similaire à une boucle « tick » pilotée par les messages du client.
Interrogation sans application de force (commande de détection)
Si vous souhaitez observer l'évolution de l'étatsans appliquerde force ni modifier les paramètres de simulation, veuillez utiliser la commande de détection :
probe_positionUtilisé pourinverse3probe_orientationÀ utiliser avec les poignées Verse
La commande de détection ne contient pas de données de commande ; elle sert uniquement à forcer 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 a été envoyée (set_cursor_force,set_cursor_position (etc.)Pas besoin deEnvoi d'une commande de détection — L'état de l'appareil est toujours inclus dans la trame de réponse. La commande de détection est destinée à être utilisée pourSurveillance uniquementles sessions (par exemple, « Haply Hub ») qui nécessitent d'interroger l'état des périphériques sans les piloter.
Configuration et état
- Inventaire initial Le message contient les informations relatives à l'appareil
config,stateetstatus。 - habituel Le point sur la situation Le message contient les informations relatives à l'appareil
stateetstatus。
Si vous souhaitez obtenir à nouveau un instantané complet contenant les informations de configuration, veuillez utiliser session.force_render_full_state。
configure et commands
Les informations sur les appareils prennent en charge deux mappages indépendants :configure et commands. Leurs utilisations sont différentes :
| Mappage | Utilisation | Fréquence d'envoi |
|---|---|---|
configure | Configuration unique et permanente : préréglages, valeurs de référence, mode d'installation, filtres, paramètres des modules | Envoyer une seule fois, ou envoyer en cas de modification |
commands | Entrée en vigueur à chaque tick, non persistante : force, position, couple | Il faut l'envoyer à chaque tick |
commands les entrées de (set_cursor_force,set_cursor_position,set_angular_torques,set_angular_position)Elle est appliquée à chaque tick et n'est pas enregistrée.——Si l'envoi est interrompu, l'effet disparaîtra immédiatement et l'appareil reviendra à l'état inactif. Pour les contenus devant rester actifs d'un tick à l'autre sans qu'il soit nécessaire de les renvoyer, veuillez utiliser configure。
set_transform Il s'agit d'un cas particulier.set_transform Situé à commands ci-dessous, mais c'estdurable——Le service conservera
la dernière transformation que vous avez envoyée jusqu’à ce que vous en envoyiez une nouvelle. VousCe n'est pas nécessaireIl est envoyé à chaque tick.
Cependant, comme il est destiné à la navigation dans la caméra ou la scène,
il est envoyé en continu à la fréquence des ticks lorsque l'utilisateur effectue un panoramique dans la scène (par exemple, en envoyant une transformation à chaque image),
ce qui correspond exactement au mode d'utilisation prévu pour la navigation en mouvement continu.
Système de coordonnées
Haply Le systèmede coordonnées droitierest utilisé par défaut,l'axe Z étant orienté vers le haut.
Deux entrées « configure » ont une incidence sur l'interprétation et le mode de restitution des coordonnées :
session.configure.basis: Modifier la relation de correspondance entre les coordonnées de Haply et le système de coordonnées de l'application (voir ci-dessous) Référence).inverse3[*].configure.preset: Sélectionnez une configuration d'usine nommée pour définir l'origine de l'espace de travail (par exemplearm_front_centeredva(0, 0, 0)(à placer au centre de l'espace de travail). Veuillez consulter la section « Configurer » de chaque appareil.
Format des messages
Cette section présente la structure de l'enveloppe de message de haut niveau ainsi que les types de messages. Vous trouverez des exemples complets plus loin dans ce document.
Regroupement des équipements
Les messages sont regroupés au niveau supérieur par type d'appareil (par exemple inverse3,verse_grip,wireless_verse_grip). Chaque clé de type d'appareil correspond à un tableau composé des différents objets d'appareil.
Inventaire initial (serveur → client)
À envoyer une seule fois, dès l'établissement de la connexion WebSocket.
Chaque entrée comprend :
device_idconfigstatestatus
Voir aussi :Exemple : Charge de l'inventaire initial
Mise à jour de l'état (serveur → client)
Chaque message client donne lieu à un envoi.
Chaque entrée comprend :
device_idstatestatus
Voir aussi :Exemple : Mise à jour de l'état de la charge
Enveloppe de commande de session (client → serveur)
Les commandes de session sont des opérations qui s'appliquent à la connexion ou à la session en cours,sans concerner un périphérique spécifique.
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
Enveloppe de commande de périphérique (client → serveur)
Les commandes destinées aux appareils sont envoyées sous forme de tableau sous la clé « type d'appareil », ce qui permet à un seul message d'envoyer simultanément des commandes à un ou plusieurs appareils. Chaque entrée d'appareil prend également en charge configure Mappage et commands Mappage.
{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}
Vous pouvez inclure plusieurs éléments dans un même message afin d'envoyer simultanément des commandes à plusieurs appareils d'un même type.
Veuillez noter que,commands Il s'agit d'un dictionnaire pouvant contenir plusieurs commandes pour un même appareil, mais il ne peut y avoir qu'une seule commande par type.
execute Champ
Chaque commande ou option de `configure` accepte un argument facultatif "execute" Valeur booléenne (par défaut : true). Paramètres "execute": false Ensuite, cet élément sera analysé mais ne sera pas appliqué. Cela s'applique aux sérialiseurs basés sur la réflexion qui affichent systématiquement tous les champs (par exemple, Unity JsonUtility) C'est très utile.
"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
Référence des commandes
Conversation
Forcer le rendu de l'état complet
Demander un instantané de l'état et de la configuration de tous les appareils.
{
"session": {
"force_render_full_state": {}
}
}
Configuration de la session
La configuration persistante au niveau de la session est assurée par session.configure Envoyer. Cela correspond à la fonction au niveau de l'appareil configure correspondant au mode de mappage.
Fichier de configuration
Définissez le nom du profil de session ;Haply Hub s'en sert pour identifier la simulation et enregistrer les réglages personnalisés de chaque application.
{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
Mappage de référence (au niveau de la session)
Définissez un mappage de référence pour l'ensemble de la session. Le mappage de référence décrit la manière dont les coordonnées de Haply sont converties dans le système de coordonnées de votre application ; une fois défini, tous les états de l'appareil seront renvoyés selon ce mappage, et toutes les valeurs que vous envoyez seront interprétées selon ce mappage.
Représenter la chaîne de caractères en se référant au système de coordonnées de droite / axe Z de Haply , c'est-à-dire X,Y,Z une disposition, qui peut être choisie sous la forme de + ou - En tant que préfixe. Exemple :
XYZ,ZYX,+Y-Z+X,X-ZYYZXExprimez votreYL'axe correspond au côté droit de Haply , votreZL'axe correspond à la partie avant de Haply , votreXL'axe correspond à la partie supérieure de Haply .
Exemple avec l'axe Z orienté vers le haut (main gauche) (Unreal,X-YZ) :
{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
session.set_basis MigrationAncienne version session.set_basis et session.configure.basis La convention relative aux signes des axes a changé. Une configuration qui fonctionnait avec l'ancienne commande peut entraîner une inversion des axes avec la nouvelle commande — veuillez inverser le signe de l'axe concerné si nécessaire (par exemple X-ZY devenir XZ-Y).
Toutes les commandes relatives aux appareils
Détection (tous les appareils)
La commande de détection permet de demander les dernières informations de position et d'attitude sans avoir à appliquer de force ni à modifier d'autres paramètres de simulation.
- 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": {}
}
}
]
}
Configuration des transformations (tous les appareils)
Configurer la transformation de l'espace de travail pour l'appareil (de l'espace de l'appareil à l'espace de l'application).
Autres commands Les entrées diffèrent,set_transform Ouidurable——Le service conserve la dernière valeur que vous avez envoyée jusqu’à ce que vous en envoyiez une nouvelle. Vous n’avez pas besoin de l’envoyer à chaque tick ; toutefois, comme il est destiné à la navigation dans la caméra ou la scène, il est prévu de l’envoyer de manière continue à la fréquence des ticks (par exemple, en envoyant une transformation à chaque image) dans le cadre de mouvements continus, tels que le panoramique de la scène par l’utilisateur.
{
"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 appareils Verse grip et Verse grip sans fil, seuls les éléments en cours de transformation rotation La force exercée a une incidence : la poignée ne transmet que l'orientation, par conséquent position et scale Il n'y a pas de contenu pouvant être agrandi ou déplacé.position et scale Ces champs ne sont acceptés que pour respecter la structure de Inverse3 (et seront affichés dans l'instantané) ; il est généralement recommandé de les conserver sous forme de valeurs unitaires (position = {0,0,0},scale = {1,1,1}).
Inverse3 Configuration du matériel
Les entrées de configuration ont été validées configure Envoi des mappages. Ceux-ci sont persistants : il suffit de les envoyer une seule fois, ou de les renvoyer en cas de modification.
Préréglages
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
}
}
]
}
Valeurs possibles :device_defaults,arm_front,arm_front_centered,led_front,led_front_centered,custom。
Mappage de référence (au niveau du périphérique)
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}
Installation
Configurer la conversion lors de l'installation de l'appareil. Remarque : mount interne transform Couche d'emballage.
{
"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 }
}
}
}
}
]
}
OrdreD'un côté (du client vers le serveur), la transformation est encapsulée :"mount": { "transform": { ... } }。
InstantanéD'un côté (du serveur vers le client), la structure est plate :"mount": { "position": {...}, "rotation": {...}, "scale": {...} }。
C'est un choix délibéré : les commandes utilisent une structure d'encapsulation uniforme, tandis que les instantanés sérialisent directement les transformations. Veillez à ne pas reproduire la structure des instantanés dans le corps de la commande.
Amortissement
Contrôle de l'amortissement uniforme et/ou de l'amortissement directionnel. Au moins un champ doit être renseigné.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}
Vous pouvez également régler l'amortissement directionnel, ou les deux à la fois :
"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }
Commande par force
Réglez le gain d'atténuation de la commande de force (de 0,0 à 1,0).
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}
Inverse3 Ordre
Il faut s'adresser à inverse3 Pour envoyer une commande à l'appareil, veuillez saisir inverse3 La touche contient un device_id Entrées correspondantes. La commande prend effet à chaque « tick » et doit être renvoyée à chaque « tick » pour rester active.
Envoyer une commande à un appareil spécifique :
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Envoyer une commande à plusieurs appareils dans 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éfinir 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 force 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 de serrage
{
"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 Verse Grip sans fil prennent en charge les mêmes fonctionnalités que Inverse3 configure Touche, servant à preset,basis et mount。
Extension Verse Grip
set_extension_data Cette commande fait partie du protocole d'extension Verse Grip et s'applique aux versions de la poignée qui implémentent le protocole de communication d'extension au niveau de la carte.
Configurer les données d'extension de la poignée
Longueur des données prise en charge :
- Trafic montant (client → appareil) : 20 octets maximum.
- Trafic descendant (appareil → client) : 12 octets maximum, via des messages de mise à jour d'état afin de
state.extension_dataRetour.
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 de la liste initiale des équipements
Lorsqu'une connexion WebSocket est établie, le service envoie un message contenant la liste complète des appareils. Le message initial se présente comme suit : 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
}
}
]
}
Exemple : charge de mise à jour d'état
À chaque fois que le service reçoit un message, il envoie un message de mise à jour de l'état contenant l'état de tous les appareils.
Si vous souhaitez connaître l'état d'un appareil, vous devez au préalable lui envoyer un message (par exemple, une commande de détection ou une valeur de force, même si celle-ci est nulle). Ceci est particulièrement important lorsque l'appareil est utilisé comme source d'entrée (par exemple, pour le suivi de position) et qu'aucune force n'est exercée.
Les messages de mise à jour de statut adoptent le format suivant : 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
}
}
]
}