Protocole WebSocket
Le canal de simulation est une connexion WebSocket sur le port 10001. Il achemine la boucle de commande et d'état en temps réel entre votre application et le service.
Règle fondamentale : une réponse par message
Le service envoie exactement une mise à jour d'état pour chaque message qu'il reçoit de votre client :
Il n'y a ni sondage (polling), ni abonnement, ni envoi spontané hors bande. La cadence est entièrement déterminée par votre fréquence d'envoi.
Étape 1 — État des lieux initial (premier message du service)
Lors de la connexion, le service envoie un instantané complet contenant config, state
et status pour chaque appareil détecté :
{
"session_id": 7,
"inverse3": [
{
"device_id": "049D",
"config": {
"type": "inverse3",
"port": "COM12",
"device_info": { "major_version": 7, "minor_version": 4, "id": "049D", "device_type": 4, "uuid": "…" },
"extended_device_id": "…",
"extended_firmware_version": "…",
"gravity_compensation": { "enabled": true, "scaling_factor": 1.0 },
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": { "enabled": true },
"home_return": { "enabled": false },
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 } },
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": 0.0 }
}
},
"state": {
"cursor_position": { "x": 0.0, "y": -0.12, "z": 0.15 },
"cursor_velocity": { "x": 0.0, "y": 0.0, "z": 0.0 },
"angular_position": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"angular_velocity": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"body_orientation": { "w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0 },
"current_cursor_force": { "x": 0.0, "y": 0.0, "z": 0.0 },
"current_cursor_position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"current_angular_torques": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"current_angular_position": { "a0": 0.0, "a1": 0.0, "a2": 0.0 },
"control_domain": "cartesian",
"control_mode": "idle",
"transform": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 } },
"transform_velocity": { "position": { "x": 0, "y": 0, "z": 0 }, "rotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, "scale": { "x": 0, "y": 0, "z": 0 } }
},
"status": {
"calibrated": true,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [],
"wireless_verse_grip": []
}
Les trois blocs ont des fréquences de mise à jour différentes :
| Bloc | Contenu | Modifications |
|---|---|---|
config | Informations sur le micrologiciel, les préréglages, la base, la monture et les filtres | Rarement — uniquement en cas de modification explicite de la configuration |
state | Position, vitesse, force, orientation, transformations | À chaque cycle (haute fréquence) |
status | Prêt, calibré, sous tension, en cours d'utilisation | De temps en temps (rarement) |
Étape 2 — Envoyez votre première commande
Analysez l'inventaire initial pour trouver l'identifiant de votre appareil, puis renvoyez un message contenant votre profil de session, toute configuration initiale et/ou une commande de contrôle :
{
"session": {
"configure": {
"profile": { "name": "co.haply.inverse.tutorials:hello-floor" }
}
},
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
},
"commands": {
"set_cursor_force": { "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
}
}
]
}
Étape 3 — Mises à jour d'état suivantes
Après le premier échange, le service n'envoie que state + status (sans
config), sauf si une modification de la configuration déclenche l'envoi d'un instantané complet :
{
"session_id": 7,
"inverse3": [
{
"device_id": "049D",
"state": { "cursor_position": { … }, "cursor_velocity": { … }, … },
"status": { "ready": true, "calibrated": true, … }
}
]
}
Étape 4 — Répétez
Envoyez une commande → recevez une mise à jour sur son statut. Le cycle se répète à la fréquence à laquelle vous envoyez vos commandes.
configure ou commands
Chaque entrée d'appareil de votre message sortant peut contenir deux dictionnaires :
| Dictionnaire | Rôle | Persévérance |
|---|---|---|
configure | Réglages ponctuels : préréglage, base, montage, filtres, configuration des modules | Enregistrés jusqu'à modification |
commands | Contrôle à chaque cycle : force, position, couples | Appliqués une fois, puis oubliés |
Voir Configuration de session pour consulter la
liste complète des clés de configure, et Commandes de contrôle pour
les entrées de commands.
set_transform fait exceptionset_transform se trouve sous commands, mais elle est persistante — Le service
conserve la dernière valeur jusqu'à ce qu'une nouvelle soit envoyée. Cela s'explique par le fait que sa fonction est
la navigation dans la scène : vous transmettez les transformations en continu pendant les déplacements de la caméra, mais vous souhaitez que la dernière position reste inchangée entre deux pressions.
L'indicateur execute
Toute entrée configure ou commands peut inclure "execute": false pour que le
service analyser et valider la charge utile sans l'appliquer.
{
"inverse3": [{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front", "execute": true },
"damping": { "scalar": 0.0, "execute": false }
}
}]
}
Cela est utile pour les sérialiseurs à réflexion (par exemple, JsonUtility (de Unity)
qui émettent toujours tous les champs : définissez les entrées inutilisées comme execute: false afin qu'elles
ne remplacent pas la configuration actuelle. La valeur par défaut est true — omettre l'indicateur signifie
« appliquer normalement ».
Avertissements
probe_position et probe_orientation sont des messages de maintien de connexion (keepalive) destinés à la surveillance, pour les
sessions qui n'envoient pas de commandes de contrôle (par exemple, Haply Hub). Si votre session envoie déjà set_cursor_force, set_cursor_position, etc., n'envoyez pas non plus de sondes — L'état de l'appareil est déjà indiqué dans chaque réponse.
Combiner les deux gaspille de la bande passante et peut déclencher des événements session-probe-dropped.
Si une commande semble ne produire aucun effet, le nom du champ est probablement incorrect. Le service ignore actuellement les clés JSON non reconnues sans générer d'erreur. Consultez les journaux du service et vérifiez les noms des champs dans la référence de l'API. Ce comportement est appelé à changer dans une prochaine version.