Passer directement au contenu principal

08. Configurateur de sessions à distance

Nous allons reconfigurerune session déjà en cours d'exécution ailleurs, qu'il s'agisse d'une autre application, d'une scène Unity ou d'une démode l'Haply Hub. Pour ce faire, nous enverrons des appels REST HTTP à l'appareil. Dans ce tutoriel, nous n'ouvrirons pas de WebSocket. Nous utiliserons uniquement les méthodes GET, POST et DELETE pour modifier le plan de base, les préréglages de l'espace de travail et la transformation de montage, tandis que l'autre application continue d'afficher les effets haptiques.

Cas d'utilisation​

  • Ajuster la démo en cours d'exécution en temps réel. Lancez la démo Orb de l'Haply Hub, puis exécutez ce tutoriel dans un deuxième terminal. Lorsque vous basculez entre les modes de tri de base, modifiez les préréglages de l'espace de travail ou affinez la transformation de montage, le repère d'Orb change instantanément sans que la démo ne s'arrête.
  • Personnalisation de l'espace de travail pour chaque utilisateur. Tout en continuant à exécuter la scène Haptics sur la machine principale, un opérateur se trouvant sur le même réseau mount Permet d'envoyer les données de décalage, de rotation et de mise à l'échelle. Cela permet d'adapter l'espace de travail virtuel au bureau de l'utilisateur.
  • Menu d'options avec sélection de périphériques. Avec le même helper HTTP, GET /devices(Tutoriel 00 (voir) pour répertorier les périphériques et créer un menu interactif. Sélectionnez un périphérique dans le menu, puis reconfigurez-le. La session WebSocket n'est pas concernée. Ce tutoriel est /sessions en effectuant une requête sur *inverse/0 est codé en dur, mais/devices Pour le remplacer par le sélecteur de base, il suffit d'effectuer une modification locale.
  • Reconfiguration par script. Avant de lancer l'enregistrement d'une session, automatisez les étapes préparatoires (configuration de la base, des préréglages et du montage). Il n'est pas nécessaire d'intégrer cette configuration sur chaque client.

Conditions préalables​

Le tutoriel n° 08 permet de reconfigurerune session déjà en cours. Une session haptique active est requise. Il peut s'agir d'un autre tutoriel, d'une scène Unity ou d'une démo de l'Haply Hub.

Comment démarrer une session le plus rapidement possible ?

Haply Ouvrez Hub, lancez la démoOrb, puis sélectionnez-la directement :

./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 changez de système de référence ou de préréglage, ou que vous affinez la transformation de montage, le repère de coordonnées d'Orb évolue 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

Ce tutoriel affiche, au démarrage, les valeurs actuelles de « basis », « preset » et « mount » de la session, puis attend que l'utilisateur appuie sur une touche. À chaque pression sur une touche,un seul appel REST est envoyé.

Définir le nom d'un profil dans la simulation

Les sessions sans nom de profil ne peuvent être spécifiées qu'à l'aide d'un identifiant numérique. Cet identifiant change à chaque exécution. Dans le premier message de l'application principale, session.configure.profile.name Si l'on fait en sorte de l'appeler,--session :my_profile:0 Vous pouvez réutiliser un sélecteur stable de ce type à chaque exécution. Pour plus de détails, Session — Nom du profil Veuillez consulter [...].

Raccourcis clavier​

