Zum Hauptinhalt springen

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 /devices abfragen (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 /sessions ab und fest codiert *inverse/0; der Wechsel zu einer über /devices Bei 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“.

Der schnellste Weg, eine Sitzung zu starten

Ö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.

Legen Sie in Ihrer Simulation einen Profilnamen fest

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​

GeschmackAktion
BBasis-Permutation durchschalten
PArbeitsbereich-Voreinstellung aktivieren
W / E / RMontage-Bearbeitungsmodus auswählen — Position (mm) / Drehung (°) / Skalierung (%)
← / →Im aktuellen Modus einen Schritt −X / +X
↑ / ↓Im aktuellen Modus einen Schritt +Y / −Y
Page Up / Page DownIm aktuellen Modus einen Schritt +Z / −Z
= / -Gleichmäßige Skalierung ± auf allen drei Achsen gleichzeitig (stets verfügbar)
DeleteDELETE für Basis + Preset + Mount — Auf die Werkseinstellungen zurücksetzen
HHilfe anzeigen
EscBeenden (Ctrl+C (das funktioniert ebenfalls)

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.

MethodeZweckVerwendete Pfade
GETAktuellen Status abrufen — Sitzungsliste, gezielte Sitzungsabfrage, aktuelle Konfigurationswerte/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POSTEinen Konfigurationswert ersetzen — Der Request-Body ist JSON/<device_selector>/config/{basis,preset,mount}?session=...
DELETEEinen 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 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}"

Sitzungsermittlung — GET /sessions​

Verzweigt anhand von --session:

  • --session SELECTOR angegeben → ein GET /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.

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 Konfigurationsaufruf ist auf ein Gerät beschränkt. Das Tutorial verwendet einen Selektor aus Familien-Wildcard + Index:

/*inverse/0/config/<key>
  • *inverse passt auf jedes Gerät der Inverse-Familie (inverse3, inverse3x, minverse) — Das Tutorial funktioniert unverändert, unabhängig vom jeweiligen Modell.
  • 0 ist 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.

Tutorial 08 wird beendet, wenn das betreffende Gerät fehlt

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

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

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

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": { ... }}} — gibt die effektive Transformation nach der Normalisierung zurück.

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)
mount und preset schließen sich gegenseitig aus

Ein 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.

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

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.

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

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 Down sowie = / - verschieben die Achsen der Halterung, solange die Taste gedrückt wird; B und P wechseln 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​

Im SDK-Installationsprogramm enthalten

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)