Passer directement au contenu principal

Canal de simulation

Résumé​

Un canal de simulation est un canal WebSocket bidirectionnel à haut débit utilisé pour échangerdes informations sur l'état d'un périphériqueet pour envoyer des commandesrelatives à la session ou au périphérique.

URL par défaut : ws://localhost:10001

Le port peut être modifié dans les paramètres.

Dispositions fondamentales (important) :

  • Lorsqu'un client se connecte, le service envoie un message d'inventaire initial contenant la liste complète de tous les appareils.
  • Par la suite, le serveur envoie exactement un message « State Update » pour chaque message reçu du client.
  • Chaque « State Update » contient l'état (et le statut) detousles appareils.
Module

Cette page présentele protocole de base etlescommandesdu canal de simulation.

Les modules et fonctionnalités supplémentaires permettent d'étendre le système en enregistrant des commandes et des champs d'état spécifiques. Ceux-ci sont décrits séparément dans la section « Modules ».

Règles du protocole​

Une réponse par message client​

Pour chaque messagereçu d'un client, le service envoie un message « State Update » contenant l'étatde tous les appareils.

Cela signifie :

  • Si vous avez besoin d'un instantané à jour de l'état du périphérique, vous devez envoyerun message (commande de session, commande de sondage ou commande de périphérique).
  • Ce canal fonctionne comme une boucle « tick » pilotée par les messages des clients.

Interroger sans appliquer de tension (commande de sondage)​

Si vous souhaitez surveiller l'évolution de l'état sans appliquer de force ni modifier les paramètres de simulation, utilisez la commande « probe » :

  • inverse3 destiné à probe_position
  • Destiné à Verse Grip probe_orientation

La commande « probe » ne contient aucune donnée ; elle se contente de déclencher une requête d'informations sur le périphérique. De ce fait, les données de position et d'orientation seront à jour lors de la prochaine mise à jour d'état (State Update).

Dans certains cas, les commandes de sondage ne sont pas nécessaires.

