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
mountle 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/sessionset a été codé en dur*inverse/0, mais en optant pour une solution proposée par/devicesLe 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).
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.
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
- Python
- C++
| touche | Opération |
|---|---|
B | Commutation cyclique – Remplacement de base |
P | Basculer entre les préréglages d'espace de travail |
W / E / R | Sé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 Down | Avancer pas à pas dans le mode actuel le long des axes +Z / −Z |
= / - | Mise à l'échelle simultanée et uniforme des trois axes ± (toujours disponible) |
Delete | DELETE base + préréglage + support — Restaurer les valeurs par défaut de l'appareil |
H | Afficher l'aide |
Esc | Quitter (Ctrl+C (C'est également possible) |
Saisie ligne par ligne — Tapez le texte, puis appuyez sur la touche ENTRÉE.
| Ordre | Opération |
|---|---|
b | Commutation cyclique et remplacement de la base |
p | Basculer entre les préréglages d'espace de travail de manière cyclique |
w / e / r | Sélectionner le mode d'édition du montage — Position (mm) / Rotation (°) / Échelle (%) |
x+[N] … z-[N] | Avancer d'un pas le long de l'axe actuel en utilisant l'unité naturelle du mode actuel N(séparé) x+ (valeur par défaut : 5) |
sx+[N] … sz-[N] | Raccourci permettant d'appliquer un redimensionnement non uniforme à un axe (en pourcentage), toujours disponible |
u+[N] / u-[N] | Mise à l'échelle simultanée et uniforme des trois axes de ±N % |
reset | DELETE base + préréglage + support |
h | Afficher l'aide |
Appuyez sur Ctrl+C(ou Ctrl+D / EOF) pour quitter.
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.
| Verbe | Utilisation | Chemin d'accès utilisé |
|---|---|---|
GET | Lecture 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=... |
POST | Remplacer les valeurs de configuration — Corps de la requête au format JSON | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | Ré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 :
- Python
- C++ (nlohmann)
- C++ (Glaze)
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}"
libhv Mise à disposition requests::get / requests::post / requests::Delete(Attention à la majuscule initiale) D — delete (c'est un mot-clé du C++). La méthode POST nécessite de construire manuellement la requête afin de définir Content-Type: application/json:
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 comporte toujours {"ok", "data": T} Structure de encapsulation. Un seul modèle permet d'encapsuler toutes les requêtes GET avec des types de données ; de même, HttpRequest Assortiment de modèles glz::write_json Peut être utilisé pour 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;
}
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 foisGET /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.
- 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;
}
Modélisez la structure de la réponse à l'aide de structures ; Glaze se chargera automatiquement de la réflexion :
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 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>
*inverseCompatible 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.0Il 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。
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"}}
- 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é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"}}
- 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": { ... }}} — Transformation qui prend effet après la normalisation de l'affichage.
- 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]},
}});
}
mount et preset Exclusion mutuelleL'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.
- 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"));
}
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.
- 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,
};
}
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
keyboardEnveloppe — Multiplateforme, gestion native de la répétition lors d'un appui long sur une touche. Touches directionnelles,Page Up/Page Downainsi que=/-l'axe de suspension se déplacera progressivement tant que le bouton restera enfoncé ;BetPCommutation 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#ifdefDistinguer les API des consoles de chaque plateforme, ce qui facilite la portabilité.
Code source
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)