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
mountPermet 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,/devicesLe 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.
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é.
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
- Python
- C++
| clé | Mouvement |
|---|---|
B | base, permutation, cycle |
P | Rotation des préréglages d'espace de travail |
W / E / R | Sé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 Down | Dans 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) |
Delete | base + préréglage + support DELETE Appliquer — Rétablir les paramètres par défaut de l'appareil |
H | Afficher l'aide |
Esc | Fin (Ctrl+C(fonctionne également) |
Saisie ligne par ligne — Saisissez la commande, puis appuyez sur ENTRÉE.
| commande | Mouvement |
|---|---|
b | base, permutation, cycle |
p | Rotation des préréglages d'espace de travail |
w / e / r | Sélection du mode d'édition de la monture — Position (mm) / Rotation (°) / Grossissement (%) |
x+[N] … z-[N] | Définir l'axe actuel comme unité par défaut en mode actif NSe déplacer d'autant (x+Il suffit de saisir pour obtenir la valeur par défaut 5) |
sx+[N] … sz-[N] | Commande de réduction non uniforme sur un axe (en pourcentage), toujours disponible |
u+[N] / u-[N] | Réglage simultané et uniforme de l'agrandissement de ± N % sur les trois axes |
reset | base + préréglage + support DELETE Application |
h | Afficher l'aide |
Appuyez sur Ctrl+C (ou Ctrl+D / EOF) pour quitter.
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éthode | Rôle | Mode d'emploi |
|---|---|---|
GET | Lecture 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=... |
POST | Remplacement des valeurs de configuration — Le corps du message est au format JSON | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | Ré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
- C++ (nlohmann)
- C++ (Glaze)
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}"
libhvest requests::get / requests::post / requests::Deletepropose (deleteComme il s'agit d'un mot-clé C++, il doit être écrit en majuscules D(utilise). Dans la méthode POST, Content-Type: application/jsonPour le configurer, vous devez configurer la requête vous-même :
static std::string session_url(const std::string &endpoint) {
return BASE_URL + endpoint + "?session=" + session_selector;
}
static json http_get(const std::string &url) {
auto resp = requests::get(url.c_str());
if (!resp || resp->status_code != 200) return {};
try { return json::parse(resp->body); } catch (...) { return {}; }
}
static bool http_post_json(const std::string &url, const json &body) {
auto req = std::make_shared<HttpRequest>();
req->method = HTTP_POST;
req->url = url;
req->content_type = APPLICATION_JSON;
req->body = body.dump();
auto resp = requests::request(req);
return resp && resp->status_code == 200;
}
static bool http_delete(const std::string &url) {
auto resp = requests::Delete(url.c_str());
return resp && resp->status_code == 200;
}
Le corps de la réponse est toujours {"ok", "data": T} Il dispose d'un « enveloppe ». Un seul modèle englobe toutes les requêtes GET de ce type, et le même HttpRequest le motif glz::write_jsonUtilisé conjointement avec cette fonctionnalité, il permet également de traiter les requêtes POST :
template <typename T> struct envelope { bool ok{}; T data{}; };
template <typename Payload>
static std::optional<Payload> http_get_envelope(const std::string &url) {
auto resp = requests::get(url.c_str());
if (!resp || resp->status_code != 200) return std::nullopt;
envelope<Payload> env{};
if (glz::read<glz_settings>(env, resp->body)) return std::nullopt;
return std::move(env.data);
}
template <typename Body>
static bool http_post_json(const std::string &url, const Body &body) {
std::string buf;
if (glz::write_json(body, buf)) return false;
auto req = std::make_shared<HttpRequest>();
req->method = HTTP_POST;
req->url = url;
req->content_type = APPLICATION_JSON;
req->body = std::move(buf);
auto resp = requests::request(req);
return resp && resp->status_code == 200;
}
static bool http_delete(const std::string &url) {
auto resp = requests::Delete(url.c_str());
return resp && resp->status_code == 200;
}
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.
- Python
- C++ (nlohmann)
- C++ (Glaze)
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
static bool discover_session(const std::string &session_arg) {
if (!session_arg.empty()) {
const auto data = http_get(BASE_URL + "/sessions/" + session_arg);
if (data.is_null()) return false;
session_selector = session_arg;
return true;
}
const auto data = http_get(BASE_URL + "/sessions");
const json &list = data["data"]["sessions"];
for (size_t i = 0; i < list.size(); ++i) {
const int sid = list[i].value("session_id", 0);
std::string prof = "default";
if (list[i].contains("config") && list[i]["config"].contains("profile"))
prof = list[i]["config"]["profile"].value("name", std::string{"default"});
printf(" [%zu] session #%d profile=%s\n", i, sid, prof.c_str());
}
std::string line; std::getline(std::cin, line);
const json &picked = list[std::stoi(line)];
std::string prof;
if (picked.contains("config") && picked["config"].contains("profile"))
prof = picked["config"]["profile"].value("name", std::string{});
session_selector = (!prof.empty() && prof != "default")
? ":" + prof + ":0"
: "#" + std::to_string(picked.value("session_id", 0));
return true;
}
Si vous modélisez le type de réponse sous forme de structure, Glaze le reflète automatiquement :
struct profile_info { std::string name; };
struct session_config{ std::optional<profile_info> profile; };
struct session_info { int session_id{}; std::optional<session_config> config; };
struct sessions_list { int session_count{}; std::vector<session_info> sessions; };
static bool discover_session(const std::string &session_arg) {
if (!session_arg.empty()) {
auto resp = requests::get((BASE_URL + "/sessions/" + session_arg).c_str());
if (!resp || resp->status_code != 200) return false;
session_selector = session_arg;
return true;
}
auto list = http_get_envelope<sessions_list>(BASE_URL + "/sessions");
if (!list || list->sessions.empty()) return false;
for (size_t i = 0; i < list->sessions.size(); ++i) {
const auto &s = list->sessions[i];
std::string prof = "default";
if (s.config && s.config->profile) prof = s.config->profile->name;
printf(" [%zu] session #%d profile=%s\n", i, s.session_id, prof.c_str());
}
std::string line; std::getline(std::cin, line);
const auto &picked = list->sessions[std::atoi(line.c_str())];
std::string prof;
if (picked.config && picked.config->profile) prof = picked.config->profile->name;
session_selector = (!prof.empty() && prof != "default")
? ":" + prof + ":0"
: "#" + std::to_string(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 à [...].
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"}}
- Python
- C++ (nlohmann)
- C++ (Glaze)
def post_basis():
perm, _ = BASIS_OPTIONS[basis_index]
api_post(session_url("/inverse3/0/config/basis"), {"permutation": perm})
static void post_basis() {
http_post_json(session_url("/inverse3/0/config/basis"),
{{"permutation", BASIS_OPTIONS[basis_index].first}});
}
struct basis_body { std::string permutation; };
static void post_basis() {
http_post_json(session_url("/inverse3/0/config/basis"),
basis_body{BASIS_OPTIONS[basis_index].first});
}
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"}}
- Python
- C++ (nlohmann)
- C++ (Glaze)
def post_preset():
preset = PRESET_OPTIONS[preset_index]
api_post(session_url("/inverse3/0/config/preset"), {"preset": preset})
static void post_preset() {
http_post_json(session_url("/inverse3/0/config/preset"),
{{"preset", PRESET_OPTIONS[preset_index]}});
}
struct preset_body { std::string preset; };
static void post_preset() {
http_post_json(session_url("/inverse3/0/config/preset"),
preset_body{PRESET_OPTIONS[preset_index]});
}
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.
- Python
- C++ (nlohmann)
- C++ (Glaze)
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)
static void post_mount() {
http_post_json(session_url("/inverse3/0/config/mount"), {
{"transform", {
{"position", {{"x", mount_pos[0]}, {"y", mount_pos[1]}, {"z", mount_pos[2]}}},
{"rotation", quat_from_euler_deg(mount_rot[0], mount_rot[1], mount_rot[2])},
{"scale", {{"x", mount_scale[0]}, {"y", mount_scale[1]}, {"z", mount_scale[2]}}},
}},
});
}
struct vec3 { float x{}, y{}, z{}; };
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct transform_t { vec3 position{}; quat rotation{}; vec3 scale{1.0f, 1.0f, 1.0f}; };
struct mount_body { transform_t transform; };
static void post_mount() {
http_post_json(session_url("/inverse3/0/config/mount"), mount_body{
transform_t{
.position = vec3{mount_pos[0], mount_pos[1], mount_pos[2]},
.rotation = quat_from_euler_deg(mount_rot[0], mount_rot[1], mount_rot[2]),
.scale = vec3{mount_scale[0], mount_scale[1], mount_scale[2]},
}});
}
mountWa presetsont mutuellement exclusivesLorsque 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.
- Python
- C++ (nlohmann)
- C++ (Glaze)
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"))
static void reset_all() {
http_delete(session_url("/inverse3/0/config/basis"));
http_delete(session_url("/inverse3/0/config/preset"));
http_delete(session_url("/inverse3/0/config/mount"));
}
static void reset_all() {
http_delete(session_url("/inverse3/0/config/basis"));
http_delete(session_url("/inverse3/0/config/preset"));
http_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.
- Python
- C++ (nlohmann)
- C++ (Glaze)
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,
}
static json quat_from_euler_deg(float pitch_x, float yaw_z, float roll_y) {
constexpr float deg_to_rad = 3.14159265358979323846f / 180.0f;
const float hx = pitch_x * 0.5f * deg_to_rad;
const float hy = roll_y * 0.5f * deg_to_rad;
const float hz = yaw_z * 0.5f * deg_to_rad;
const float cx = std::cos(hx), sx = std::sin(hx);
const float cy = std::cos(hy), sy = std::sin(hy);
const float cz = std::cos(hz), sz = std::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},
};
}
static quat quat_from_euler_deg(float pitch_x, float yaw_z, float roll_y) {
constexpr float deg_to_rad = 3.14159265358979323846f / 180.0f;
const float hx = pitch_x * 0.5f * deg_to_rad;
const float hy = roll_y * 0.5f * deg_to_rad;
const float hz = yaw_z * 0.5f * deg_to_rad;
const float cx = std::cos(hx), sx = std::sin(hx);
const float cy = std::cos(hy), sy = std::sin(hy);
const float cz = std::cos(hz), sz = std::sin(hz);
return quat{
.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,
};
}
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,
keyboardOn 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, etBetPLa 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
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)