08. Konfigurator für Fernsitzungen
Umeine Sitzung, die bereits an anderer Stelle ausgeführt wird– beispielsweise in einer anderen Anwendung, einer Unity-Szene oder einer „Haply Hub“-Demo –, neu zu konfigurieren, müssen Sie einen HTTP-REST-Aufruf an das entsprechende Gerät senden. In diesem Tutorial werden keine WebSockets geöffnet: Mit GET-, POST- und DELETE-Anfragen können Sie die Basis, die Arbeitsbereichsvoreinstellungen oder die Mount-Transformation ändern, während das haptische Feedback in der anderen Anwendung weiterhin gerendert wird.
Anwendungsszenarien
- Führen Sie in Echtzeit Feinabstimmungen an der laufenden Demo durch. Starten Sie die „ Haply Hub Orb“-Demo und führen Sie dieses Tutorial in einem zweiten Terminal aus, um die Verschiebung der Basisachsen umzuschalten, die Arbeitsbereichsvoreinstellungen zu ändern oder die „mount“-Transformation fein abzustimmen – das Koordinatensystem von Orb passt sich sofort an, ohne dass die Demo angehalten werden muss.
- Kalibrierung des Arbeitsbereichs pro Benutzer. Die haptischen Szenarien sollen auf dem Host weiterlaufen und es den Bedienern im selben Netzwerk ermöglichen, Daten zu übertragen
mountVerschiebung / Drehung / Skalierung, um den virtuellen Arbeitsbereich an den Schreibtisch des Benutzers anzupassen. - Optionsmenü mit Geräteauswahl. Die gleichen HTTP-Hilfsfunktionen können aufgerufen werden
GET /devices(siehe Tutorial 00) um Geräte aufzulisten und ein interaktives Menü zu erstellen – nach Auswahl eines Geräts kann dieses neu konfiguriert werden –, ohne den WebSocket in der Sitzung zu berühren. In diesem Tutorial wird Folgendes abgefragt:/sessionsund fest codiert*inverse/0, aber stattdessen wurde auf/devicesDie Auswahl des Treibers ist lediglich eine lokale Änderung. - Skriptgesteuerte Neukonfiguration. Automatische Ausführung der Vorbereitungsschritte (Einrichtung von „basis“ + Voreinstellungen + Einbindung) vor Beginn der Aufzeichnung einer Sitzung, sodass diese Einstellungen nicht in jedem Client einzeln vorgenommen werden müssen.
Voraussetzungen
Tutorial 08 dient zur Neukonfigurationeiner bereits laufenden Sitzung. Du benötigst eine aktive Haptic-Sitzung – entweder ein anderes Tutorial, eine Unity-Szene oder eine „ Haply -Hub“-Demo.
Öffnen Sie den „Haply Hub“, startenSie die Orb-Demo und legen Sie diese direkt als Ziel fest:
./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 im Arbeitsbereich des Geräts. Wenn Sie im Tutorial 08 zwischen „basis“, den Voreinstellungen oder der Feinabstimmung der „mount“-Transformation hin- und herwechseln, verschiebt sich das Koordinatensystem von „Orb“ in Echtzeit visuell.
Verwendung
# 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 beim Start die aktuellen Werte für „basis“, „preset“ und „mount“ der Sitzung aus und wartet dann auf Tasteneingaben – jede Tasteneingabe löst genaueinen REST-Aufruf aus.
Sitzungen, für die kein Profilname festgelegt wurde, können nur über eine numerische ID angegeben werden – wobei sich diese ID bei jeder Ausführung ändert. Bitte lassen Sie die Hauptanwendung in ihrer ersten Nachricht Folgendes aufrufen: session.configure.profile.name, danach kann man bei jedem Durchlauf den gleichen Selektor wiederverwenden, zum Beispiel --session :my_profile:0。Siehe Sitzung — Profilname。
Tastenbelegung
- Python
- C++
| Taste | Bedienung |
|---|---|
B | Zykluswechsel – Basiswechsel |
P | Zyklische Umschaltung zwischen Arbeitsbereichsvoreinstellungen |
W / E / R | Befestigungsmodus auswählen – Position (mm) / Drehung (°) / Skalierung (%) |
← / → | Im aktuellen Modus entlang −X / +X schreiten |
↑ / ↓ | Im aktuellen Modus entlang der +Y-/−Y-Achse schreiten |
Page Up / Page Down | Im aktuellen Modus entlang +Z / −Z schreiten |
= / - | Gleichzeitige einheitliche Skalierung aller drei Achsen ± (immer verfügbar) |
Delete | DELETE Basis + Voreinstellung + Halterung — Auf die Werkseinstellungen zurücksetzen |
H | Hilfe anzeigen |
Esc | Beenden (Ctrl+C (Das geht auch.) |
Zeilenweise Eingabe – Nach der Eingabe die ENTER-Taste drücken.
| Befehl | Bedienung |
|---|---|
b | Zyklische Umschaltung, Basisersetzung |
p | Zyklusweise zwischen Arbeitsbereichsvoreinstellungen wechseln |
w / e / r | Befestigungsmodus auswählen – Position (mm) / Drehung (°) / Skalierung (%) |
x+[N] … z-[N] | Um einen Schritt in Richtung der aktuellen Achse mit den natürlichen Einheiten des aktuellen Modus ausführen N(separat) x+ Standardwert ist 5) |
sx+[N] … sz-[N] | Schnellzugriff auf die ungleichmäßige Skalierung einzelner Achsen (in Prozent), jederzeit verfügbar |
u+[N] / u-[N] | Gleichzeitige einheitliche Skalierung aller drei Achsen um ±N % |
reset | DELETE Basis + Voreinstellung + Halterung |
h | Hilfe anzeigen |
Drücken Sie Strg+C(oder Strg+D / EOF), um das Programm zu beenden.
HTTP-Verben – GET, POST, DELETE
In diesem Tutorial werden die drei HTTP-Verben verwendet – und zwar ausschließlich diese drei. Jeder Aufruf gibt einen Standardwert zurück. JSON-Antwortformat(im Erfolgsfall) {"ok": true, "data": {...}}, im Fehlerfall {"ok": false, "error": "..."}), und gibt einen der folgenden drei Statuscodes zurück:200 Erfolg,400 Fehler im Anfrageformat,404 Der Selektor hat keine Treffer gefunden.
| Verb | Verwendungszweck | Verwendete Pfade |
|---|---|---|
GET | Aktuellen Status abrufen – Sitzungsliste, Abfrage einer bestimmten Sitzung, aktuelle Konfigurationswerte | /sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=... |
POST | Konfigurationswerte ersetzen – Anfragetext im JSON-Format | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | Die Konfigurationswerte auf die Werkseinstellungen zurücksetzen | /<device_selector>/config/{basis,preset,mount}?session=... |
HTTP-Hilfsfunktionen
Dies ist eine schlanke Kapselung der drei oben genannten Verben, wodurch sich der Rest dieses Tutorials eher wie Geschäftslogik liest:
- Python
- C++ (nlohmann)
- C++ (Glaze)
Verwendung von Python requests.Session() Realisierung der Wiederverwendung von HTTP-Dauerverbindungen (wodurch die Latenz pro Anfrage von ca. 50 ms auf ca. 5 ms gesenkt werden kann):
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 Bereitstellung requests::get / requests::post / requests::Delete(Achtung: Großschreibung der Anfangsbuchstaben) D — delete (ist ein Schlüsselwort in C++). Bei POST muss die Anfrage manuell erstellt werden, um 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;
}
Die Antwort enthält immer {"ok", "data": T} Verpackungsstruktur. Mit einer einzigen Vorlage lassen sich alle GET-Anfragen mit Typen verpacken; ebenso HttpRequest Musteranpassung glz::write_json Kann für POST verwendet 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;
}
Sitzungserkennung — GET /sessions
gemäß --session Die Parameter bestimmen den Ausführungsweg:
- Es wurden
--session SELECTOR→ Einmal sendenGET /sessions/<SELECTOR>。200→ Direkt verwenden;404→ Mit Fehlermeldung beenden. - Es wurden keine Parameter angegeben. →
GET /sessions(Liste) → Sitzungen mit Profilnamen anzeigen → Eingabe eines Indexes anfordern → Endgültigen Selektor erstellen (vorrangig verwenden, sofern verfügbar):profile:0, andernfalls wird auf#id).
SELECTOR Unterstützung Selektor – Sitzungsselektor Alle darin definierten Formen::profile:instance、#id、:-1、:0、reiner Profilname oder in der Form co.haply.hub::*:0 的 Profil-Platzhalter für Namen Muster. In diesem Tutorial wird diese Zeichenfolge unverändert weitergeleitet; die Auswertung erfolgt auf dem Server.
- 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;
}
Modellieren Sie die Struktur der Antwort mithilfe einer Struktur; Glaze führt automatisch die Reflexionsberechnung 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 Konfigurationsaufruf ist auf ein bestimmtes Gerät beschränkt. In diesem Tutorial werden Selektoren inder Form „Serienplatzhalter + Index“verwendet:
/*inverse/0/config/<key>
*inverseKompatibel mit beliebigen Geräten der Inverse-Serie (inverse3、inverse3x、minverse) — Unabhängig vom konkreten Modell lässt sich diese Anleitung ohne Änderungen ausführen.0Dies ist der Index dieser Serie, der bei 0 beginnt – in diesem Tutorial wird nur das erste Inverse-Gerät behandelt.
Um das Zielgerät zu wechseln, muss lediglich eine Zeichenfolge geändert werden – übergeben --device Parameter (der Pfad wird anhand dieses Parameters generiert):
/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 im IndexZuvorIn Kraft treten – „—“*inverse:left/0„bedeutet ‚der 0.‘“linke Hand „inverse“ und nicht „das 0. inverse, hoffentlich das linke“. Siehe Selektoren – Klassenfilter werden vor der Indizierung ausgewertet。
Das Tutorial gibt beim Start eine Meldung aus GET …/config/basis Anfrage. Falls in der Sitzung keine mit
--device Passende Geräte (z. B. bei Verwendung in einer Umgebung, in der nur rechtshändige Geräte vorhanden sind) --device '*inverse:left/0'), gibt der Dienst Folgendes zurück:
404 no device matches selector '<sel>'; Die Anleitung wird ausgedruckt Device '<sel>' not found in session '<sel>'.
und mit dem Status 1 Beenden. Es wird nicht mit veralteten Standardwerten in den Tastaturzyklus eingetreten.
Die vollständige Syntax finden Sie unter Selektor – Geräteauswahl。Um das Geräteauswahlmenü zu erstellen, anstatt es fest zu programmieren, verwenden Sie GET /devices?session=<selector>(Tutorial 00) Geräte auflisten und das ausgewählte device_id Geben Sie den Konfigurationspfad ein.
POST-Konfiguration — basis, preset, mount
Die drei Anfragen haben denselben Aufbau, unterscheiden sich jedoch in der Struktur des Request-Körpers. Jede POST-Anfrage gibt Folgendes zurück: 200, und außerdem data enthält den Ergebniswert; wenn der Sitzungs- oder Geräteselektor kein Objekt findet, wird 404。
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});
}
Voreinstellung
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": { ... }}} — Eine Transformation, die erst nach der Normalisierung der Rückmeldung wirksam wird.
- 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 und preset ExklusivitätDie Ausführung eines POST-Aufrufs für eine der beiden Optionen löscht die andere auf dem Gerät. In diesem Tutorial wird dies nicht ausdrücklich behandelt – jeder POST-Aufruf ist eigenständig, und die Lösung von Konflikten obliegt dem Server. Eine Erläuterung derselben Regel aus Sicht der WebSocket-Seite finden Sie in Tutorial 07.
DELETE Zurücksetzen — dreimaliger Aufruf
reset Für jeden Konfigurationsposten wird jeweils eine DELETE-Anfrage gesendet. Bei jeder Anfrage wird Folgendes zurückgegeben: 200, und außerdem data Enthält die wiederhergestellten Standardwerte.
- 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"));
}
Kombiniertes Aufhängen und Drehen
Bei der Übertragung,transform.rotation Es handelt sich um eine Quaternion-Einheit. In diesem Tutorial werden die Drehungen als Z-Y-X-Tupel von Euler-Winkeln (Pitch um die X-Achse, Yaw um die Z-Achse, Roll um die Y-Achse, jeweils in Grad) gespeichert und bei jedem POST wieder zu einer Quaternion zusammengesetzt.
- 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,
};
}
Einheit: Hamilton-Quaternionen, Rechtshandregel, Skalar vorne (w) – In Übereinstimmung mit den Vereinbarungen in anderen Teilen des Dienstes, siehe quaternion。Die Reihenfolge der Kombinationen lautet Z-Y-X Innenrotation (intrinsisch)(q = q_z * q_y * q_x):Zuerst wird eine Nickbewegung (Pitch) um die X-Achse, dann eine Rollbewegung (Roll) um die Y-Achse und schließlich eine Gierbewegung (Yaw) um die Z-Achse ausgeführt.
Das Tutorial gibt in jeder Statuszeile sowohl die abgeleitete Quaternion als auch das Eulerwinkel-Tupel aus, damit Sie die Ergebnisse der Kombination überprüfen können, bevor sich das Gerät dreht. Unabhängig davon, welche Werte in der Sitzung bereits vorhanden sind, beginnt der lokale Eulerwinkel-Status bei (0, 0, 0) Los geht’s – das erste Mal mount POST Der ursprüngliche Wert wird überschrieben.
Eingabemodell (Kurzbeschreibung)
Die Verbindungslogik von HTTP steht im Vordergrund, die Tastaturinteraktion ist nur zweitrangig. Hier gibt es zwei bewusst vorgenommene Vereinfachungen:
- Python Verwendung
keyboardPaket – plattformübergreifend, native Verarbeitung von wiederholten Langdruck-Eingaben. Pfeiltasten,Page Up/Page Downsowie=/-Die Befestigungsachse wird während des Gedrückthaltens schrittweise verschoben;BundPWechselt bei der steigenden Flanke zwischen Basis und Voreinstellung hin und her. - C++ Verwendung
std::getline(std::cin, ...)und die kompakte Token-Syntax (x+20、sx-5、u+10) – Für kontinuierliche Feineinstellungen ist es nicht besonders handlich, aber man muss nicht#ifdefDie Konsolen-APIs der einzelnen Plattformen werden voneinander getrennt, was die Portierung erleichtert.
Quellcode
Das Tutorial 08 wird ebenfalls zusammen mit dem SDK lokal installiert – es befindet sich im Installationsverzeichnis des Dienstes unter tutorials/08-haply-inverse-http-remote-config/ dort gefunden.
Verwandte Themen: Sitzungen – Fernsteuerung · Auswahlwerkzeug · Gerätekonfiguration · Basis-Ersetzung · Einbindungen und Arbeitsbereiche · JSON-Konventionen · Tutorial 00 – Geräteliste · Tutorial 07 – Basis und Einbindungen (WebSocket-Version)