Aller au contenu principal

08. Configurateur de sessions à distance

Pour reconfigurerune session déjà en cours d'exécution ailleurs(par exemple dans une autre application, une scène Unity ou une démonstration sur l'Haply Hub ), il suffit d'envoyer une requête HTTP REST à l'appareil concerné. Ce tutoriel n'ouvre aucun WebSocket : il suffit d'utiliser des requêtes GET, POST et DELETE pour modifier la base, les préréglages de l'espace de travail ou la transformation de montage, tout en continuant à afficher le retour tactile dans l'autre application.

Cas d'utilisation​

  • Ajustez en temps réel la démo en cours d'exécution. Lancez la démo « Haply Hub Orb », puis exécutez ce tutoriel dans un deuxième terminal pour modifier le décalage de l'axe « basis », changer les préréglages de l'espace de travail ou affiner la transformation « mount » : le système de coordonnées d'Orb s'adaptera instantanément, sans que la démo ne s'interrompe.
  • Effectuer l'étalonnage de l'espace de travail par utilisateur. Maintenir les scénarios tactiles en exécution sur le serveur et permettre aux opérateurs du même réseau d'envoyer des données mount le décalage / la rotation / le zoom, afin d'aligner l'espace de travail virtuel sur le bureau de l'utilisateur.
  • Menu d'options avec sélection d'équipements. Les mêmes fonctions auxiliaires HTTP peuvent être appelées GET /devices(Voir Tutoriel 00) pour répertorier les appareils et créer un menu interactif — permettant de reconfigurer un appareil une fois celui-ci sélectionné — sans avoir à intervenir sur le WebSocket de la session. Ce tutoriel interroge /sessions et a été codé en dur *inverse/0, mais en optant pour une solution proposée par /devices Le sélecteur de pilote n'est qu'une modification locale.
  • Reconfiguration automatisée. Exécution automatique des étapes de pré-vérification (configuration de base + préréglages + montage) avant le début de l'enregistrement de la session, ce qui évite d'avoir à intégrer ces paramètres dans chaque client.

Conditions préalables​

Tutoriel n° 08 : reconfigurationd'une session déjà en cours. Vous devez disposer d'une session tactile active (qu'il s'agisse d'un autre tutoriel, d'une scène Unity ou d'une démo de l'Haply Hub).

La méthode la plus rapide pour démarrer une session

Ouvrez Haply Hub, lancezla démo Orb, puis utilisez-la directement comme cible :

./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

La scène « Orb » permet de rendre une sphère dans l'espace de travail de l'appareil. Lorsque vous utilisez le tutoriel 08 pour basculer entre les transformations « basis », les préréglages ou les réglages fins de « mount », le système de coordonnées d'« Orb » se décale visuellement en temps réel.

Mode d'emploi​

# Pick a session interactively (lists every session the service knows)
./08-haply-inverse-http-remote-config
python 08-haply-inverse-http-remote-config.py

# Target the Haply Hub Orb demo directly
./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

# Target one directly by selector
./08-haply-inverse-http-remote-config --session :my_profile:0
python 08-haply-inverse-http-remote-config.py --session "#42"

# Or by a wildcard profile pattern (first match) — handy when the exact profile is unknown
./08-haply-inverse-http-remote-config --session "co.haply.hub::*:0"

# Pick a specific device on the session (default is *inverse/0)
./08-haply-inverse-http-remote-config --device 'inverse3/#A12' # by exact id
./08-haply-inverse-http-remote-config --device '*inverse:left/0' # first left-handed inverse
./08-haply-inverse-http-remote-config --device '*inverse:right/0' # first right-handed inverse
./08-haply-inverse-http-remote-config --device '*verse_grip/0' # first grip-family device

Au démarrage, le tutoriel affiche les paramètres actuels de la session (basis / preset / mount), puis attend une entrée au clavier — chaque pression sur une touche déclenche exactementun appel REST.

Définir le nom du profil dans la simulation

Les sessions pour lesquelles aucun nom de profil n'a été défini ne peuvent être identifiées qu'à l'aide d'un identifiant numérique — qui change à chaque exécution. Veuillez vous assurer que l'application principale appelle la fonction suivante dans son premier message : session.configure.profile.name, ce qui permet ensuite de réutiliser un sélecteur stable à chaque exécution, par exemple --session :my_profile:0. Voir Session — Nom du profil。

