08. Remote-Session-Konfigurator
Konfigurieren Sie eine bereits anderweitig laufende Sitzung neu – beispielsweise eine andere App, eine Szene in Unity oder eine Demo von „Haply Hub“ –, indem Sie über HTTP REST-Aufrufe an das entsprechende Gerät senden. Dieses Tutorial öffnet keine WebSockets: Es werden lediglich GET-, POST- und DELETE-Anfragen gesendet, um die Basis, die Workspace-Voreinstellung oder die Mount-Transformation zu ändern, während die andere App weiterhin haptisches Feedback ausgibt.
Anwendungsfälle
- Laufende Demo live anpassen. Starten Sie die Orb-Demo im „ Haply “-Hub und führen Sie dann dieses Tutorial in einem zweiten Terminal aus, um die Basispermutation zu tauschen, die Workspace-Voreinstellung zu ändern oder die Mount-Transformation leicht zu verschieben – das Koordinatensystem der Orb-Szene verschiebt sich sofort, ohne dass die Demo angehalten werden muss.
- Arbeitsbereichskalibrierung pro Benutzer. Lassen Sie die haptische Szene auf dem Hauptrechner laufen und ermöglichen Sie einem Bediener im selben Netzwerk, einen
mount-Offset / eine Drehung / Skalierung zu übermitteln, damit der virtuelle Arbeitsbereich mit dem Schreibtisch des Benutzers übereinstimmt. - Optionsmenü mit Geräteauswahl. Dieselben HTTP-Helfer können
GET /devicesabfragen (siehe Anleitung 00), um Geräte aufzulisten und ein interaktives Menü aufzubauen – ein Gerät auswählen und anschließend neu konfigurieren –, ohne die WebSocket-Verbindung der Sitzung zu beeinträchtigen. Das Tutorial fragt/sessionsab und fest codiert*inverse/0; der Wechsel zu einer über/devicesBei der gesteuerten Auswahl handelt es sich jedoch um eine lokale Änderung. - Skriptgesteuerte Neukonfiguration. Automatisieren Sie vorbereitende Schritte (Basis, Voreinstellung und Mount festlegen), bevor eine Aufzeichnungssitzung beginnt, ohne die Einrichtung in jeden Client integrieren zu müssen.
Voraussetzungen
Tutorial 08 dient dazu, eine bereits laufende Sitzung neu zu konfigurieren. Sie benötigen eine beliebige aktive haptische Sitzung – ein anderes Tutorial, eine Szene in Unity oder eine Demo im „ Haply -Hub“.
Öffnen Sie den „Haply -Hub“, starten Sie die Orb-Demo und sprechen Sie sie anschließend direkt an:
./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 mit Tutorial 08 „Basis“ oder „Preset“ umschalten oder die Mount-Transformation leicht verschieben, verschiebt sich das Koordinatensystem der Orb-Szene sichtbar in Echtzeit.
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 zeigt beim Start die aktuelle Basis / das Preset / den Mount der Sitzung an und wartet dann auf Tastendrücke – jeder Tastendruck löst genau einen REST-Aufruf aus.
Sitzungen ohne Profilnamen können nur über eine numerische ID angesprochen werden – und diese ändert sich bei jedem Durchlauf. Lassen Sie Ihre Hauptanwendung bei ihrer ersten Nachricht session.configure.profile.name aufrufen; anschließend können Sie einen stabilen Selektor wie --session :my_profile:0 über alle Läufe hinweg wiederverwenden. Siehe Sitzungen — Profilname.
Tastenbelegung
- Python
- C++
| Geschmack | Aktion |
|---|---|
B | Basis-Permutation durchschalten |
P | Arbeitsbereich-Voreinstellung aktivieren |
W / E / R | Montage-Bearbeitungsmodus auswählen — Position (mm) / Drehung (°) / Skalierung (%) |
← / → | Im aktuellen Modus einen Schritt −X / +X |
↑ / ↓ | Im aktuellen Modus einen Schritt +Y / −Y |
Page Up / Page Down | Im aktuellen Modus einen Schritt +Z / −Z |
= / - | Gleichmäßige Skalierung ± auf allen drei Achsen gleichzeitig (stets verfügbar) |
Delete | DELETE für Basis + Preset + Mount — Auf die Werkseinstellungen zurücksetzen |
H | Hilfe anzeigen |
Esc | Beenden (Ctrl+C (das funktioniert ebenfalls) |
Zeilenbasiert – Geben Sie den Befehl ein und drücken Sie anschließend die ENTER-Taste.
| Befehl | Aktion |
|---|---|
b | Basis-Permutation durchschalten |
p | Arbeitsbereich-Voreinstellung aktivieren |
w / e / r | Montage-Bearbeitungsmodus auswählen — Position (mm) / Drehung (°) / Skalierung (%) |
x+[N] … z-[N] | Die aktuelle Achse um N in der natürlichen Einheit des aktiven Modus verschieben (x+ ohne Wert = Standard 5) |
sx+[N] … sz-[N] | Tastenkombination für ungleichmäßige Skalierung auf einer Achse (Prozent), immer verfügbar |
u+[N] / u-[N] | Gleichmäßige Skalierung ± N % auf allen drei Achsen gleichzeitig |
reset | DELETE für Basis + Voreinstellung + Halterung |
h | Hilfe anzeigen |
Zum Beenden Strg+C drücken (oder Strg+D / EOF).
HTTP-Methoden – GET, POST, DELETE
Das Tutorial verwendet drei HTTP-Methoden und ausschließlich diese drei. Jeder Aufruf liefert die standardmäßige JSON-Umschlag ({"ok": true, "data": {...}} im Erfolgsfall, {"ok": false, "error": "..."} (im Fehlerfall) sowie einen von drei Statuscodes: 200 Erfolg, 400 Fehlerhafte Anfrage, 404 Selektor hat nichts gefunden.
| Methode | Zweck | Verwendete Pfade |
|---|---|---|
GET | Aktuellen Status abrufen — Sitzungsliste, gezielte Sitzungsabfrage, aktuelle Konfigurationswerte | /sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=... |
POST | Einen Konfigurationswert ersetzen — Der Request-Body ist JSON | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | Einen Konfigurationswert auf die Werkseinstellung zurücksetzen | /<device_selector>/config/{basis,preset,mount}?session=... |
HTTP-Hilfsfunktionen
Ein schlanker Wrapper um die drei Methoden, damit sich der Rest des Tutorials wie reine Anwendungslogik liest:
- Python
- C++ (nlohmann)
- C++ (Glaze)
Python verwendet requests.Session() für HTTP-Keep-Alive (verkürzt die Latenz pro Anfrage von ~50 ms auf ~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 stellt requests::get / requests::post / requests::Delete bereit (großes D — delete ist ein C++-Schlüsselwort). Für POST ist eine manuell erstellte Anfrage erforderlich, um Content-Type: application/json festzulegen:
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;
}
Response-Bodies haben immer den {"ok", "data": T}-Envelope. Eine einzige Vorlage umschließt jeden typisierten GET; dasselbe HttpRequest-Muster verarbeitet 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;
}
Sitzungsermittlung — GET /sessions
Verzweigt anhand von --session:
--session SELECTORangegeben → einGET /sessions/<SELECTOR>.200→ verwenden;404→ Fehler ausgeben.- Keine Flagge →
GET /sessions(Auflistung) → Sitzungen mit Profilnamen anzeigen → nach einem Index fragen → den endgültigen Selektor erstellen (vorzugsweise:profile:0, sofern verfügbar; andernfalls#id).
SELECTOR akzeptiert jede Form, die unter Selektoren – Sitzungsselektor ist definiert als: :profile:instance, #id, :-1, :0, einen einfachen Profilnamen oder ein Profilnamen-Platzhalter-Muster wie co.haply.hub::*:0. Das Tutorial übergibt die Zeichenkette unverändert; der Dienst analysiert sie.
- 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;
}
Stellen Sie die Antwortstruktur als Structs dar; Glaze bildet sie automatisch ab:
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 Gerät beschränkt. Das Tutorial verwendet einen Selektor aus Familien-Wildcard + Index:
/*inverse/0/config/<key>
*inversepasst auf jedes Gerät der Inverse-Familie (inverse3,inverse3x,minverse) — Das Tutorial funktioniert unverändert, unabhängig vom jeweiligen Modell.0ist der nullbasierte Index innerhalb dieser Familie – im Tutorial wird immer nur die erste Inverse behandelt.
Die Umstellung auf ein anderes Gerät erfordert lediglich eine Änderung der Zeichenkette — übergeben Sie --device (daraus wird der Pfad erstellt):
/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 vor bei der Indizierung angewendet — *inverse:left/0 bedeutet „die 0-te“ Linkshänder „Inverse“, nicht „die 0. Inverse, hoffentlich links“. Siehe Selektoren – Klassenfilter werden vor der Indizierung aufgelöst.
Das Tutorial zeigt beim Start eine GET …/config/basis-Anfrage. Verfügt die Sitzung über kein
Gerät, das --device entspricht (z. B. --device '*inverse:left/0' bei einer
nur rechtsseitigen Anordnung) antwortet der Dienst mit 404 no device matches selector '<sel>'; das Tutorial bietet Device '<sel>' not found in session '<sel>'. aus
und endet mit dem Status 1. Dabei wird keine Tastaturschleife mit veralteten Standardwerten gestartet.
Die vollständige Syntax finden Sie unter Selektoren – Geräteselektor. Um ein Menü zur Geräteauswahl anstelle fest programmierter Werte zu erstellen, zählen Sie die Geräte mit GET /devices?session=<selector> auf (Anleitung 00) und übernehmen die gewählte device_id in die Konfigurationspfade.
POST-Konfiguration – Basis, Voreinstellung, Einbindung
Drei Schlüssel mit identischem Anfrageaufbau, aber unterschiedlichem Body-Schema. Jede POST-Anfrage liefert ein 200 mit dem sich daraus ergebenden Wert in data, oder ein 404, falls die Suche nach Sitzung oder Gerät keine Treffer ergab.
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 effektive Transformation nach der 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 und preset schließen sich gegenseitig ausEin POST-Request für eines der beiden Felder löscht auf dem Gerät das jeweils andere. Das Tutorial geht darauf nicht explizit ein – jeder POST-Request ist eigenständig, und der Server löst den Konflikt auf. Siehe Tutorial 07 für dieselbe Regel aus der Perspektive von WebSocket.
DELETE-Zurücksetzung — drei Aufrufe
reset sendet pro Konfigurationsschlüssel einen DELETE-Befehl. Jeder liefert 200 mit dem nun standardmäßigen Wert 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"));
}
Zusammenstellung der Mount-Rotation
transform.rotation ist in der Übertragung ein Einheitsquaternion. Das Tutorial speichert die Rotation als intrinsisches Euler-Tripel in der Reihenfolge Z-Y-X (Nicken um X, Gieren um Z, Rollen um Y – alle in Grad) und setzt das Quaternion bei jedem POST neu zusammen.
- 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,
};
}
Einheitsquaternion nach Hamilton, rechtshändig, Skalar zuerst (w) — dieselbe Konvention wie im restlichen Dienst, siehe quaternion. Die Reihenfolge der Kompositionen ist Z-Y-X intrinsisch (q = q_z * q_y * q_x): Wenden Sie zunächst die Drehung um die X-Achse (Pitch) an, danach die Drehung um die Y-Achse (Roll) und zuletzt die Drehung um die Z-Achse (Yaw).
Das Tutorial gibt bei jeder Statuszeile das abgeleitete Quaternion zusammen mit dem Euler-Tripel aus, damit Sie die Komposition überprüfen können, bevor sich das Gerät dreht. Der lokale Euler-Zustand beginnt bei (0, 0, 0), unabhängig davon, was die Sitzung bereits enthält — der erste mount POST überschreibt den vorhandenen Wert.
Eingabemodell (kurz)
Im Mittelpunkt steht die Anbindung an HTTP; die Tastatursteuerung ist zweitrangig. Zwei bewusste Verkürzungen:
- Python Das Paket verwendet
keyboard— plattformübergreifend und mit nativer Unterstützung für die Wiederholung von Tasten bei gedrückter Taste. Pfeiltasten,Page Up/Page Downsowie=/-verschieben die Achsen der Halterung, solange die Taste gedrückt wird;BundPwechseln bei der steigenden Flanke zyklisch zwischen Basis und Preset. - C++ verwendet
std::getline(std::cin, ...)und eine kompakte Token-Grammatik (x+20,sx-5,u+10) — für fortlaufende Feinanpassungen weniger komfortabel, dafür ohne plattformspezifische#ifdef- Verzweigungen für Konsolen-APIs sind portierbar.
Quellcode
Tutorial 08 wird ebenfalls lokal mit dem SDK installiert – suchen Sie im Installationsverzeichnis des Dienstes unter tutorials/08-haply-inverse-http-remote-config/.
Verwandte Themen: Sitzungen – Fernsteuerung · Selektoren · Gerätekonfiguration · Basispermutation · Mount & Workspace · JSON-Konventionen · Tutorial 00 – Geräteliste · Tutorial 07 – Basis & Mount (WebSocket-Version)