Zum Haupttext springen

08. Konfigurator für Remote-Sitzungen

Es rekonstruiert eine bereits an anderer Stelle ausgeführte Sitzung(z. B. in einer anderen App, einer Unity-Szene oder einer „Haply Hub“-Demo ). In diesem Tutorial wird kein WebSocket geöffnet; es werden lediglich HTTP-REST-Aufrufe (GET, POST, DELETE) an das Gerät gesendet. Sie können die Basis, die Arbeitsbereichsvoreinstellungen und die Befestigungstransformation ändern, während die andere App die haptische Darstellung fortsetzt.

Anwendungsbeispiele​

  • Echtzeitanpassung der laufenden Demo. Haply Starten Sie die Hub Orb-Demo und führen Sie anschließend dieses Tutorial in einem zweiten Terminal aus, um die Basispermutation zu ändern, die Arbeitsbereichsvoreinstellungen anzupassen oder die Mount-Transformation fein abzustimmen. Das Koordinatensystem von Orb wird sofort umgeschaltet, ohne dass die Demo angehalten werden muss.
  • Benutzerspezifische Kalibrierung des Arbeitsbereichs. Die haptische Szene wird auf dem Hauptrechner weiter ausgeführt, und ein Bediener im selben Netzwerk mount Stellen Sie sicher, dass Versatz, Drehung und Skalierung übertragen werden. Dadurch wird der virtuelle Arbeitsbereich an Ihren Schreibtisch angepasst.
  • Menü zur Geräteauswahl. Mit demselben HTTP-Helper GET /devices(Tutorial 00 Wenn Sie die Anweisung (Hinweis) ausführen, wird eine Liste der Geräte angezeigt und ein interaktives Menü erstellt. Wählen Sie ein Gerät aus und konfigurieren Sie es neu, ohne dabei den WebSocket der Sitzung zu beeinträchtigen. Dieses Tutorial behandelt /sessionsabfragen und *inverse/0wird fest codiert, aber /devices Der Wechsel zum Basis-Selektor ist über eine lokale Änderung möglich.
  • Skriptbasierte Konfiguration. Automatisiert die Vorbereitungsschritte (Einstellungen für „basis“, „preset“ und „mount“) vor Beginn der Sitzungsaufzeichnung. Die Einstellungen müssen nicht auf allen Clients übernommen werden.

Voraussetzungen​

In Tutorial 08 wird eine bereits laufende Sitzungneu konfiguriert. Dazu ist eine aktive haptische Sitzung erforderlich. Dies kann ein beliebiges anderes Tutorial, eine Unity-Szene oder eine Demo aus dem „ Haply -Hub“ sein.

Wie man eine Sitzung am schnellsten ausführt

Haply Öffnen Sieden Hub, starten Sie die Orb -Demo und wählen Sie anschließend diese Sitzung als Ziel aus:

./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

Die „Orb“-Szene rendert eine Kugel (sphere) im Geräte-Arbeitsbereich. Wenn Sie gemäß Tutorial 08 zwischen „basis“ und den Voreinstellungen wechseln oder die „mount“-Transformation feinjustieren, verschiebt sich das Koordinatensystem von „Orb“ in Echtzeit auf dem Bildschirm.

Anleitung​

# Pick a session interactively (lists every session the service knows)
./08-haply-inverse-http-remote-config
python 08-haply-inverse-http-remote-config.py

# Target the Haply Hub Orb demo directly
./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"

# Target one directly by selector
./08-haply-inverse-http-remote-config --session :my_profile:0
python 08-haply-inverse-http-remote-config.py --session "#42"

# Or by a wildcard profile pattern (first match) — handy when the exact profile is unknown
./08-haply-inverse-http-remote-config --session "co.haply.hub::*:0"

# Pick a specific device on the session (default is *inverse/0)
./08-haply-inverse-http-remote-config --device 'inverse3/#A12' # by exact id
./08-haply-inverse-http-remote-config --device '*inverse:left/0' # first left-handed inverse
./08-haply-inverse-http-remote-config --device '*inverse:right/0' # first right-handed inverse
./08-haply-inverse-http-remote-config --device '*verse_grip/0' # first grip-family device

Das Tutorial gibt zu Beginn die Werte für „basis“, „preset“ und „mount“ der aktuellen Sitzung aus und wartet anschließend auf Tasteneingaben. Mit jedem Tastendruck wird genau ein REST-Aufruf gesendet.

Profilnamen in der Simulation festlegen

Sitzungen ohne Profilnamen können nur mit einer numerischen ID bezeichnet werden, wobei sich diese ID bei jeder Ausführung ändert. Die Haupt-App erhält in der ersten Nachricht session.configure.profile.nameWenn man so aufruft, wird bei jeder Ausführung --session :my_profile:0Feste Selektoren wie diesen können wiederverwendet werden. Sitzung – ProfilnameSiehe dazu.

Tastenkombinationen​

SchlüsselBewegung
BBasis, Permutation, Zyklus
PWechseln zwischen Arbeitsbereichsvoreinstellungen
W / E / RAuswahl des Bearbeitungsmodus für die Halterung — Position (mm) / Drehung (°) / Vergrößerung (%)
← / →Im aktuellen Modus einen Schritt in Richtung −X / +X bewegen
↑ / ↓Im aktuellen Modus einen Schritt in Richtung +Y / −Y bewegen
Page Up / Page DownIm aktuellen Modus einen Schritt in Richtung +Z / −Z bewegen
= / -Gleichmäßige Anpassung des Vergrößerungsfaktors um ± auf allen drei Achsen gleichzeitig (jederzeit verfügbar)
DeleteBasis + Voreinstellung + Halterung DELETE Anwenden — Auf die Werkseinstellungen des Geräts zurücksetzen
HHilfe anzeigen
EscEnde (Ctrl+C(auch in Betrieb)

HTTP-Methoden – GET, POST, DELETE​

Das Tutorial verwendet nur drei HTTP-Methoden. Alle Aufrufe erfolgen nach dem Standard JSON-Envelope(Bei Erfolg {"ok": true, "data": {...}}, falls dies fehlschlägt {"ok": false, "error": "..."}) und der Statuscode ist einer der folgenden drei: 200 Erfolg, 400 Falsches Anfrageformat, 404 Es wurden keine Einträge gefunden, die dem Selektor entsprechen.

MethodeRolleAnwendungsweg
GETAktuellen Status abrufen – Sitzungsliste anzeigen, bestimmte Sitzung anzeigen, aktuelle Einstellungswerte anzeigen/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POSTEinstellungswerte ersetzen — Der Text ist im JSON-Format/<device_selector>/config/{basis,preset,mount}?session=...
DELETEEinstellungen auf die Werkseinstellungen zurücksetzen/<device_selector>/config/{basis,preset,mount}?session=...

HTTP-Helfer​

Ein schlanker Wrapper, der drei Methoden umschließt und es ermöglicht, den Code im Tutorial fortan mit Fokus auf die Geschäftslogik zu lesen:

Python unterstützt HTTP-Keep-Alive requests.Session()wird verwendet (wodurch die Latenzzeit pro Anfrage von etwa 50 ms auf etwa 5 ms reduziert wird):

http = requests.Session()

def api_get(path):
r = http.get(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def api_post(path, body):
r = http.post(f"{BASE_URL}{path}", json=body, timeout=3)
return r.json() if r.status_code == 200 else None

def api_delete(path):
r = http.delete(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None

def session_url(endpoint):
return f"{endpoint}?session={session_selector}"

Sitzungsnavigation — GET /sessions​

--session Je nach Option erfolgt eine Verzweigung:

  • --session SELECTORfalls angegeben → GET /sessions/<SELECTOR> Einmal aufrufen. 200dann die Ergebnisse verwenden und, 404Andernfalls wird eine Fehlermeldung ausgegeben und das Programm beendet.
  • Wenn kein Flag vorhanden ist → GET /sessions(Liste abrufen) → Sitzungen mit Profilnamen anzeigen → Indexabfrage → Endgültigen Selektor erstellen (sofern möglich :profile:0zuerst verwenden, und wenn nicht vorhanden, #idersetzt).

SELECTORist Selektor – SitzungsselektorEs werden alle dort definierten Formate zugelassen: :profile:instance, #id, :-1, :0, allgemeiner Profilname oder co.haply.hub::*:0wie Profilname-Platzhalter Es handelt sich um ein Muster. Das Tutorial übergibt diese Zeichenfolge unverändert, und die Auswertung übernimmt der Dienst.

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

Geräteauswahl — *inverse/0​

Jeder Aufruf einer Konfiguration gilt für ein einzelnes Gerät. Im Tutorial wird ein Selektor im Format „Produktfamilie-Platzhalter + Index“ verwendet:

/*inverse/0/config/<key>
  • *inversealle Geräte, die zur Inverse-Produktfamilie gehören (inverse3, inverse3x, minverse) – Das Tutorial funktioniert unabhängig vom jeweiligen Modell ohne Anpassungen.
  • 0ist ein Index innerhalb dieser Produktgruppe, der bei 0 beginnt – im Tutorial wird stets nur die erste Inverse verwendet.

Um das Zielgerät zu ändern, muss lediglich eine Zeichenfolge geändert werden — --deviceBitte geben Sie Folgendes an (der Pfad wird aus diesem Wert zusammengestellt):

/verse_grip/0/config/basis?session=... # --device verse_grip/0
/*verse_grip/0/config/basis?session=... # --device '*verse_grip/0' — first grip, any kind
/inverse3/A14/config/mount?session=... # --device 'inverse3/#A14'
/*inverse:left/0/config/basis?session=... # --device '*inverse:left/0' — first left-handed inverse
/*inverse:right/0/config/basis?session=... # --device '*inverse:right/0' — first right-handed inverse

:left / :right Klassen sind indiziert Zuvor Gilt für — *inverse:left/0ist „die 0.“ Linkshänder „Inverse“ und nicht „die 0. Inverse, die vermutlich links liegt“. Weitere Informationen finden Sie unter Selektor – Auswertung des Klassenfilters vor der IndizierungSiehe dazu.

Wenn kein Zielgerät vorhanden ist, wird Tutorial 08 beendet.

Das Tutorial beginnt zu Beginn GET …/config/basisbitte. In der Sitzung --deviceWenn kein passendes Gerät gefunden wird (z. B. bei einer Einstellung ausschließlich für Rechtshänder --device '*inverse:left/0'(bei Verwendung von), bietet der Dienst 404 no device matches selector '<sel>'antwortete, und das Tutorial Device '<sel>' not found in session '<sel>'.nach der Ausgabe von … im Beendigungszustand 1… endet. Es wird nicht in die Tastaturschleife gewechselt, wie es bei den alten Standardwerten der Fall war.

Die gesamte Grammatik lautet Selektor — GeräteselektorSiehe hier. Um anstelle einer festen Angabe ein Menü zur Geräteauswahl zu erstellen, GET /devices?session=<selector>(Tutorial 00) die Geräte auflisten und das ausgewählte device_idVerbinden Sie es mit dem festgelegten Pfad.

POST-Einstellungen – Basis, Voreinstellung, Einbau​

Die drei Schlüssel haben zwar dasselbe Anfrageformat, unterscheiden sich jedoch im Schema des Hauptteils. Alle POST-Anfragen liefern einen Rückgabewert datadarin 200zurück und wenn kein Eintrag gefunden wird, der dem Sitzungs- oder Geräteselektor entspricht, 404gibt zurück.

Grundlage​

POST /*inverse/0/config/basis?session=:my_profile:0
Content-Type: application/json

{"permutation": "XZY"}

Antwort: {"ok": true, "data": {"permutation": "XZY"}}

def post_basis():
perm, _ = BASIS_OPTIONS[basis_index]
api_post(session_url("/inverse3/0/config/basis"), {"permutation": perm})

Voreinstellungen​

POST /*inverse/0/config/preset?session=:my_profile:0
Content-Type: application/json

{"preset": "arm_front_centered"}

Antwort: {"ok": true, "data": {"preset": "arm_front_centered"}}

def post_preset():
preset = PRESET_OPTIONS[preset_index]
api_post(session_url("/inverse3/0/config/preset"), {"preset": preset})

Halterung​

POST /*inverse/0/config/mount?session=:my_profile:0
Content-Type: application/json

{
"transform": {
"position": {"x": 0.02, "y": 0.0, "z": 0.0},
"rotation": {"w": 0.966, "x": 0.0, "y": 0.259, "z": 0.0},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0}
}
}

Antwort: {"ok": true, "data": {"transform": { ... }}} — Die gültige Transformation nach der Normalisierung wird unverändert zurückgegeben.

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)
mountWow presetsind sich gegenseitig ausschließend

Wenn auf einer Seite ein POST-Request gesendet wird, werden die Einstellungen auf der anderen Seite vom Gerät gelöscht. Das Tutorial geht darauf nicht ausdrücklich ein – jeder POST-Request ist unabhängig, und Konflikte werden vom Server gelöst. Die entsprechenden Regeln aus Sicht von WebSocket finden Sie in Tutorial 07.

DELETE-Initialisierung – drei Aufrufe​

resetruft für jeden Einstellungsschlüssel einmal DELETE auf. Jeder Aufruf dataund füge nun den Wert ein, der nun als Standardwert gilt 200gibt zurück.

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"))

Konfiguration der Halterungsdrehung​

transform.rotationEs handelt sich um Einheitsquaternionen im Übertragungsformat. Im Tutorial werden Drehungen als intrinsische Euler-Tripel im Z-Y-X-Koordinatensystem gespeichert (Pitch um die X-Achse, Yaw um die Z-Achse, Roll um die Y-Achse – jeweils in Grad). Außerdem wird das Quaternion bei jedem POST neu berechnet.

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,
}
Quaternion-Konvention

Einheits-Hamilton-Quaternion, Rechtshänder-Koordinatensystem, Skalar-Priorität (w) Dies ist das Verfahren. Es handelt sich um dasselbe Protokoll wie für den restlichen Teil des Dienstes, und quaternionSiehe dort. Die Synthesefolge lautet Z-Y-X – intrinsisch Verfahren (q = q_z * q_y * q_x). Das heißt, zunächst wird die Neigung (Pitch) um die X-Achse, anschließend die Rollbewegung (Roll) um die Y-Achse und schließlich die Gierbewegung (Yaw) um die Z-Achse angewendet.

Da das Tutorial für jede Statusleiste die abgeleiteten Quaternionen zusammen mit den Euler-Winkelwerten ausgibt, können Sie die Ergebnisse der Bildkomposition überprüfen, bevor sich das Gerät dreht. Der lokale Euler-Zustand ist unabhängig von den bereits in der Sitzung gespeicherten Werten (0, 0, 0)Es beginnt mit. Das erste mount POSTüberschreibt den bisherigen Wert.

Eingabemodell (kurz gefasst)​

Die Struktur der HTTP-Verbindung steht im Mittelpunkt, die Tastatur-UX ist zweitrangig. Wir wenden die folgenden zwei bewussten Vereinfachungsansätze an.

  • Python In der Umsetzung gilt: keyboard Es wird ein Paket verwendet. Es ist plattformübergreifend und verarbeitet standardmäßig die Wiederholung von Tastenanschlägen, wenn eine Taste gedrückt gehalten wird. Pfeiltasten, Page Up / Page Down, = / - Solange die Taste gedrückt gehalten wird, bewegt sich die Befestigungsachse, und B sowie P Der Schlüssel wechselt bei der steigenden Flanke (rising edge) zwischen Basis und Voreinstellung hin und her.
  • C++ In der Umsetzung gilt: std::getline(std::cin, ...)und eine prägnante Token-Syntax (x+20, sx-5, u+10) wird verwendet. Dies ist zwar für fortlaufende Feinabstimmungen weniger praktisch, aber für plattformspezifische Konsolen-APIs #ifdefEine Transplantation ist auch ohne dessen Verwendung möglich.

Quelle​

Zusammen mit dem SDK-Installationsprogramm bereitgestellt

Auch Tutorial 08 wird zusammen mit dem SDK lokal installiert. Im Installationsverzeichnis des Dienstes unter tutorials/08-haply-inverse-http-remote-config/ Bitte überprüfen Sie die Route.

Verwandte Dokumente: Sitzung – Fernsteuerung · Selektor · Gerätekonfiguration · Basispermutation · Einbindungen und Arbeitsbereiche · JSON-Konvention · Tutorial 00 – Geräteliste · Tutorial 07 – Basis und Einbindungen (WebSocket-Version)