Commandes de contrôle (set_cursor_force,set_cursor_position Dans les sessions de simulation où des commandes (etc.) ont déjà été envoyées, envoyer une commande de sondageCe 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 en marche,Réservé à la surveillanceDestiné aux sessions (par exemple : « Haply Hub »).

Paramètres et état​

  • Inventaire initial Le message indique que l'appareil config,state,status en fait partie.
  • Habituellement Le point sur la situation Le message indique que l'appareil state et status en fait partie.

Si vous devez à nouveau créer un instantané contenant les paramètres,session.force_render_full_state Veuillez utiliser ceci.

configure et commands La différence​

Les messages de l'appareil sont,configure et commands Prend en charge deux cartes distinctes. Celles-ci sont utilisées à des fins différentes :

CarteObjectifFréquence d'envoi
configureConfiguration permanente à définir une seule fois : préréglages, base, montage, filtres, paramètres des modulesÀ envoyer une seule fois ou lors d'une modification
commandsCommandes non persistantes à chaque impulsion : puissance, position, coupleUne transmission à chaque tick est nécessaire

commands Entrées contenues dans (set_cursor_force,set_cursor_position,set_angular_torques,set_angular_position) est,S'applique une fois à chaque tick et n'est pas enregistré.. Dès que l'envoi est interrompu, l'effet disparaît immédiatement et l'appareil revient à l'état de veille. Pour les éléments qui doivent être conservés d'un tick à l'autre sans qu'il soit nécessaire de les renvoyer,configure Veuillez utiliser ceci.

Remarque
set_transform C'est un cas particulier.

set_transform は commands Il sera placé en dessous, mais,Permanent. Le service conserve la dernière transformation envoyée jusqu’à ce qu’une nouvelle transformation soit transmise. Envoyer à chaque tickCe n'est pas nécessaire.. Cela dit, comme l'objectif est la navigation entre les caméras et les scènes, la diffusion en continu au taux de rafraîchissement (par exemple : envoyer une transformation par image lorsque l'utilisateur effectue un panoramique dans la scène) correspond au scénario d'utilisation prévu pour la navigation dans le cadre de mouvements continus.

Système de coordonnées​

Haply utilise par défaut le système de coordonnées « main droite » de Z-Up.

Deux entrées « configure » ont une incidence sur 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 « Haply » et le système de coordonnées de l'application (voir ci-dessous Base (voir)
  • inverse3[*].configure.preset: Sélectionnez une configuration d'usine nommée pour définir l'emplacement d'origine de l'espace de travail (par exemple : arm_front_centered は (0, 0, 0) (Placez-le au centre de l'espace de travail). Reportez-vous à la section « Configure » correspondant à chaque appareil.

Format des messages​

Cette section présente l'enveloppe de haut niveau des messages ainsi que les différents types de messages. Vous trouverez des exemples complets plus loin dans ce document.

Groupe de périphériques​

Les messages sont regroupés au niveau supérieur par type d'appareil (par exemple : inverse3,verse_grip,wireless_verse_grip). Les clés correspondant à chaque type d'appareil correspondent à un tableau d'objets propres à chaque appareil.

Inventaire initial (serveur → client)​

Il est envoyé une seule fois, immédiatement après l'établissement de la connexion WebSocket.

Chaque entrée comprend les éléments suivants :

  • device_id
  • config
  • state
  • status

Voir : Exemple : charge utile de l'inventaire initial

Mise à jour de l'état (serveur → client)​

Il est envoyé une fois par message client.

Chaque entrée comprend les éléments suivants :

  • device_id
  • state
  • status

Référence : Exemple : charge utile de « State Update »

Enveloppe des commandes de session (client → serveur)​

Une commande de session est une action qui s'applique à la connexion ou à la session en cours ;elle n'est pas spécifique à un périphérique.

{
"session": {
"<command_name>": {
"...": "..."
}
}
}

Enveloppe des commandes de périphérique (client → serveur)​

Les commandes destinées aux périphériques sont envoyées sous forme de tableau sous la clé correspondant au type de périphérique ; il est possible d'envoyer des commandes à un ou plusieurs périphériques dans un seul message. L'entrée correspondant à chaque périphérique est :configure Carte et commands Les deux cartes sont prises en charge.

{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}

Pour envoyer des commandes à plusieurs appareils du même type, il est possible d'inclure plusieurs entrées dans un même message. commands Il s'agit d'un dictionnaire pouvant contenir plusieurs commandes pour un même périphérique. Toutefois, il n'y en a qu'une seule par type de commande.

execute Champ​

Toutes les entrées « command » ou « configure » sont facultatives "execute" Valeur booléenne (par défaut : true) sont acceptées."execute": false Si vous définissez cette option, les entrées seront analysées mais ne seront pas appliquées. Il s'agit d'un sérialiseur basé sur la réflexion qui affiche systématiquement tous les champs (par exemple : Unity JsonUtility) peut s'avérer utile.

"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }

Référence des commandes​

Session​

Rendu forcé en état complet​

Demandez un instantané de l'état et de la configuration de tous les appareils.

{
"session": {
"force_render_full_state": {}
}
}

Structure de la session​

Les paramètres de configuration persistants au niveau de la session sont session.configure est transmis via. Il s'agit d'un niveau de périphérique configure Elle reprend 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 enregistrer les réglages spécifiques à chaque application sur l'appareil.

{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
Niveau de base (niveau de session)​

Configurez le mappage de base pour l'ensemble de la session. Le mappage de base définit la manière dont les coordonnées deHaply sont converties dans le système de coordonnées de l'application. Une fois la configuration effectuée, tous les états des périphériques sont renvoyés selon ce mappage de base, et toutes les valeurs transmises sont également interprétées selon ce mappage.

Les chaînes de permutations sont représentées en se référant au système de coordonnées de type droit (Haply ) avec l'axe Z orienté vers le haut.X,Y,Z dans cet ordre, si nécessaire + ou bien - on le place au début. Exemple :

  • XYZ,ZYX,+Y-Z+X,X-ZY
  • YZX est l'application Y qui se trouve à droite de Haply ,Z mais en amont de Haply ,X ce qui signifie qu'il se trouve au-dessus de Haply .

Exemple avec la main gauche et l'axe Z orienté vers le haut (Unreal,X-YZ) :

{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
Avertissement
session.set_basis Migration depuis

traditionnel session.set_basis et session.configure.basis Entre-temps, la règle relative au signe des axes a été modifiée. Il peut arriver que des permutations qui fonctionnaient avec l'ancienne commande génèrent une transformation inverse avec la nouvelle commande. Veuillez inverser le signe des axes si nécessaire (par exemple :X-ZY は XZ-Y (ce qui donne).

Commandes communes à tous les appareils​

Test par sondage (tous les appareils)​

La commande de sondage permet de demander les informations de position et d'orientation les plus récentes sans avoir à appliquer de force ni à modifier d'autres paramètres de simulation.

  • inverse3: probe_position
  • Verse Grip(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 (conversion de l'espace de l'appareil vers l'espace de l'application).

Autres commands Contrairement à une entrée,set_transform はPermanent. Le service conserve la dernière valeur envoyée jusqu’à ce que la valeur suivante soit transmise. Il n’est pas nécessaire d’envoyer des données à chaque tick, mais comme il s’agit d’une utilisation destinée à la caméra ou à la navigation dans une scène, on suppose généralement une diffusion en continu à la fréquence des ticks pour les mouvements continus (par exemple : envoyer une transformation par image lorsque l’utilisateur effectue un panoramique 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 }
}
}
}
}
]
}
Verse Grip / Verse Grip sans fil

Sur les appareils Verse Grip et Wireless Verse Grip, parmi les conversions, rotation Seuls les ingrédients ont un effet. Le grip ne rend compte que de la posture, c'est pourquoi,position や scale Il n'y a aucun élément susceptible d'être redimensionné ou déplacé parallèlement.position et scale Ce champ n'est accepté que dans le but de préserver la cohérence du schéma avecInverse3 (et est renvoyé tel quel dans les instantanés) ; il prend la valeur identique (position = {0,0,0},scale = {1,1,1}) : c'est la méthode standard.

Inverse3 Configuration de l'appareil​

Les entrées de configuration sont configure Envoyez-les via la carte. Ces données sont persistantes. Envoyez-les une seule fois ou lorsque leur valeur change.

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。

Niveau de base (au niveau du périphérique)​

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}

