Aller au contenu principal

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.
Module

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_position Utilisé pour inverse3
  • probe_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.

Vous n'aurez peut-être pas besoin de la commande de détection

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,state et status。
  • habituel Le point sur la situation Le message contient les informations relatives à l'appareil state et status。

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 :

MappageUtilisationFréquence d'envoi
configureConfiguration unique et permanente : préréglages, valeurs de référence, mode d'installation, filtres, paramètres des modulesEnvoyer une seule fois, ou envoyer en cas de modification
commandsEntrée en vigueur à chaque tick, non persistante : force, position, coupleIl 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。

Remarque
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 exemple arm_front_centered va (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_id
  • config
  • state
  • status

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_id
  • state
  • status

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-ZY
  • YZX Exprimez votre Y L'axe correspond au côté droit de Haply , votre Z L'axe correspond à la partie avant de Haply , votre X L'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" }
}
}
}
Remarque
Depuis session.set_basis Migration

Ancienne 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 }
}
}
}
}
]
}
Verse Grip / Verse Grip sans fil

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 }
}
}
}
}
]
}
Le schéma des champs d'installation est asymétrique

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_data Retour.

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
}
}
]
}