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
mountPermet 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/sessionsen effectuant une requête sur*inverse/0est codé en dur, mais/devicesPour 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.
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é.
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
- Python
- C++
| Clé | Opération |
|---|---|
B | Changer l'ordre des permutations de la base, l'une après l'autre |
P | Passer d'un préréglage d'espace de travail à l'autre |
W / E / R | Sé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 Down | Se déplacer dans la direction +Z / −Z dans le mode actuel |
= / - | Agrandissement/réduction uniforme et simultané des trois axes (±) (toujours disponible) |
Delete | DELETE Réinitialiser les paramètres « basis », « preset » et « mount » pour rétablir les paramètres par défaut de l'appareil |
H | Afficher l'aide |
Esc | Fin (Ctrl+C (ou bien) |
Saisissez les données ligne par ligne. Appuyez sur la touche ENTER après chaque saisie.
| Commande | Opération |
|---|---|
b | Changer les permutations de la base les unes après les autres |
p | Passer d'un préréglage d'espace de travail à l'autre |
w / e / r | Sélectionner le mode d'édition du montage — Position (mm) / Rotation (°) / Échelle (%) |
x+[N] … z-[N] | L'axe actuel, en unités du mode actif N Déplacement uniquement (x+ Si ce n'est que cela, la valeur par défaut est 5) |
sx+[N] … sz-[N] | Raccourci pour une échelle non uniforme sur un axe (en pourcentage). Toujours disponible |
u+[N] / u-[N] | Agrandissement/réduction uniforme de ± N % sur les trois axes simultanément |
reset | DELETE Réinitialiser « basis », « preset » et « mount » |
h | Afficher l'aide |
Appuyez sur Ctrl+C(ou Ctrl+D / EOF) pour quitter.
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éthode | Rôle | Chemin d'accès à utiliser |
|---|---|---|
GET | Lire 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=... |
POST | Remplacer les valeurs de configuration — Le corps 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'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 :
- Python
- C++ (nlohmann)
- C++ (Glaze)
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}"
libhv は requests::get / requests::post / requests::Delete est disponible (D est en majuscules.delete (car il s'agit d'un mot-clé du C++). Avec la méthode POST,Content-Type: application/json Pour le configurer, vous devez composer la requête manuellement :
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} possède une enveloppe. Toutes les requêtes GET typées sont encapsulées dans un seul modèle. Il en va de même pour les requêtes POST. HttpRequest en utilisant le modèle,glz::write_json est traité comme suit :
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
--session Le traitement varie selon que cette option est spécifiée ou non :
--session SELECTORsi vous spécifiez →GET /sessions/<SELECTOR>Exécutez cette opération une fois.200Dans ce cas, on utilise ce résultat, et404Dans 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:0en 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.
- 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;
}
Lorsque vous modélisez les types de réponse sous forme de structures, Glaze les 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 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>
*inverseTout appareil appartenant à la gamme Inverse (inverse3,inverse3x,minverse). Ce tutoriel fonctionne tel quel, quel que soit le modèle utilisé.0Il 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 [...].
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"}}
- 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": { ... }}} — 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]},
}});
}
mount et preset sont mutuellement exclusivesLorsqu'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.
- 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"));
}
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.
- 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,
};
}
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 は
keyboardNous 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.BetPÀ 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#ifdefComme cela permet d'éviter les ramifications, le code est portable.
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/ 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)