Monture​

Configurez la conversion de montage des périphériques.mount à l'intérieur de transform Méfiez-vous des rappeurs.

{
"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 }
}
}
}
}
]
}
Asymétrie du schéma « mount »

CommandeDu côté client (du client vers le serveur), la conversion est encapsulée dans un wrapper :"mount": { "transform": { ... } }。 InstantanéDu côté (du serveur vers le client), la structure est plate :"mount": { "position": {...}, "rotation": {...}, "scale": {...} }。 Il s'agit d'une conception délibérée. Les commandes utilisent un wrapper unifié, tandis que les instantanés sérialisent directement la transformation. Veillez à ne pas copier le format de l'instantané dans la charge utile de la commande.

Atténuation​

Permet de contrôler l'atténuation uniforme et/ou l'atténuation directionnelle. Il est nécessaire de spécifier au moins un champ.

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}

Vous pouvez configurer l'atténuation par direction ou les deux simultanément :

"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }

Force Gate​

Réglez le gain d'atténuation du Force Gate (0,0 à 1,0).

{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}

Inverse3 Commande​

inverse3 Pour envoyer une commande à un périphérique,inverse3 Sous la clé, la correspondance device_id inclut les entrées qui possèdent cette valeur. La commande est valable à chaque tick et doit être renvoyée à chaque tick pour rester active.

Pour envoyer une commande à un seul appareil :

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

Envoyer une commande à plusieurs appareils en 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
}
}
}
}
]
}

Réglage 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 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 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 À ce sujet, identique àInverse3 configure Prend en charge les touches.

Extension Verse Grip​

set_extension_data Cette commande fait partie du protocole d'extension de Verse Grip, utilisé avec les versions de la poignée qui implémentent le protocole de communication d'extension de la carte.

Configuration des données d'extension de la poignée​

Longueur de données prise en charge :

  • En amont (client → appareil) : 20 octets maximum.
  • En aval (périphérique → client) : 12 octets maximum.state.extension_data Il est renvoyé sous la forme d'un 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 l'inventaire initial​

Une fois la connexion WebSocket établie, le service envoie un message contenant la liste complète des appareils. Le message initial est le suivant : JSON Il se présente sous la forme suivante :

{
"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​

Pour chaque message reçu, le service envoie un message de mise à jour d'état contenant l'état de tous les appareils.

Si vous souhaitez connaître l'état de la machine, vous devez envoyer un message au préalable (par exemple : même si la valeur est nulle, une commande de sondage ou la valeur de la force, etc.). Cela est particulièrement important lorsque vous utilisez l'appareil comme source d'entrée (par exemple, pour le suivi de position) sans appliquer de force.

Les messages de mise à jour d'état sont les suivants : JSON Il se présente sous la forme suivante :

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