08. Konfigurator für Remote-Sitzungen
Es rekonstruiert eine bereits an anderer Stelle ausgeführte Sitzung(z. B. in einer anderen App, einer Unity-Szene oder einer „Haply Hub“-Demo ). In diesem Tutorial wird kein WebSocket geöffnet; es werden lediglich HTTP-REST-Aufrufe (GET, POST, DELETE) an das Gerät gesendet. Sie können die Basis, die Arbeitsbereichsvoreinstellungen und die Befestigungstransformation ändern, während die andere App die haptische Darstellung fortsetzt.
Anwendungsbeispiele
- Echtzeitanpassung der laufenden Demo. Haply Starten Sie die Hub Orb-Demo und führen Sie anschließend dieses Tutorial in einem zweiten Terminal aus, um die Basispermutation zu ändern, die Arbeitsbereichsvoreinstellungen anzupassen oder die Mount-Transformation fein abzustimmen. Das Koordinatensystem von Orb wird sofort umgeschaltet, ohne dass die Demo angehalten werden muss.
- Benutzerspezifische Kalibrierung des Arbeitsbereichs. Die haptische Szene wird auf dem Hauptrechner weiter ausgeführt, und ein Bediener im selben Netzwerk
mountStellen Sie sicher, dass Versatz, Drehung und Skalierung übertragen werden. Dadurch wird der virtuelle Arbeitsbereich an Ihren Schreibtisch angepasst. - Menü zur Geräteauswahl. Mit demselben HTTP-Helper
GET /devices(Tutorial 00 Wenn Sie die Anweisung (Hinweis) ausführen, wird eine Liste der Geräte angezeigt und ein interaktives Menü erstellt. Wählen Sie ein Gerät aus und konfigurieren Sie es neu, ohne dabei den WebSocket der Sitzung zu beeinträchtigen. Dieses Tutorial behandelt/sessionsabfragen und*inverse/0wird fest codiert, aber/devicesDer Wechsel zum Basis-Selektor ist über eine lokale Änderung möglich. - Skriptbasierte Konfiguration. Automatisiert die Vorbereitungsschritte (Einstellungen für „basis“, „preset“ und „mount“) vor Beginn der Sitzungsaufzeichnung. Die Einstellungen müssen nicht auf allen Clients übernommen werden.
Voraussetzungen
In Tutorial 08 wird eine bereits laufende Sitzungneu konfiguriert. Dazu ist eine aktive haptische Sitzung erforderlich. Dies kann ein beliebiges anderes Tutorial, eine Unity-Szene oder eine Demo aus dem „ Haply -Hub“ sein.
Haply Öffnen Sieden Hub, starten Sie die Orb -Demo und wählen Sie anschließend diese Sitzung als Ziel aus:
./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"
Die „Orb“-Szene rendert eine Kugel (sphere) im Geräte-Arbeitsbereich. Wenn Sie gemäß Tutorial 08 zwischen „basis“ und den Voreinstellungen wechseln oder die „mount“-Transformation feinjustieren, verschiebt sich das Koordinatensystem von „Orb“ in Echtzeit auf dem Bildschirm.
Anleitung
# 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
Das Tutorial gibt zu Beginn die Werte für „basis“, „preset“ und „mount“ der aktuellen Sitzung aus und wartet anschließend auf Tasteneingaben. Mit jedem Tastendruck wird genau ein REST-Aufruf gesendet.
Sitzungen ohne Profilnamen können nur mit einer numerischen ID bezeichnet werden, wobei sich diese ID bei jeder Ausführung ändert. Die Haupt-App erhält in der ersten Nachricht session.configure.profile.nameWenn man so aufruft, wird bei jeder Ausführung --session :my_profile:0Feste Selektoren wie diesen können wiederverwendet werden. Sitzung – ProfilnameSiehe dazu.
Tastenkombinationen
- Python
- C++
| Schlüssel | Bewegung |
|---|---|
B | Basis, Permutation, Zyklus |
P | Wechseln zwischen Arbeitsbereichsvoreinstellungen |
W / E / R | Auswahl des Bearbeitungsmodus für die Halterung — Position (mm) / Drehung (°) / Vergrößerung (%) |
← / → | Im aktuellen Modus einen Schritt in Richtung −X / +X bewegen |
↑ / ↓ | Im aktuellen Modus einen Schritt in Richtung +Y / −Y bewegen |
Page Up / Page Down | Im aktuellen Modus einen Schritt in Richtung +Z / −Z bewegen |
= / - | Gleichmäßige Anpassung des Vergrößerungsfaktors um ± auf allen drei Achsen gleichzeitig (jederzeit verfügbar) |
Delete | Basis + Voreinstellung + Halterung DELETE Anwenden — Auf die Werkseinstellungen des Geräts zurücksetzen |
H | Hilfe anzeigen |
Esc | Ende (Ctrl+C(auch in Betrieb) |
Zeilenweise Eingabe — Geben Sie den Befehl ein und drücken Sie anschließend die ENTER-Taste.
| Befehl | Bewegung |
|---|---|
b | Basis, Permutation, Zyklus |
p | Wechseln zwischen Arbeitsbereichsvoreinstellungen |
w / e / r | Auswahl des Bearbeitungsmodus für die Halterung — Position (mm) / Drehung (°) / Vergrößerung (%) |
x+[N] … z-[N] | Die aktuelle Achse als Standardmaßeinheit im aktiven Modus festlegen NUm diese Strecke bewegen (x+Wenn Sie nur dies eingeben, wird der Standardwert 5) verwendet. |
sx+[N] … sz-[N] | Befehl zur ungleichmäßigen Verkleinerung entlang einer Achse (in Prozent), jederzeit verfügbar |
u+[N] / u-[N] | Gleichmäßige Skalierungsanpassung um ± N % auf allen drei Achsen gleichzeitig |
reset | Basis + Voreinstellung + Halterung DELETE Anwendung |
h | Hilfe anzeigen |
Drücken Sie Strg+C (oder Strg+D / EOF), um das Programm zu beenden.
HTTP-Methoden – GET, POST, DELETE
Das Tutorial verwendet nur drei HTTP-Methoden. Alle Aufrufe erfolgen nach dem Standard JSON-Envelope(Bei Erfolg {"ok": true, "data": {...}}, falls dies fehlschlägt {"ok": false, "error": "..."}) und der Statuscode ist einer der folgenden drei: 200 Erfolg, 400 Falsches Anfrageformat, 404 Es wurden keine Einträge gefunden, die dem Selektor entsprechen.
| Methode | Rolle | Anwendungsweg |
|---|---|---|
GET | Aktuellen Status abrufen – Sitzungsliste anzeigen, bestimmte Sitzung anzeigen, aktuelle Einstellungswerte anzeigen | /sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=... |
POST | Einstellungswerte ersetzen — Der Text ist im JSON-Format | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | Einstellungen auf die Werkseinstellungen zurücksetzen | /<device_selector>/config/{basis,preset,mount}?session=... |
HTTP-Helfer
Ein schlanker Wrapper, der drei Methoden umschließt und es ermöglicht, den Code im Tutorial fortan mit Fokus auf die Geschäftslogik zu lesen:
- Python
- C++ (nlohmann)
- C++ (Glaze)
Python unterstützt HTTP-Keep-Alive requests.Session()wird verwendet (wodurch die Latenzzeit pro Anfrage von etwa 50 ms auf etwa 5 ms reduziert wird):
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}"
libhvist requests::get / requests::post / requests::Deletebietet (deleteist ein C++-Schlüsselwort, daher Großbuchstaben Dwird verwendet). Bei POST Content-Type: application/jsonUm dies einzurichten, müssen Sie die Anfrage manuell konfigurieren:
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;
}
Der Antworttext lautet immer {"ok", "data": T} Es gibt einen „Envelope“. Eine einzige Vorlage umschließt alle GET-Anfragen eines bestimmten Typs, und dieselbe HttpRequest Muster glz::write_jsonIn Kombination damit können auch POST-Anfragen verarbeitet werden:
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;
}
Sitzungsnavigation — GET /sessions
--session Je nach Option erfolgt eine Verzweigung:
--session SELECTORfalls angegeben →GET /sessions/<SELECTOR>Einmal aufrufen.200dann die Ergebnisse verwenden und,404Andernfalls wird eine Fehlermeldung ausgegeben und das Programm beendet.- Wenn kein Flag vorhanden ist →
GET /sessions(Liste abrufen) → Sitzungen mit Profilnamen anzeigen → Indexabfrage → Endgültigen Selektor erstellen (sofern möglich:profile:0zuerst verwenden, und wenn nicht vorhanden,#idersetzt).
SELECTORist Selektor – SitzungsselektorEs werden alle dort definierten Formate zugelassen: :profile:instance, #id, :-1, :0, allgemeiner Profilname oder co.haply.hub::*:0wie Profilname-Platzhalter Es handelt sich um ein Muster. Das Tutorial übergibt diese Zeichenfolge unverändert, und die Auswertung übernimmt der Dienst.
- 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;
}
Wenn man die Antwortform als Struktur modelliert, führt Glaze automatisch eine Reflexion durch:
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;
}
Geräteauswahl — *inverse/0
Jeder Aufruf einer Konfiguration gilt für ein einzelnes Gerät. Im Tutorial wird ein Selektor im Format „Produktfamilie-Platzhalter + Index“ verwendet:
/*inverse/0/config/<key>
*inversealle Geräte, die zur Inverse-Produktfamilie gehören (inverse3,inverse3x,minverse) – Das Tutorial funktioniert unabhängig vom jeweiligen Modell ohne Anpassungen.0ist ein Index innerhalb dieser Produktgruppe, der bei 0 beginnt – im Tutorial wird stets nur die erste Inverse verwendet.
Um das Zielgerät zu ändern, muss lediglich eine Zeichenfolge geändert werden — --deviceBitte geben Sie Folgendes an (der Pfad wird aus diesem Wert zusammengestellt):
/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 Klassen sind indiziert Zuvor Gilt für — *inverse:left/0ist „die 0.“ Linkshänder „Inverse“ und nicht „die 0. Inverse, die vermutlich links liegt“. Weitere Informationen finden Sie unter Selektor – Auswertung des Klassenfilters vor der IndizierungSiehe dazu.
Das Tutorial beginnt zu Beginn GET …/config/basisbitte. In der Sitzung --deviceWenn kein passendes Gerät gefunden wird (z. B. bei einer Einstellung ausschließlich für Rechtshänder --device '*inverse:left/0'(bei Verwendung von), bietet der Dienst 404 no device matches selector '<sel>'antwortete, und das Tutorial Device '<sel>' not found in session '<sel>'.nach der Ausgabe von … im Beendigungszustand 1… endet. Es wird nicht in die Tastaturschleife gewechselt, wie es bei den alten Standardwerten der Fall war.
Die gesamte Grammatik lautet Selektor — GeräteselektorSiehe hier. Um anstelle einer festen Angabe ein Menü zur Geräteauswahl zu erstellen, GET /devices?session=<selector>(Tutorial 00) die Geräte auflisten und das ausgewählte device_idVerbinden Sie es mit dem festgelegten Pfad.
POST-Einstellungen – Basis, Voreinstellung, Einbau
Die drei Schlüssel haben zwar dasselbe Anfrageformat, unterscheiden sich jedoch im Schema des Hauptteils. Alle POST-Anfragen liefern einen Rückgabewert datadarin 200zurück und wenn kein Eintrag gefunden wird, der dem Sitzungs- oder Geräteselektor entspricht, 404gibt zurück.
Grundlage
POST /*inverse/0/config/basis?session=:my_profile:0
Content-Type: application/json
{"permutation": "XZY"}
Antwort: {"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});
}
Voreinstellungen
POST /*inverse/0/config/preset?session=:my_profile:0
Content-Type: application/json
{"preset": "arm_front_centered"}
Antwort: {"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]});
}
Halterung
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}
}
}
Antwort: {"ok": true, "data": {"transform": { ... }}} — Die gültige Transformation nach der Normalisierung wird unverändert zurückgegeben.
- 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]},
}});
}
mountWow presetsind sich gegenseitig ausschließendWenn auf einer Seite ein POST-Request gesendet wird, werden die Einstellungen auf der anderen Seite vom Gerät gelöscht. Das Tutorial geht darauf nicht ausdrücklich ein – jeder POST-Request ist unabhängig, und Konflikte werden vom Server gelöst. Die entsprechenden Regeln aus Sicht von WebSocket finden Sie in Tutorial 07.
DELETE-Initialisierung – drei Aufrufe
resetruft für jeden Einstellungsschlüssel einmal DELETE auf. Jeder Aufruf dataund füge nun den Wert ein, der nun als Standardwert gilt 200gibt zurück.
- 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"));
}
Konfiguration der Halterungsdrehung
transform.rotationEs handelt sich um Einheitsquaternionen im Übertragungsformat. Im Tutorial werden Drehungen als intrinsische Euler-Tripel im Z-Y-X-Koordinatensystem gespeichert (Pitch um die X-Achse, Yaw um die Z-Achse, Roll um die Y-Achse – jeweils in Grad). Außerdem wird das Quaternion bei jedem POST neu berechnet.
- 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,
};
}
Einheits-Hamilton-Quaternion, Rechtshänder-Koordinatensystem, Skalar-Priorität (w) Dies ist das Verfahren. Es handelt sich um dasselbe Protokoll wie für den restlichen Teil des Dienstes, und quaternionSiehe dort. Die Synthesefolge lautet Z-Y-X – intrinsisch Verfahren (q = q_z * q_y * q_x). Das heißt, zunächst wird die Neigung (Pitch) um die X-Achse, anschließend die Rollbewegung (Roll) um die Y-Achse und schließlich die Gierbewegung (Yaw) um die Z-Achse angewendet.
Da das Tutorial für jede Statusleiste die abgeleiteten Quaternionen zusammen mit den Euler-Winkelwerten ausgibt, können Sie die Ergebnisse der Bildkomposition überprüfen, bevor sich das Gerät dreht. Der lokale Euler-Zustand ist unabhängig von den bereits in der Sitzung gespeicherten Werten (0, 0, 0)Es beginnt mit. Das erste mount POSTüberschreibt den bisherigen Wert.
Eingabemodell (kurz gefasst)
Die Struktur der HTTP-Verbindung steht im Mittelpunkt, die Tastatur-UX ist zweitrangig. Wir wenden die folgenden zwei bewussten Vereinfachungsansätze an.
- Python In der Umsetzung gilt:
keyboardEs wird ein Paket verwendet. Es ist plattformübergreifend und verarbeitet standardmäßig die Wiederholung von Tastenanschlägen, wenn eine Taste gedrückt gehalten wird. Pfeiltasten,Page Up/Page Down,=/-Solange die Taste gedrückt gehalten wird, bewegt sich die Befestigungsachse, undBsowiePDer Schlüssel wechselt bei der steigenden Flanke (rising edge) zwischen Basis und Voreinstellung hin und her. - C++ In der Umsetzung gilt:
std::getline(std::cin, ...)und eine prägnante Token-Syntax (x+20,sx-5,u+10) wird verwendet. Dies ist zwar für fortlaufende Feinabstimmungen weniger praktisch, aber für plattformspezifische Konsolen-APIs#ifdefEine Transplantation ist auch ohne dessen Verwendung möglich.
Quelle
Auch Tutorial 08 wird zusammen mit dem SDK lokal installiert. Im Installationsverzeichnis des Dienstes unter tutorials/08-haply-inverse-http-remote-config/ Bitte überprüfen Sie die Route.
Verwandte Dokumente: Sitzung – Fernsteuerung · Selektor · Gerätekonfiguration · Basispermutation · Einbindungen und Arbeitsbereiche · JSON-Konvention · Tutorial 00 – Geräteliste · Tutorial 07 – Basis und Einbindungen (WebSocket-Version)