Aller directement au corps du texte

08. Configurateur de sessions à distance

Il reconfigure une session déjà en cours d’exécution ailleurs (dans une autre application, une scène Unity, une démo de l’Haply Hub, etc.). Ce tutoriel n’ouvre pas de WebSocket et envoie uniquement des appels HTTP REST (GET, POST, DELETE) à l’appareil. Vous pouvez modifier la base, les préréglages de l’espace de travail et la transformation de montage pendant qu’une autre application continue le rendu haptique.

Cas d'utilisation​

  • Ajustement en temps réel d'une démo en cours d'exécution. Haply Lancez la démo Hub Orb, puis exécutez ce tutoriel dans un deuxième terminal pour modifier la permutation de base, changer les préréglages de l'espace de travail ou affiner la transformation de montage. Le système de coordonnées d'Orb bascule instantanément, sans que vous ayez à interrompre la démo.
  • Calibrage de l'espace de travail par utilisateur. Tout en continuant à exécuter la scène haptique sur la machine principale, un opérateur se trouvant sur le même réseau mount Permet de transférer le décalage, la rotation et la mise à l'échelle. L'espace de travail virtuel s'aligne alors sur le bureau de l'utilisateur.
  • Menu des options de sélection des appareils. En utilisant le même assistant HTTP, GET /devices(Tutoriel 00 Remarque : en envoyant cette requête, vous pouvez afficher la liste des appareils et créer un menu interactif. Vous pouvez sélectionner un appareil puis le reconfigurer, sans modifier le WebSocket de la session. Ce tutoriel /sessionset a posé la question suivante : *inverse/0est codé en dur, mais, /devices Le passage au sélecteur de base peut s'effectuer via une modification locale.
  • Reconfiguration basée sur des scripts. Automatise les étapes préliminaires (configuration de « basis », « preset » et « mount ») avant le début de l'enregistrement de la session. Il n'est pas nécessaire d'inclure ces paramètres pour tous les clients.

Conditions préalables​

Le tutoriel n° 08 permet de reconfigurer une session déjà en cours. Vous devez disposer d'une session haptique active. Il peut s'agir d'un autre tutoriel, d'une scène Unity ou de n'importe quelle démo de l'Haply Hub.

Comment lancer une session le plus rapidement possible ?

Haply OuvrezHub, lancez la démo Orb, puis sélectionnez directement cette session :

./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 » affiche une sphère dans l'espace de travail de l'appareil. Dans le tutoriel n° 08, lorsque vous modifiez successivement la base ou les préréglages, ou que vous affinez la transformation de montage, le repère de coordonnées d'Orb se déplace en temps réel à l'écran.

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 « basis », « preset » et « mount » de la session en cours, puis attend que vous appuyiez sur une touche. Chaque fois que vous appuyez sur une touche, exactement un appel REST est envoyé.

Configurer le nom du profil dans la simulation

Les sessions sans nom de profil ne peuvent être identifiées que par un identifiant numérique, et cet identifiant change à chaque exécution. L'application principale, dès le premier message, session.configure.profile.nameSi on le configure pour qu'il appelle, à chaque exécution, --session :my_profile:0Vous pouvez réutiliser des sélecteurs fixes de ce type. Session — Nom du profilVeuillez vous reporter à [...].

Raccourcis clavier​

cléMouvement
Bbase, permutation, cycle
PRotation des préréglages d'espace de travail
W / E / RSélection du mode d'édition de la monture — Position (mm) / Rotation (°) / Grossissement (%)
← / →Se déplacer d'un pas dans la direction −X / +X en mode actuel
↑ / ↓Se déplacer d'un cran dans la direction +Y / −Y en mode actuel
Page Up / Page DownDans le mode actuel, se déplacer d'un pas dans la direction +Z / −Z
= / -Réglage uniforme et simultané du grossissement sur les trois axes (toujours disponible)
Deletebase + préréglage + support DELETE Appliquer — Rétablir les paramètres par défaut de l'appareil
HAfficher l'aide
EscFin (Ctrl+C(fonctionne également)

Méthodes HTTP — GET, POST, DELETE​

Ce tutoriel n'utilise que trois méthodes HTTP. Tous les appels sont standard Enveloppe JSON(En cas de réussite, {"ok": true, "data": {...}}, en cas d'échec {"ok": false, "error": "..."}) et le code d'état correspond à l'une des trois valeurs suivantes : 200 Succès, 400 Format de requête incorrect, 404 Aucun élément ne correspond au sélecteur.

méthodeRôleMode d'emploi
GETLecture de l'état actuel — Consultation de la liste des sessions, recherche d'une session spécifique, consultation des paramètres actuels/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POSTRemplacement des valeurs de configuration — Le corps du message est au format JSON/<device_selector>/config/{basis,preset,mount}?session=...
DELETERétablir les paramètres par défaut de l'appareil/<device_selector>/config/{basis,preset,mount}?session=...

Aide HTTP​

Il s'agit d'un « wrapper » léger qui encapsule ces trois méthodes, ce qui permet ensuite de lire le code du tutoriel en se concentrant sur la logique métier :

Python prend en charge le protocole HTTP Keep-Alive requests.Session()utilise (ce qui permet de réduire le temps de latence par requête 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}"

Navigation dans les sessions — GET /sessions​

--session La branche dépend de l'option choisie :

  • --session SELECTORlorsque [...], est spécifié → GET /sessions/<SELECTOR> Un seul appel. 200puis utiliser les résultats obtenus, et, 404Dans ce cas, le programme affiche un message d'erreur et se termine.
  • En l'absence de drapeau → GET /sessions(Consultation de la liste) → Affichage des sessions comportant un nom de profil → Demande de saisie d'index → Création du sélecteur final (si possible :profile:0Utilisez d'abord celui-ci, et s'il n'y en a pas, #id(remplacé par).

SELECTORest Sélecteur — Sélecteur de sessionAccepte tous les formats définis dans : :profile:instance, #id, :-1, :0, nom de profil standard, ou co.haply.hub::*:0tel que Caractères génériques pour le nom de profil Il s'agit d'un modèle. Le tutoriel transmet cette chaîne telle quelle, et c'est le service qui se charge de son analyse.

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 de périphériques — *inverse/0​

Chaque appel de configuration s'applique au niveau de l'appareil. Le tutoriel utilise un sélecteur au format « * » (caractère générique) suivi d'un index:

/*inverse/0/config/<key>
  • *inverseTous les appareils appartenant à la gamme Inverse (inverse3, inverse3x, minverse) — Le tutoriel fonctionne tel quel, quel que soit le modèle utilisé.
  • 0Il s'agit d'un index commençant à 0 au sein de cette gamme de produits — le tutoriel n'utilise toujours que le premier Inverse.

Pour changer de périphérique cible, il suffit de modifier une seule chaîne de caractères — --deviceVeuillez indiquer (le chemin d'accès est constitué à partir de cette valeur) :

/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 La classe est indexée Auparavant S'applique — *inverse:left/0est « le 0e gaucher « inverse », et non « l'inverse de rang 0, qui est probablement celui de gauche ». Pour plus de détails, Sélecteur — Interprétation du filtre de classe avant l'indexationVeuillez vous reporter à [...].

Si aucun appareil n'est détecté, le tutoriel 08 se termine.

Le tutoriel commence dès le lancement GET …/config/basisJe demande que, lors de la session, --deviceS'il n'y a aucun appareil compatible (par exemple, dans les paramètres réservés aux droitiers, --device '*inverse:left/0'(si vous utilisez), le service 404 no device matches selector '<sel>'et le tutoriel Device '<sel>' not found in session '<sel>'.après avoir affiché [...], l'état à la fermeture 1et se termine par . Il n'entre pas dans la boucle du clavier, conformément à l'ancienne valeur par défaut.

La grammaire complète est la suivante : Sélecteur — Sélecteur d'appareilVeuillez vous reporter à [...]. Pour créer un menu de sélection des périphériques au lieu d'utiliser un codage en dur, GET /devices?session=<selector>(Tutoriel 00) pour afficher la liste des appareils, puis sélectionnez device_idVeuillez le relier au chemin d'accès défini.

Paramètres POST — base, préréglage, montage​

Ces trois clés ont le même format de requête, mais leur schéma de corps diffère. Toutes les requêtes POST renvoient une valeur de résultat datay mettre 200et, si aucun élément ne correspond au sélecteur de session ou d'appareil, 404renvoie.

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églages​

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": { ... }}} — Renvoie telle quelle la transformation valide après normalisation.

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)
mountWa presetsont mutuellement exclusives

Lorsque vous effectuez une requête POST sur l'un des côtés, les paramètres de l'autre côté sont effacés sur l'appareil. Le tutoriel ne suit pas explicitement ce processus : chaque requête POST est indépendante et les conflits sont résolus par le serveur. Pour connaître la même règle du point de vue de WebSocket, consultez le tutoriel 07.

Réinitialisation DELETE — trois appels​

resetappelle la fonction DELETE une fois pour chaque clé de configuration. Chaque appel dataen y plaçant la valeur qui est désormais la valeur par défaut 200renvoie.

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"))

Configuration de la rotation du support​

transform.rotationIl s'agit d'un quaternion unitaire dans ce format de transmission. Le tutoriel enregistre les rotations sous forme de triplets d'Euler intrinsèques Z-Y-X (tangage par rapport à l'axe X, lacet par rapport à l'axe Z, roulis par rapport à l'axe Y — tous exprimés en degrés). Et à chaque POST, le quaternion est reconstruit.

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 des quaternions

Quaternion de Hamilton unitaire, système de coordonnées de la main droite, priorité aux scalaires (w) C'est la méthode utilisée. Il s'agit du même protocole que celui utilisé pour le reste du service, et quaternionVeuillez vous reporter à [...]. L'ordre de synthèse est le suivant : Z-Y-X intrinsèque Méthode (q = q_z * q_y * q_x). Autrement dit, on applique d'abord le tangage (pitch) autour de l'axe X, puis le roulis (roll) autour de l'axe Y, et enfin le lacet (yaw) autour de l'axe Z.

Le tutoriel affiche, pour chaque barre d'état, le quaternion calculé ainsi que les angles d'Euler, ce qui permet de vérifier le résultat de la synthèse avant que l'appareil ne pivote. L'état d'Euler local est indépendant des valeurs déjà enregistrées dans la session, (0, 0, 0)On commence par. Le premier mount POSTremplace la valeur existante.

Modèle d'entrée (en bref)​

La structure de la connexion HTTP est essentielle, tandis que l'expérience utilisateur au clavier est secondaire. Nous utilisons les deux méthodes de simplification délibérées suivantes.

  • Python Dans la mise en œuvre, keyboard On utilise ce package. Il est multiplateforme et gère par défaut la saisie répétée lorsqu’on maintient une touche enfoncée. Les touches de direction, Page Up / Page Down, = / - Tant que la touche reste enfoncée, l'axe de montage se déplace, et B et P La clé alterne entre la base et le préréglage lors du front montant (rising edge).
  • C++ Dans la mise en œuvre, std::getline(std::cin, ...)et une syntaxe de jetons concise (x+20, sx-5, u+10) est utilisée. Bien que cela soit moins pratique pour les ajustements fins successifs, en ce qui concerne les API de console spécifiques à chaque plateforme, #ifdefLa greffe est possible même sans utiliser [...].

Source​

Distribué avec le programme d'installation du SDK

Le tutoriel 08 est également installé localement avec le SDK. Dans le répertoire d'installation du service, sous tutorials/08-haply-inverse-http-remote-config/ Vérifiez l'itinéraire.

Documents associés : Session — Contrôle à distance · Sélecteur · Configuration des appareils · Permutations de base · Montages et espaces de travail · Spécifications JSON · Tutoriel 00 — Liste des appareils · Tutoriel 07 — Permutations de base et montages (version WebSocket)