CléOpération
BChanger l'ordre des permutations de la base, l'une après l'autre
PPasser d'un préréglage d'espace de travail à l'autre
W / E / RSélectionner le mode d'édition du montage — Position (mm) / Rotation (°) / Échelle (%)
← / →Se déplacer dans la direction −X / +X dans le mode actuel
↑ / ↓Se déplacer dans la direction +Y / −Y dans le mode actuel
Page Up / Page DownSe déplacer dans la direction +Z / −Z dans le mode actuel
= / -Agrandissement/réduction uniforme et simultané des trois axes (±) (toujours disponible)
DeleteDELETE Réinitialiser les paramètres « basis », « preset » et « mount » pour rétablir les paramètres par défaut de l'appareil
HAfficher l'aide
EscFin (Ctrl+C (ou bien)

Méthodes HTTP — GET, POST, DELETE​

Dans ce tutoriel, nous n'utiliserons que trois des méthodes HTTP. Tous les appels seront effectués via le Enveloppe JSON renvoie (en cas de réussite, {"ok": true, "data": {...}}, en cas d'échec : {"ok": false, "error": "..."}). De plus, le code d'état peut être l'un des trois types suivants : 200 Succès,400 Format de la requête incorrect,404 Aucun élément ne correspond au sélecteur.

MéthodeRôleChemin d'accès à utiliser
GETLire l'état actuel — Liste des sessions, recherche d'une session spécifique, valeurs de configuration actuelles/sessions,/sessions/<selector>,/<device_selector>/config/{basis,preset,mount}?session=...
POSTRemplacer les valeurs de configuration — Le corps 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'une implémentation qui encapsule légèrement les trois méthodes ci-dessus, afin que le reste du tutoriel puisse être lu comme la logique principale du programme :

En Python, pour activer le protocole HTTP Keep-Alive, requests.Session() Nous utilisons (ce qui permet de réduire la 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}"

Détection de session — GET /sessions​

--session Le traitement varie selon que cette option est spécifiée ou non :

  • --session SELECTOR si vous spécifiez → GET /sessions/<SELECTOR> Exécutez cette opération une fois.200 Dans ce cas, on utilise ce résultat, et404 Dans ce cas, le programme se termine par une erreur.
  • Sans indicateur → GET /sessionsExécuter (Liste) → Afficher la session avec le nom du profil → Demander la saisie de l'index → Construire le sélecteur final (si disponible) :profile:0 en donnant la priorité à cela, et pour le reste, #id (en cas de défaillance).

SELECTOR は Sélecteur — Sélecteur de session Accepte tous les formats définis dans : :profile:instance,#id,:-1,:0, uniquement le nom du profil, ou co.haply.hub::*:0 comme Caractères génériques dans les noms de profil C'est un modèle. Dans ce tutoriel, la chaîne de caractères est transmise telle quelle, et l'analyse est effectuée côté service.

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'appareils — *inverse/0​

Tous les appels relatifs à la configuration sont définis au niveau de chaque appareil. Dans ce tutoriel, nous utiliserons le sélecteur «caractère générique de famille + index » :

