Aller au contenu principal

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 :

BlocContenuModifications
configInformations sur le micrologiciel, preset, basis, mount, filtersRarement — uniquement lors d'un changement explicite de configuration
statePosition, vitesse, force, orientation, transformationsÀ chaque cycle (fréquence élevée)
statusPrêt, calibré, alimentation, in_useOccasionnellement (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 :

DictionnaireRôlePersistance
configureRéglages ponctuels : preset, basis, mount, filters, configuration des modulesMémorisés jusqu'à modification
commandsContrôle à chaque cycle : force, position, couplesAppliqué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.

remarque
set_transform fait exception

set_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​

N'envoyez pas de sondes si vous transmettez déjà des commandes

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.

Les clés inconnues sont ignorées silencieusement

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.