Configuration des touches​

toucheOpération
BCommutation cyclique – Remplacement de base
PBasculer entre les préréglages d'espace de travail
W / E / RSélectionner le mode d'édition du montage — Position (mm) / Rotation (°) / Échelle (%)
← / →Avancer par pas de −X / +X dans le mode actuel
↑ / ↓Avancer de +Y / −Y pas dans le mode actuel
Page Up / Page DownAvancer pas à pas dans le mode actuel le long des axes +Z / −Z
= / -Mise à l'échelle simultanée et uniforme des trois axes ± (toujours disponible)
DeleteDELETE base + préréglage + support — Restaurer les valeurs par défaut de l'appareil
HAfficher l'aide
EscQuitter (Ctrl+C (C'est également possible)

Verbes HTTP — GET, POST, DELETE​

Ce tutoriel utilise les trois verbes HTTP, et uniquement ceux-là. Chaque appel renvoie une réponse standard Format d'encapsulation des réponses JSON(en cas de réussite : {"ok": true, "data": {...}}, en cas d'échec : {"ok": false, "error": "..."}), et renvoie l'un des trois codes d'état suivants :200 Succès,400 Format de la requête incorrect,404 Aucun résultat n'a été trouvé pour ce sélecteur.

VerbeUtilisationChemin d'accès utilisé
GETLecture de l'état actuel — Liste des sessions, requête sur une session spécifique, valeurs de configuration actuelles/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POSTRemplacer les valeurs de configuration — Corps de la requête au format JSON/<device_selector>/config/{basis,preset,mount}?session=...
DELETERétablir les valeurs par défaut de l'appareil/<device_selector>/config/{basis,preset,mount}?session=...

Fonctions auxiliaires HTTP​

Il s'agit d'un encapsulement allégé des trois verbes mentionnés ci-dessus, qui permet au reste de ce tutoriel de se lire davantage comme de la logique métier :

Utilisation de Python requests.Session() Mise en œuvre de la réutilisation des connexions HTTP persistantes (permettant de réduire le délai d'une requête unique d'environ 50 ms à environ 5 ms) :

http = requests.Session()

def api_get(path):
r = http.get(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def api_post(path, body):
r = http.post(f"{BASE_URL}{path}", json=body, timeout=3)
return r.json() if r.status_code == 200 else None

def api_delete(path):
r = http.delete(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def session_url(endpoint):
return f"{endpoint}?session={session_selector}"

Détection de session — GET /sessions​

Conformément à --session Les paramètres déterminent le chemin d'exécution :

  • a été fourni --session SELECTOR → Envoyer une fois GET /sessions/<SELECTOR>。200 → Utilisation directe ;404 → Quitter en raison d'une erreur.
  • Aucun paramètre fourni → GET /sessions(Liste) → Afficher les sessions avec leur nom de profil → Demander la saisie d'un index → Construire le sélecteur final (à privilégier lorsque cela est possible) :profile:0, sinon revenir à #id).

SELECTOR Soutien Sélecteur — Sélecteur de session Toutes les formes définies dans ::profile:instance,#id,:-1,:0, un nom de profil « pur », ou sous la forme co.haply.hub::*:0 le profil : caractères génériques de nom Modèle. Ce tutoriel transmettra cette chaîne telle quelle ; c'est au serveur qu'il revient de l'analyser.

def discover_session(session_arg):
global session_selector

if session_arg:
# Direct lookup (e.g. ":my_profile:0", "#42", ":-1")
if api_get(f"/sessions/{session_arg}") is None:
return False
session_selector = session_arg
return True

# Otherwise: list and pick
data = api_get("/sessions")
sessions = data.get("data", {}).get("sessions", [])
for i, s in enumerate(sessions):
name = s.get("config", {}).get("profile", {}).get("name", "default")
print(f" [{i}] session #{s['session_id']} profile={name}")

picked = sessions[int(input("Pick session index: "))]
name = picked.get("config", {}).get("profile", {}).get("name", "")
# Prefer the profile selector — it survives restarts; id doesn't
session_selector = (f":{name}:0" if name and name != "default"
else f"#{picked['session_id']}")
return True

Sélecteur d'équipements — *inverse/0​

Chaque appel de configuration est limité à un périphérique spécifique. Ce tutoriel utilise des sélecteurs sous la formed'un caractère générique de série suivi d'un index:

/*inverse/0/config/<key>
  • *inverse Compatible avec n'importe quel appareil de la gamme Inverse (inverse3,inverse3x,minverse) — Quel que soit le modèle utilisé, ce tutoriel fonctionne tel quel, sans aucune modification.
  • 0 Il s'agit de l'index de cette série, qui commence à 0 — ce tutoriel ne porte que sur le premier appareil Inverse.

Pour changer de périphérique cible, il suffit de modifier une chaîne de caractères — à passer en paramètre --device Il suffit d'indiquer le paramètre (le chemin d'accès est généré à partir de ce paramètre) :

/verse_grip/0/config/basis?session=... # --device verse_grip/0
/*verse_grip/0/config/basis?session=... # --device '*verse_grip/0' — first grip, any kind
/inverse3/A14/config/mount?session=... # --device 'inverse3/#A14'
/*inverse:left/0/config/basis?session=... # --device '*inverse:left/0' — first left-handed inverse
/*inverse:right/0/config/basis?session=... # --device '*inverse:right/0' — first right-handed inverse

:left / :right Classes dans l'indexAuparavantEntrée en vigueur — « — »*inverse:left/0» signifie « le 0emain gauche « inverse », et non « le 0e inverse, qui devrait être celui de la main gauche ». Voir Sélecteurs — Les filtres de classe sont analysés avant l'indexation。

Tutoriel 08 : Sortie en cas d'absence de l'appareil cible

Le tutoriel émet un signal au démarrage GET …/config/basis Demande. Si la session ne contient pas d'élément correspondant à --device Appareils compatibles (par exemple, en cas d'utilisation dans un environnement où seuls des appareils pour droitiers sont disponibles) --device '*inverse:left/0'), le service renvoie 404 no device matches selector '<sel>'; le tutoriel sera imprimé Device '<sel>' not found in session '<sel>'. et avec l'état 1 Quitter. Le cycle du clavier ne démarre pas avec les valeurs par défaut obsolètes.

Pour consulter la grammaire complète, voir Sélecteur — Sélecteur d'appareils。Pour configurer le menu de sélection des appareils plutôt que de l'intégrer en dur, utilisez GET /devices?session=<selector>(Tutoriel 00) répertorier les périphériques, puis sélectionner le device_id Indiquez le chemin d'accès au fichier de configuration.

Configuration POST — basis, preset, mount​

Les trois requêtes ont la même structure, mais le corps de la requête diffère. Chaque requête POST renvoie 200, et dans data contient la valeur du résultat ; si le sélecteur de session ou d'appareil ne correspond à aucun objet, il renvoie 404。

Base​

POST /*inverse/0/config/basis?session=:my_profile:0
Content-Type: application/json

{"permutation": "XZY"}

Réponse :{"ok": true, "data": {"permutation": "XZY"}}

def post_basis():
perm, _ = BASIS_OPTIONS[basis_index]
api_post(session_url("/inverse3/0/config/basis"), {"permutation": perm})

Préréglage​

POST /*inverse/0/config/preset?session=:my_profile:0
Content-Type: application/json

{"preset": "arm_front_centered"}

Réponse :{"ok": true, "data": {"preset": "arm_front_centered"}}

def post_preset():
preset = PRESET_OPTIONS[preset_index]
api_post(session_url("/inverse3/0/config/preset"), {"preset": preset})

Mont​

POST /*inverse/0/config/mount?session=:my_profile:0
Content-Type: application/json

{
"transform": {
"position": {"x": 0.02, "y": 0.0, "z": 0.0},
"rotation": {"w": 0.966, "x": 0.0, "y": 0.259, "z": 0.0},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0}
}
}

Réponse :{"ok": true, "data": {"transform": { ... }}} — Transformation qui prend effet après la normalisation de l'affichage.

def post_mount():
body = {
"transform": {
"position": {"x": mount_pos[0], "y": mount_pos[1], "z": mount_pos[2]},
"rotation": quat_from_euler_deg(*mount_rot),
"scale": {"x": mount_scale[0], "y": mount_scale[1], "z": mount_scale[2]},
}
}
api_post(session_url("/inverse3/0/config/mount"), body)
mount et preset Exclusion mutuelle

L'exécution d'une de ces requêtes POST entraîne la suppression de l'autre sur l'appareil. Ce tutoriel n'aborde pas explicitement ce point : chaque requête POST est indépendante, et c'est au serveur qu'il revient de résoudre les conflits. Pour une explication de cette même règle du point de vue du côté WebSocket, consultez le tutoriel 07.

DELETE Réinitialisation — trois appels​

reset Une requête DELETE sera envoyée pour chaque élément de configuration. À chaque fois, la réponse sera 200, et dans data Il contient les valeurs par défaut rétablies.

def reset_all():
api_delete(session_url("/inverse3/0/config/basis"))
api_delete(session_url("/inverse3/0/config/preset"))
api_delete(session_url("/inverse3/0/config/mount"))

Montage combiné et rotation​

Lors du transfert,transform.rotation Il s'agit d'un quaternion unitaire. Ce tutoriel stocke les rotations sous la forme de triplets d'angles de rotation d'Euler Z-Y-X (pitch autour de l'axe X, yaw autour de l'axe Z, roll autour de l'axe Y, tous exprimés en degrés), puis les recombine en quaternion à chaque appel de la fonction POST.

def quat_from_euler_deg(pitch_x, yaw_z, roll_y):
"""Hamilton quaternion for q = q_z * q_y * q_x (apply X, then Y, then Z)."""
hx, hy, hz = (math.radians(a) * 0.5 for a in (pitch_x, roll_y, yaw_z))
cx, sx = math.cos(hx), math.sin(hx)
cy, sy = math.cos(hy), math.sin(hy)
cz, sz = math.cos(hz), math.sin(hz)
return {
"w": cz*cy*cx + sz*sy*sx,
"x": cz*cy*sx - sz*sy*cx,
"y": cz*sy*cx + sz*cy*sx,
"z": sz*cy*cx - cz*sy*sx,
}
Convention relative aux quaternions

Quaternions de Hamilton, système de coordonnées de la main droite, scalaire en premier (w) — Conformément aux dispositions applicables aux autres parties du service, voir quaternion. L'ordre d'assemblage est le suivant : Z-Y-X : rotation interne (intrinsèque)(q = q_z * q_y * q_x) : on applique d'abord une inclinaison (pitch) autour de l'axe X, puis un roulis (roll) autour de l'axe Y, et enfin un lacet (yaw) autour de l'axe Z.

Le tutoriel affiche simultanément, pour chaque ligne d'état, le quaternion et le triplet d'angles d'Euler obtenus par déduction, afin que vous puissiez vérifier les résultats de la combinaison avant que l'appareil ne tourne. Quelles que soient les valeurs déjà présentes dans la session, l'état local des angles d'Euler commence à partir de (0, 0, 0) Début — Première fois mount POST Cela remplacera la valeur d'origine.

Modèle d'entrée (résumé)​

C'est la logique de connexion HTTP qui est essentielle ; l'interaction via le clavier n'est qu'accessoire. On note ici deux simplifications délibérées :

  • Python Utilisation keyboard Enveloppe — Multiplateforme, gestion native de la répétition lors d'un appui long sur une touche. Touches directionnelles,Page Up / Page Down ainsi que = / - l'axe de suspension se déplacera progressivement tant que le bouton restera enfoncé ;B et P Commutation cyclique entre la base et le préréglage lors du front montant.
  • C++ Utilisation std::getline(std::cin, ...) et une syntaxe de jetons compacte (x+20,sx-5,u+10) — Ce n'est pas très pratique pour les réglages fins en continu, mais il n'est pas nécessaire d'utiliser #ifdef Distinguer les API des consoles de chaque plateforme, ce qui facilite la portabilité.

Code source​

Fourni avec le programme d'installation du SDK

Le tutoriel 08 est également installé localement avec le SDK — il se trouve dans le répertoire d'installation du service, sous tutorials/08-haply-inverse-http-remote-config/ Trouvé dans.

Contenu associé : Session — Contrôle à distance · Sélecteur · Configuration des périphériques · Remplacement de la base · Montage et espace de travail · Conventions JSON · Tutoriel 00 — Liste des périphériques · Tutoriel 07 — Base et montage (version WebSocket)