08. Konfigurator für Remote-Sitzungen
Wir rekonstruieren eine Sitzung, die bereits an einem anderen Ort ausgeführt wird– beispielsweise in einer anderen App, einer Unity-Szene oder einer Demoim „Haply Hub “. Dies geschieht durch das Senden von HTTP-REST-Aufrufen an das Gerät. In diesem Tutorial werden keine WebSockets geöffnet. Wir verwenden ausschließlich GET-, POST- und DELETE-Aufrufe, um die Basis, die Arbeitsbereichsvoreinstellungen und die Befestigungstransformation zu ändern, während die andere App weiterhin Haptik-Effekte rendert.
Anwendungsfall
- Die laufende Demo live anpassen. Starten Sie zunächst die Orb-Demo im „ Haply Hub“ und führen Sie dann dieses Tutorial in einem zweiten Terminal aus. Wenn Sie die Sortierreihenfolge der Basiselemente umschalten, die Voreinstellungen des Arbeitsbereichs ändern oder die Befestigungstransformation feinjustieren, ändert sich der Koordinatenrahmen von Orb sofort, ohne dass die Demo angehalten werden muss.
- Anpassung des Arbeitsbereichs für jeden Benutzer. Während die Haptik-Szene weiterhin auf dem Hauptrechner ausgeführt wird, kann ein Bediener im selben Netzwerk
mountErmöglicht das Senden von Versatz, Drehung und Skalierung. Dadurch lässt sich der virtuelle Arbeitsbereich an den Schreibtisch des Benutzers anpassen. - Optionsmenü mit Geräteauswahl. Mit demselben HTTP-Helper
GET /devices(Tutorial 00 (siehe dort) aufrufen, um Geräte aufzulisten und ein interaktives Menü zu erstellen. Wählen Sie ein Gerät aus dem Menü aus und konfigurieren Sie es neu. Der WebSocket der Sitzung bleibt davon unberührt. Dieses Tutorial/sessionsabfragen*inverse/0ist fest codiert, aber/devicesUm ihn durch den Basis-Picker zu ersetzen, reicht eine lokale Änderung aus. - Automatisierung per Skript. Vor dem Start der Sitzungsaufzeichnung werden die Vorbereitungsschritte (Einstellungen für Basis, Voreinstellungen und Mount) automatisiert. Es ist nicht erforderlich, die Konfiguration auf jedem einzelnen Client zu installieren.
Voraussetzungen
In Tutorial 08 wirdeine bereits laufende Sitzungneu konfiguriert. Dazu ist eine aktive Haptik-Sitzung erforderlich. Dabei kann es sich um ein anderes Tutorial, eine Unity-Szene oder eine Demo aus dem „Haply -Hub“ handeln.
Haply Öffnen Sie Hub, starten Siedie Orb-Demo und wählen Sie diese dann 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 zeichnet eine Kugel im Arbeitsbereich des Geräts. Wenn Sie in Tutorial 08 zwischen Basis und Voreinstellungen wechseln oder die Befestigungstransformation feinjustieren, ändert sich das Koordinatensystem von „Orb“ in Echtzeit sichtbar.
Gebrauchsanweisung
# 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
Dieses Tutorial zeigt beim Start die aktuellen Werte für „basis“, „preset“ und „mount“ der Sitzung an und wartet anschließend auf Tasteneingaben. Bei jedem Tastendruck wirdgenau ein REST-Aufruf gesendet.
Sitzungen ohne Profilnamen können nur über eine numerische ID angegeben werden. Diese ID ändert sich bei jeder Ausführung. In der ersten Nachricht der Hauptanwendung session.configure.profile.name Wenn man so vorgeht, dass man … aufruft,--session :my_profile:0 Solche stabilen Selektoren können bei jeder Ausführung wiederverwendet werden. Weitere Informationen finden Sie unter Sitzung — Profilname Siehe dazu.
Tastenbelegung
- Python
- C++
| Schlüssel | Bedienung |
|---|---|
B | Die Permutationen von „basis“ nacheinander vertauschen |
P | Nacheinander zwischen den Arbeitsbereichsvoreinstellungen wechseln |
W / E / R | Montage-Bearbeitungsmodus auswählen — Position (mm) / Drehung (°) / Skalierung (%) |
← / → | Im aktuellen Modus in Richtung −X / +X bewegen |
↑ / ↓ | Im aktuellen Modus in Richtung +Y / −Y bewegen |
Page Up / Page Down | Im aktuellen Modus in Richtung +Z / −Z bewegen |
= / - | Alle drei Achsen gleichzeitig gleichmäßig vergrößern/verkleinern (±) (jederzeit verfügbar) |
Delete | DELETE Setzen Sie „basis“, „preset“ und „mount“ zurück, um die Standardeinstellungen des Geräts wiederherzustellen. |
H | Hilfe anzeigen |
Esc | Ende (Ctrl+C (ist aber auch möglich) |
Geben Sie die Daten zeilenweise ein. Drücken Sie nach der Eingabe die ENTER-Taste.
| Befehl | Bedienung |
|---|---|
b | Die Permutationen von „basis“ nacheinander durchgehen |
p | Arbeitsbereichsvoreinstellungen nacheinander umschalten |
w / e / r | Montage-Bearbeitungsmodus auswählen — Position (mm) / Drehung (°) / Skalierung (%) |
x+[N] … z-[N] | Die aktuelle Achse in Einheiten des aktiven Modus N Nur verschieben (x+ Wenn nur 5 angegeben ist, gilt der Standardwert 5) |
sx+[N] … sz-[N] | Verknüpfung für eine ungleichmäßige Skalierung auf einer Achse (in Prozent). Immer verfügbar |
u+[N] / u-[N] | Alle drei Achsen gleichzeitig gleichmäßig um ± N % vergrößern/verkleinern |
reset | DELETE „basis“, „preset“ und „mount“ zurücksetzen |
h | Hilfe anzeigen |
Drücken Sie Strg+C(oder Strg+D / EOF), um das Programm zu beenden.
HTTP-Methoden – GET, POST, DELETE
In diesem Tutorial werden nur drei der HTTP-Methoden verwendet. Alle Aufrufe erfolgen über die Standard- JSON-Envelope gibt zurück (bei Erfolg {"ok": true, "data": {...}}、im Falle eines Fehlers {"ok": false, "error": "..."}). Außerdem kann der Statuscode einer der folgenden drei Typen sein: 200 Erfolg,400 Ungültiges Anfrageformat,404 Es gibt keine Übereinstimmung mit dem Selektor.
| Methode | Rolle | Zu verwendender Pfad |
|---|---|---|
GET | Aktuellen Status abrufen – Sitzungsübersicht, Suche nach einer bestimmten Sitzung, aktuelle Einstellwerte | /sessions、/sessions/<selector>、/<device_selector>/config/{basis,preset,mount}?session=... |
POST | Einstellungen überschreiben — Der Hauptteil besteht aus JSON | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | Die Einstellungen auf die Werkseinstellungen des Geräts zurücksetzen | /<device_selector>/config/{basis,preset,mount}?session=... |
HTTP-Helfer
Hier wurden die drei oben genannten Methoden leicht umschlossen, damit der Rest des Tutorials als Kernlogik gelesen werden kann:
- Python
- C++ (nlohmann)
- C++ (Glaze)
In Python gilt für HTTP-Keep-Alive Folgendes: requests.Session() wird verwendet (Verringerung der Latenz pro Anfrage von ca. 50 ms auf ca. 5 ms):
http = requests.Session()
def api_get(path):
r = http.get(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None
def api_post(path, body):
r = http.post(f"{BASE_URL}{path}", json=body, timeout=3)
return r.json() if r.status_code == 200 else None
def api_delete(path):
r = http.delete(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None
def session_url(endpoint):
return f"{endpoint}?session={session_selector}"
libhv は requests::get / requests::post / requests::Delete veröffentlicht (D ist großgeschrieben.delete (da es sich um ein C++-Schlüsselwort handelt). Bei POST gilt:Content-Type: application/json Um dies einzurichten, müssen Sie die Anfrage manuell zusammenstellen:
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 ist immer {"ok", "data": T} mit einer Hülle. Alle GET-Anfragen mit Typangabe werden in eine einzige Vorlage eingeschlossen. Das Gleiche gilt für POST-Anfragen. HttpRequest unter Verwendung des Musters,glz::write_json wird wie folgt verarbeitet:
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;
}
Erkennung von Sitzungen — GET /sessions
--session Je nachdem, ob dies angegeben ist oder nicht, verzweigt sich die Verarbeitung:
--session SELECTORwenn dies angegeben wird →GET /sessions/<SELECTOR>Führen Sie dies einmal aus.200In diesem Fall wird das Ergebnis verwendet, und404In diesem Fall wird das Programm mit einem Fehler beendet.- Ohne Flag →
GET /sessions(Liste) ausführen → Sitzung mit Profilnamen anzeigen → Eingabe des Indexes anfordern → den endgültigen Selektor zusammenstellen (sofern verfügbar):profile:0hat Vorrang, alles andere#id(als Ausweichlösung).
SELECTOR は Selektor — Sitzungsselektor Akzeptiert alle dort definierten Formate: :profile:instance、#id、:-1、:0, nur den Profilnamen oder co.haply.hub::*:0 wie Platzhalter für Profilnamen Das ist das Muster. In diesem Tutorial wird die Zeichenkette unverändert übergeben, die Analyse erfolgt auf der Serviceseite.
- 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 Form der Antworten als Struktur modelliert, reflektiert Glaze diese automatisch:
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
Alle Aufrufe im Zusammenhang mit der Konfiguration werden gerätebezogen festgelegt. In diesem Tutorial wird der Selektor„Familien-Platzhalter + Index“ verwendet:
/*inverse/0/config/<key>
*inversejedes Gerät, das zur Inverse-Familie gehört (inverse3、inverse3x、minverse) entspricht. Dieses Tutorial funktioniert unverändert, unabhängig vom jeweiligen Modell.0ist der 0-basierte Index innerhalb dieser Familie. In diesem Tutorial wird immer nur die erste Inverse bearbeitet.
Um das Ziel zu ändern, muss lediglich eine Zeichenfolge geändert werden.--device Bitte geben Sie Folgendes ein (der Pfad wird daraus 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 Die Klasse dient der Indizierung vonvorherGilt für — *inverse:left/0 ist „die 0.“Linkshänder-Seriebedeutet „das Gegenteil von …“ und nicht „das Gegenteil von Nummer 0, also wahrscheinlich das linke“. Weitere Informationen finden Sie unter Selektor – Klassenfilter werden vor der Indizierung aufgelöst Siehe dazu.
Das Tutorial wird beim Start GET …/config/basis wird ausgegeben. In der Sitzung
--device Wenn kein passendes Gerät gefunden wird (z. B. bei einer Konfiguration, die nur für die rechte Hand vorgesehen ist) --device '*inverse:left/0' (falls angegeben), dann wird der Dienst 404 no device matches selector '<sel>' zurückgibt, und das Tutorial ist Device '<sel>' not found in session '<sel>'. ausgeben und
Status 1 und endet damit. Es wird nicht mit den alten Standardwerten in die Tastaturschleife eingetreten.
Was die vollständige Grammatik betrifft, soSelektor – Geräteauswahl Siehe dazu. Um anstelle einer festen Programmierung ein Menü zur Geräteauswahl zu erstellen,GET /devices?session=<selector>(Tutorial 00) eine Liste abrufen und die ausgewählte device_id wird in den Suchpfad aufgenommen.
POST-Konfiguration – Basis, Voreinstellung, Einbindung
Die drei Schlüssel haben das gleiche Anfrageformat, unterscheiden sich jedoch im Schema des Hauptteils. Alle POST-Anfragen geben den Ergebniswert data enthalten 200 gibt zurück. Wenn kein Eintrag im Sitzungs-/Geräte-Selektor übereinstimmt, 404 gibt 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});
}
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 gültige, normalisierte Transformation unverändert 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 sind sich gegenseitig ausschließendWenn eine Seite per POST gesendet wird, wird die andere Seite auf dem Gerät gelöscht. In diesem Tutorial wird dies nicht explizit nachverfolgt. Jeder POST-Vorgang ist unabhängig, und Konflikte werden vom Server gelöst. Eine Erläuterung derselben Regel aus Sicht von WebSocket finden Sie inTutorial 07.
Zurücksetzen mit DELETE — 3 Aufrufe
reset Es wird für jeden Einstellungsschlüssel einmal die Funktion DELETE aufgerufen. Bei jedem Aufruf wird der Wert, der nun den Standardwert angenommen hat, data enthalten 200 gibt 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"));
}
Zusammenstellung der Drehungen der Halterung
transform.rotation Bei der Datenübertragung handelt es sich um Einheitsquaternionen. In diesem Tutorial werden die Drehungen als Dreiergruppe von Z-Y-X-eigenen Eulerwinkeln (Nick um die X-Achse, Gieren um die Z-Achse, Rollen um die Y-Achse – alle in Grad) gespeichert, und bei jedem POST-Aufruf werden die Quaternionen neu 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,
};
}
Einheits-Hamilton-Quaternionen, rechtshändiges Koordinatensystem, Skalar zuerst (w). Es gelten dieselben Nutzungsbedingungen wie für die übrigen Bereiche des Dienstes. Weitere Informationen finden Sie unter quaternion Siehe dort. Die Synthesefolge lautetZ-Y-X intrinsisch(q = q_z * q_y * q_x) Das heißt: Zuerst wird die Neigung um die X-Achse angewendet, dann die Rollbewegung um die Y-Achse und schließlich die Gierbewegung um die Z-Achse.
Im Tutorial werden die für jede Statuszeile abgeleiteten Quaternionen zusammen mit einem Dreierpaar von Euler-Winkeln angezeigt, sodass Sie die Zusammensetzung überprüfen können, bevor sich das Gerät dreht. Der lokale Euler-Zustand wird unabhängig von den bereits in der Sitzung gespeicherten Werten (0, 0, 0) beginnt mit. Das erste mount POST Dadurch werden die bisherigen Werte überschrieben.
Eingabemodell (Übersicht)
Die HTTP-Verkabelung ist entscheidend, die Benutzererfahrung der Tastatur ist zweitrangig. Es gibt zwei Punkte, die bewusst vereinfacht wurden:
- Python は
keyboardIch verwende ein Paket. Es ist plattformübergreifend und verarbeitet die wiederholte Eingabe durch langes Drücken einer Taste nativ. Pfeiltasten,Page Up/Page Down、=/-bewegt die Befestigungsachse, solange die Taste gedrückt wird.BundPBei der steigenden Flanke werden nacheinander die Basis und die Voreinstellung umgeschaltet. - C++ は
std::getline(std::cin, ...)und eine prägnante Token-Grammatik (x+20、sx-5、u+10) verwenden. Auch wenn sich diese für kontinuierliche Feinabstimmungen nur schwer eignet, nutzen wir die Konsolen-APIs der jeweiligen Plattformen,#ifdefDa keine Verzweigungen erforderlich sind, ist das System portierbar.
Quelle
Auch Tutorial 08 wurde zusammen mit dem SDK lokal installiert. Es befindet sich im Installationsverzeichnis des Dienstes unter tutorials/08-haply-inverse-http-remote-config/ Bitte überprüfen Sie dies.
Siehe auch: Sitzung – Fernsteuerung · Selektor · Gerätekonfiguration · Basisersetzung · Mounts und Arbeitsbereiche · JSON-Konventionen · Tutorial 00 – Geräteliste · Tutorial 07 – Basis und Mounts (WebSocket-Version)