Protocole WebSocket
Le canal de simulation est une connexion WebSocket sur le port 10001. Il transporte 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 scrutation (polling), ni abonnement, ni envoi spontané hors bande. La cadence est entièrement déterminée par votre fréquence d'envoi.
Étape 1 — Inventaire initial (premier message du service)
À 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, preset, basis, mount, filters | Rarement — uniquement lors d'un changement explicite de configuration |
state | Position, vitesse, force, orientation, transformations | À chaque cycle (fréquence élevée) |
status | Prêt, calibré, alimentation, in_use | Occasionnellement (fréquence faible) |
É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 un changement de 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 d'état. La boucle se poursuit à votre fréquence d'envoi.
configure ou commands
Chaque entrée d'appareil de votre message sortant peut contenir deux dictionnaires :
| Dictionnaire | Rôle | Persistance |
|---|---|---|
configure | Réglages ponctuels : preset, basis, mount, filters, configuration des modules | Mémorisés jusqu'à modification |
commands | Contrôle à chaque cycle : force, position, couples | Appliqués une fois, puis oubliés |
Voir Configuration de session pour 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'à l'envoi d'une nouvelle. 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 caméra, mais souhaitez que la dernière position reste en place entre deux pressions.
L'indicateur execute
Toute entrée configure ou commands peut inclure "execute": false pour que le
service analyse et valide 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 basés sur la réflexion (par exemple, JsonUtility de Unity)
qui émettent toujours tous les champs : définissez les entrées inutilisées à execute: false pour qu'elles
n'écrasent pas la configuration réelle. 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) réservé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 aussi de sondes — l'état de l'appareil figure déjà dans chaque réponse.
Combiner les deux gaspille la bande passante et peut déclencher des événements session-probe-dropped.
Si une commande semble sans effet, le nom du champ est probablement erroné. 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 de champs dans la référence de l'API. Ce comportement est appelé à changer dans une prochaine version.