08. Konfigurationsprogramm für Remote-Sitzungen
Konfigurieren Sie eine bereits an anderer Stelle ausgeführte Sitzung neu – in einer anderen Anwendung, einer Unity-Szene oder einer Demo im „Haply -Hub“ –, indem Sie REST-HTTP-Aufrufe an das entsprechende Gerät senden. Dieses Tutorial öffnet keine WebSockets: Es führt lediglich GET-, POST- und DELETE-Anrufe durch, um die Basis, die Arbeitsbereichsvoreinstellung oder die Montage-Transformation zu ändern, während die andere Anwendung weiterhin haptisches Feedback erzeugt.
Anwendungsfälle
- Live-Anpassung einer laufenden Demo. Starten Sie die Demo „ Haply Hub Orb“ und führen Sie anschließend dieses Tutorial in einem zweiten Terminal aus, um die Basispermutation zu ändern, die Arbeitsbereichsvoreinstellung anzupassen oder die Montage-Transformation leicht zu korrigieren – das Koordinatensystem des Orbs verschiebt sich sofort, ohne dass die Demo angehalten wird.
- Kalibrierung des Arbeitsbereichs pro Benutzer. Halten Sie die haptische Szene auf dem Hauptrechner aktiv und lassen Sie einen Bediener im selben Netzwerk eine Verschiebung, Drehung oder Skalierung von
mountdamit sich der virtuelle Arbeitsbereich an den Desktop des Benutzers anpasst. - Auswahlmenü für Geräte. Dieselben HTTP-Hilfsfunktionen können Abfragen durchführen
GET /devices(siehe Tutorial 00) um die Geräte aufzulisten und ein interaktives Menü zu erstellen – ein Gerät auswählen und es anschließend neu konfigurieren –, ohne den WebSocket der Sitzung zu verändern. Das Tutorial fragt ab/sessionsund fest*inverse/0im Code, aber diese Auswahl durch einen Selektor ersetzen, der gesteuert wird durch/deviceserfordert lediglich eine lokale Änderung. - Skriptgesteuerte Konfiguration. Automatisieren Sie die vorbereitenden Schritte (Datenbank, Voreinstellung und Montage festlegen), bevor eine Sitzung mit der Aufzeichnung beginnt, ohne die Konfiguration in jeden Client integrieren zu müssen.
Voraussetzungen
In Tutorial 08 wird eine bereits laufende Sitzung neu konfiguriert. Sie benötigen eine beliebige aktive haptische Sitzung – ein anderes Tutorial, eine Unity-Szene oder eine Demo aus dem „ Haply -Hub“.
Öffnen Sie den „Haply -Hub“ und starten Sie die Orb-Demo. Wählen Sie diese anschließend direkt 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 zeigt eine Kugel im Arbeitsbereich des Geräts an. Wenn Sie die Basis oder die Voreinstellung in einer Schleife durchlaufen oder die Montage-Transformation mithilfe des Tutorials 08 leicht anpassen, verschiebt sich der Koordinatenursprung des Orbs visuell in Echtzeit.
Nutzung
# 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
Beim Start zeigt das Tutorial die aktuelle Basis, die Voreinstellung und die aktuelle Konfiguration der Sitzung an und wartet anschließend auf Tasteneingaben – jede Tasteneingabe löst einen einzelnen REST-Aufruf aus.
Sitzungen ohne Profilnamen können nur über eine numerische Kennung identifiziert werden, die sich bei jeder Ausführung ändert. Rufen Sie Folgendes auf: session.configure.profile.name über Ihre Hauptanwendung bereits ab der ersten Nachricht: Sie können dann einen stabilen Selektor wie --session :my_profile:0 bei jeder Ausführung. Siehe Sessions – Profilname.
Tastaturkürzel
- Python
- C++
| Touche | Aktion |
|---|---|
B | Die Grundpermutationen durchgehen |
P | Durchsuchen der Arbeitsbereichsvoreinstellungen |
W / E / R | Bearbeitungsmodus für die Montage auswählen – Position (mm) / Drehung (°) / Maßstab (%) |
← / → | Im aktuellen Modus um −X / +X weitergehen |
↑ / ↓ | Im aktuellen Modus um +Y / −Y weitergehen |
Page Up / Page Down | Im aktuellen Modus um +Z / −Z weitergehen |
= / - | Gleichmäßige Skalierung ± auf allen drei Achsen gleichzeitig (immer verfügbar) |
Delete | DELETE Grundeinstellung + Voreinstellung + Einrichtung – Die Werkseinstellungen des Geräts wiederherstellen |
H | Hilfe anzeigen |
Esc | Drückeberger (Ctrl+C (funktioniert auch) |
Zeile für Zeile – tippen Sie den Text ein und drücken Sie dann die Eingabetaste.
| Bestellung | Aktion |
|---|---|
b | Die Grundpermutationen durchgehen |
p | Durchsuchen der Arbeitsbereichsvoreinstellungen |
w / e / r | Bearbeitungsmodus für die Montage auswählen – Position (mm) / Drehung (°) / Maßstab (%) |
x+[N] … z-[N] | Den aktuellen Schwerpunkt von … weiterentwickeln N in der natürlichen Einheit des Aktivmodus (x+ seul = 5 (Standardwert) |
sx+[N] … sz-[N] | Verknüpfung für die ungleichmäßige Skalierung entlang einer Achse (in Prozent), immer verfügbar |
u+[N] / u-[N] | Gleichmäßige Skalierung ± N % auf allen drei Achsen gleichzeitig |
reset | DELETE Basis + Voreinstellung + Montage |
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 drei HTTP-Verben verwendet, und zwar ausschließlich diese. Jeder Aufruf gibt dieStandard-JSON-Container ({"ok": true, "data": {...}} im Erfolgsfall, {"ok": false, "error": "..."} (im Falle eines Fehlers) sowie einen von drei Statuscodes: 200 Erfolg, 400 Fehlerhafte Anfrage, 404 Keine Ergebnisse für den Selektor.
| Verben | Rolle | Verwendete Pfade |
|---|---|---|
GET | Aktuellen Status anzeigen – Liste der Sitzungen, Suche nach einer bestimmten Sitzung, aktuelle Konfigurationswerte | /sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=... |
POST | Einen Konfigurationswert ersetzen – der Textkörper liegt im JSON-Format vor | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | Eine Konfigurationsoption auf den Standardwert des Geräts zurücksetzen | /<device_selector>/config/{basis,preset,mount}?session=... |
HTTP-Hilfsfunktionen
Eine dünne Kapselungsschicht um die drei Verben herum, damit sich der Rest des Tutorials wie Fachlogik liest:
- Python
- C++ (nlohmann)
- C++ (Glaze)
Python nutzen requests.Session() Für HTTP-Keep-Alive (reduziert die Latenz pro Anfrage von etwa 50 ms auf etwa 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 aufdecken requests::get / requests::post / requests::Delete (mit einem D Großbuchstabe — delete ist ein C++-Schlüsselwort). Für POST ist eine manuell erstellte Anfrage erforderlich, 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 Antworttexte haben immer einen Umschlag {"ok", "data": T}. Jeder typisierte GET-Aufruf wird von einer einzigen Vorlage umschlossen; dasselbe Muster HttpRequest POST-Anfragen mit glz::write_json :
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;
}
Einführung in die Sitzungen — GET /sessions
Das Verhalten hängt ab von --session :
--session SELECTORgeliefert → nur einerGET /sessions/<SELECTOR>.200→ es verwenden;404→ einen Fehler melden und den Vorgang abbrechen.- Keine Option →
GET /sessions(Liste) → Sitzungen mit ihren Profilnamen anzeigen → Index anfordern → den endgültigen Selektor erstellen (bevorzugt:profile:0sofern verfügbar; andernfalls verwenden#id).
SELECTOR akzeptiert alle in … definierten Formen Auswahlfelder – Sitzungsauswahlfeld : :profile:instance, #id, :-1, :0, ein einfacher Profilname oder ein Motiv mit Platzhaltern im Profilnamen wie co.haply.hub::*:0. Das Tutorial gibt die Zeichenkette unverändert weiter; sie wird vom Dienst analysiert.
- 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;
}
Modelliert die Form der Antwort mithilfe von Structs; Glaze generiert diese automatisch durch Reflexion:
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 bezieht sich auf ein bestimmtes Gerät. Im Tutorial wird ein Platzhalter-Selektor aus Familie + Index verwendet:
/*inverse/0/config/<key>
*inverseentsprechen jedem Gerät der Inverse-Familie (inverse3,inverse3x,minverse) — Die Anleitung funktioniert ohne Änderungen, unabhängig vom jeweiligen Modell.0ist der Index, beginnend bei Null, in dieser Familie – das Tutorial befasst sich ausschließlich mit dem ersten Inverse.
Um das Ziel zu ändern, ist nur eine einzige Änderung an der Zeichenkette erforderlich: Wechseln Sie zu --device (der Weg ist angelegt):
/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
Die Klasse :left / :right wird angewendet avant die Indexierung — *inverse:left/0 bezeichnet „die 0. Inverse“ Gaucher », und nicht „die 0. Inverse, hoffentlich linkshändig“. Siehe Selektoren – Klassenfilter werden vor der Indizierung aufgelöst.
Das Tutorial gibt eine GET …/config/basis beim Start. Wenn die Sitzung kein
Gerät enthält, das --device (z. B. --device '*inverse:left/0' bei einer
ausschließlich rechtsseitigen Konfiguration) antwortet der Dienst 404 no device matches selector '<sel>' ; das Tutorial zeigt an Device '<sel>' not found in session '<sel>'.
und endet mit dem Status 1. Es wird keine Tastaturschleife mit veralteten Standardwerten gestartet.
Lesen Sie Wahlschalter – Gerätewahlschalter für die vollständige Grammatik. Um ein Menü zur Geräteauswahl zu erstellen, anstatt die Geräte fest zu programmieren, listen Sie die Geräte mit GET /devices?session=<selector> (Tutorial 00) und fügen Sie das device_id in den Konfigurationspfaden ausgewählt.
Konfigurations-POST – Basis, Voreinstellung, Einbindung
Drei Konfigurationsschlüssel, gleiche Abfragestruktur, unterschiedliches Hauptschema. Jeder POST-Aufruf gibt eine 200 mit dem sich daraus ergebenden Wert in data, oder ein 404 wenn der Sitzungs- oder Gerätewähler keiner Option entspricht.
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": { ... }}} — gibt die tatsächliche Transformation nach Normalisierung zurück.
- 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 schließen sich gegenseitig ausEin POST-Aufruf für einen der beiden Schlüssel löscht den anderen auf dem Gerät. Im Tutorial wird dies nicht ausdrücklich behandelt: Jeder POST-Aufruf ist eigenständig, und der Server löst den Konflikt auf. Siehe Tutorial 07 für dieselbe Regel auf der WebSocket-Seite.
DELETE-Rücksetzung – drei Aufrufe
reset löst einen DELETE-Befehl pro Konfigurationsschlüssel aus. Jeder gibt einen 200 wobei der Standardwert in data.
- 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"));
}
Zusammensetzung der Rotation der Montage
transform.rotation ist ein unitäres Quaternion, das über das Netzwerk übertragen wird. Das Tutorial speichert die Drehung als intrinsisches Euler-Triplet Z-Y-X (Nickbewegung um die X-Achse, Gierbewegung um die Z-Achse, Rollbewegung um die Y-Achse – jeweils in Grad) und bildet das Quaternion bei jedem POST-Aufruf neu.
- 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,
};
}
Unitäres Hamilton-Quaternion im Rechtshänder-Koordinatensystem, mit dem Skalaranteil an erster Stelle (w) – gleiche Konvention wie beim restlichen Dienst, siehe quaternion. Die Reihenfolge der Zusammensetzung ist Intrinsisches Z-Y-X (q = q_z * q_y * q_x) : Zunächst wird die Nickbewegung um die X-Achse, dann die Rollbewegung um die Y-Achse und schließlich die Gierbewegung um die Z-Achse aufgebracht.
Das Tutorial zeigt in jeder Statuszeile das abgeleitete Quaternion neben dem Euler-Triplet an, damit Sie die Komposition überprüfen können, bevor sich das Gerät dreht. Der lokale Euler-Status beginnt bei (0, 0, 0), unabhängig davon, welcher Status bereits in der Sitzung vorliegt – der erste mount POST ersetzt den bestehenden Wert.
Einstiegsmodell (kurz)
Die HTTP-Integration ist das Wesentliche; die Tastaturbedienbarkeit ist zweitrangig. Zwei bewusste Abkürzungen:
- Python das Paket nutzen
keyboard— Es ist plattformübergreifend und unterstützt von Haus aus die Wiederholung von gedrückten Tasten. Die Pfeiltasten,Page Up/Page Downet=/-verschieben die Montageachsen, solange sie festgehalten werden;BetPUm die Basis- und Preset-Werte bei steigender Flanke durchlaufen zu lassen. - C++ nutzen
std::getline(std::cin, ...)und eine kompakte Token-Grammatik (x+20,sx-5,u+10) — weniger ergonomisch bei stufenlosen Einstellungen, lässt sich jedoch ohne den Einsatz von#ifdefplattformspezifisch für die Konsolen-APIs.
Quelle
Das Tutorial 08 wird ebenfalls lokal mit dem SDK installiert – siehe tutorials/08-haply-inverse-http-remote-config/ im Installationsverzeichnis des Dienstes.
Siehe auch: Sitzungen – Fernsteuerung · Wahlschalter · Gerätekonfiguration · Basisumschaltung · Mount und Arbeitsbereich · JSON-Konventionen · Tutorial 00 – Geräteliste · Tutorial 07 – Basis und Mount (WebSocket-Version)