/*inverse/0/config/<key>
  • *inverse Tout appareil appartenant à la gamme Inverse (inverse3,inverse3x,minverse). Ce tutoriel fonctionne tel quel, quel que soit le modèle utilisé.
  • 0 Il s'agit de l'index, qui commence à 0, au sein de cette famille. Dans ce tutoriel, nous ne manipulons que le premier Inverse.

Pour modifier la cible, il suffit de changer un seul caractère.--device Veuillez me le transmettre (le chemin d'accès sera construit à partir de là) :

/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 chargée de la création d'indexAuparavantS'applique — *inverse:left/0 est « le zéroièmeSystème de la main gauchesignifie « l'inverse de », et non « l'inverse du zéroième, c'est-à-dire probablement celui de gauche ». Pour plus de détails, Sélecteur — Les filtres de classe sont résolus avant la création de l'index Veuillez consulter [...].

Si aucun appareil compatible n'est détecté, le tutoriel 08 prend fin.

Le tutoriel s'affiche au démarrage GET …/config/basis sera publié. Lors de la session, --device S'il n'y a aucun périphérique correspondant (par exemple, dans le cas d'une configuration exclusivement pour la main droite) --device '*inverse:left/0' (si cette option est spécifiée), le service 404 no device matches selector '<sel>' et le tutoriel Device '<sel>' not found in session '<sel>'. Afficher le statut 1 et se termine ainsi. Le système n'entrera pas en boucle de clavier en conservant les anciennes valeurs par défaut.

Pour en savoir plus sur la grammaire complète,Sélecteur — Sélecteur d'appareils Veuillez vous reporter à [...]. Pour créer un menu de sélection des périphériques au lieu de coder les valeurs en dur,GET /devices?session=<selector>(Tutoriel 00) pour récupérer la liste, puis l'élément sélectionné device_id L'intégrer dans le chemin d'accès.

Configuration POST — base, préréglage, montage​

Ces trois clés ont le même format de requête, mais le schéma du corps diffère. Toutes les requêtes POST renvoient la valeur du résultat data qui comprend 200 renvoie. Si aucun élément ne correspond au sélecteur de session/appareil, 404 renvoie.

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": { ... }}} — 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)
mount et preset sont mutuellement exclusives

Lorsqu'une requête POST est envoyée, l'autre est effacée sur l'appareil. Ce tutoriel ne suit pas explicitement ce processus. Chaque requête POST est indépendante, et c'est le serveur qui résout les conflits. Pour une explication de cette même règle du point de vue de WebSocket, veuillez vous reporter aututoriel 07.

Réinitialisation via DELETE — 3 appels​

reset Il exécute la commande DELETE une fois pour chaque clé de configuration. Chaque appel remet à sa valeur par défaut la valeur qui a été data qui comprend 200 renvoie.

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

Composition de la rotation de la monture​

transform.rotation Lors des communications, il s'agit d'un quaternion unitaire. Dans ce tutoriel, les rotations sont stockées sous la forme d'un triplet d'angles d'Euler propres Z-Y-X (tangage autour de l'axe X, lacet autour de l'axe Z, roulis autour de l'axe Y — tous exprimés en degrés), et le quaternion est reconstitué à chaque appel 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,
}
Conditions générales de Quaternion

Quadriples de Hamilton unitaires, système de coordonnées à main droite, priorité au scalaire (w). Ces conditions générales sont les mêmes que celles applicables aux autres parties du service. Pour plus d'informations, quaternion Veuillez vous reporter à [...]. L'ordre de synthèse est le suivant :Z-Y-X intrinsèque(q = q_z * q_y * q_x) : on applique d'abord le tangage autour de l'axe X, puis le roulis autour de l'axe Y, et enfin le lacet autour de l'axe Z.

Dans le tutoriel, les quaternions dérivés pour chaque ligne d'état sont affichés avec un triplet d'angles d'Euler, ce qui vous permet de vérifier le résultat de la composition avant que l'appareil ne pivote. L'état d'Euler local est calculé indépendamment des valeurs déjà enregistrées dans la session. (0, 0, 0) Cela commence par. Le premier mount POST La valeur précédente sera alors écrasée.

Modèle d'entrée (présentation générale)​

La configuration HTTP est essentielle, tandis que l'expérience utilisateur du clavier est secondaire. J'ai délibérément simplifié deux aspects :

  • Python は keyboard Nous utilisons ce package. Il est multiplateforme et gère nativement la saisie répétée par maintien d'une touche enfoncée. Les touches fléchées,Page Up / Page Down,= / - Cela permet de déplacer l'axe de montage tant que l'on appuie dessus.B et P À chaque front montant, on bascule successivement entre le réglage de base et le préréglage.
  • C++ は std::getline(std::cin, ...) et une grammaire de jetons concise (x+20,sx-5,u+10) est utilisé. Bien qu’il soit peu pratique pour les ajustements fins en continu, les API de console propres à chaque plateforme #ifdef Comme cela permet d'éviter les ramifications, le code est portable.

Source​

Inclus dans 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/ Veuillez vérifier.

Voir aussi : Session — Contrôle à distance · Sélecteur · Configuration des périphériques · Remplacement de la base · Montages et espaces de travail · Conventions JSON · Tutoriel 00 — Liste des périphériques · Tutoriel 07 — Base et montages (version WebSocket)