Zum Hauptinhalt springen
Version: 3.5.x

08. Konfigurator für Fernsitzungen

Konfigurieren Sie eine Sitzung neu , die bereits an anderer Stelle läuft – in einer anderen App, einer Unity-Szene oder einer Haply –, indem Sie HTTP-REST-Aufrufe an das entsprechende Gerät senden. In diesem Tutorial wird kein WebSocket geöffnet: Es werden lediglich GET-, POST- und DELETE-Anfragen verwendet, um die Basis, die Arbeitsbereichsvoreinstellung oder die Mount-Transformation zu ändern, während die andere App weiterhin haptische Rückmeldungen rendert.

Anwendungsfälle

  • Nimm live Anpassungen an einer laufenden Demo vor. Starte die Haply Orb-Demo und führe dieses Tutorial anschließend in einem zweiten Terminal aus, um die Basispermutation zu tauschen, die Arbeitsbereichsvoreinstellung zu ändern oder die Befestigungstransformation leicht zu verschieben – das Koordinatensystem des Orbs verschiebt sich sofort, ohne dass die Demo angehalten wird.
  • Kalibrierung des Arbeitsbereichs pro Benutzer. Lassen Sie die haptische Anwendung auf dem Hauptrechner laufen und lassen Sie einen Bediener im selben Netzwerk einen mount Verschieben / Drehen / Skalieren, damit der virtuelle Arbeitsbereich mit dem Schreibtisch des Benutzers übereinstimmt.
  • Optionsmenü mit Geräteauswahl. Die gleichen HTTP-Helfer können Abfragen durchführen GET /devices (siehe Anleitung 00) um Geräte aufzulisten und ein interaktives Menü zu erstellen – ein Gerät auswählen und es dann neu konfigurieren –, ohne den WebSocket der Sitzung zu berühren. Das Tutorial fragt ab /sessions und fest codiert *inverse/0, aber der Wechsel zu einem /devicesDer -gesteuerte Picker ist eine lokale Änderung.
  • Skriptgesteuerte Neukonfiguration. Automatisieren Sie Vorbereitungsschritte (Basis einstellen + Voreinstellung + Einbinden) vor Beginn der Aufzeichnung einer Sitzung, ohne die Konfiguration in jeden Client einbetten zu müssen.

Voraussetzungen

In Tutorial 08 wird eine bereits laufende Sitzung neu konfiguriert. Du benötigst eine beliebige aktive haptische Sitzung – ein anderes Tutorial, eine Unity-Szene oder eine Haply Demo.

Der schnellste Weg, eine Sitzung zu starten

Öffnen Sie Haply und starten Sie die Orb -Demo, und wählen Sie sie 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 rendert eine Kugel im Arbeitsbereich des Geräts. Durch das Durchlaufen der Basis- und Voreinstellungsoptionen oder das Verschieben der Halterungstransformation mit Tutorial 08 wird das Koordinatensystem des „Orb“ in Echtzeit visuell verschoben.

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"

Das Tutorial gibt beim Start die aktuelle Basis, das Preset und den Mount der Sitzung aus und wartet anschließend 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 anhand ihrer numerischen ID angesprochen werden – diese ändert sich bei jedem Durchlauf. Lassen Sie Ihre Hauptanwendung folgenden Aufruf ausführen: session.configure.profile.name bei seiner ersten Nachricht, und du kannst einen stabilen Selektor wie --session :my_profile:0 bei jedem Durchlauf. Siehe Sitzungen – Profilname.

Tastenkombinationen

SchlüsselAktion
BZyklusbasierte Permutation
PVoreinstellung für den Arbeitsbereich „Cycle“
W / E / RBearbeitungsmodus für Halterung auswählen – Position (mm) / Drehung (°) / Skalierung (%)
/ Schritt −X / +X im aktuellen Modus
/ Schritt +Y / −Y im aktuellen Modus
Page Up / Page DownSchritt +Z / −Z im aktuellen Modus
= / -Einheitliche Skalierung ± auf allen drei Achsen gleichzeitig (immer verfügbar)
DeleteDELETE Basis + Voreinstellung + Befestigung – auf Geräte-Standardeinstellungen zurücksetzen
HHilfe anzeigen
EscBeenden (Ctrl+C (funktioniert auch)

HTTP-Verben – GET, POST, DELETE

Das Tutorial verwendet drei HTTP-Verben – und zwar genau diese drei. Jeder Aufruf gibt den Standardwert zurück JSON-Umschlag ({"ok": true, "data": {...}} bei Erfolg, {"ok": false, "error": "..."} (bei einem Fehler) und einen von drei Statuscodes: 200 Erfolg, 400 fehlerhafte Anfrage, 404 Der Selektor hat keine Übereinstimmung gefunden.

VerbRolleVerwendete Pfade
GETAktuellen Status abrufen – Liste der Sitzungen, gezielte Suche nach Sitzungen, aktuelle Konfigurationswerte/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POSTEinen Konfigurationswert ersetzen – der Textkörper ist JSON/<device_selector>/config/{basis,preset,mount}?session=...
DELETEEinen Konfigurationswert auf die Geräte-Standardeinstellung zurücksetzen/<device_selector>/config/{basis,preset,mount}?session=...

HTTP-Hilfsfunktionen

Eine schlanke Hülle um die drei Verben, damit sich der Rest des Tutorials wie Geschäftslogik liest:

Anwendungsbereiche von Python requests.Session() für HTTP-Keep-Alive (reduziert die 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}"

Sitzungserkennung – GET /sessions

Zweige auf --session:

  • --session SELECTOR angesichts → eins GET /sessions/<SELECTOR>. 200 → benutze es; 404 → Fehlermeldung.
  • Keine FlaggeGET /sessions (Auflistung) → Sitzungen mit Profilnamen darstellen → nach einem Index fragen → den endgültigen Selektor erstellen (bevorzugt :profile:0 sofern verfügbar; andernfalls auf #id).

SELECTOR akzeptiert jede in Selektoren – Sitzungsselektor: :profile:instance, #id, :-1, :0, einfacher Profilname oder ein Profilname (Platzhalter) ähnlich wie co.haply.hub::*:0. Das Tutorial leitet die Zeichenfolge unverändert weiter; der Dienst wertet sie aus.

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 Familien-Platzhalter in Verbindung mit einem Indexselektor:

/*inverse/0/config/<key>
  • *inverse passt zu jedem Gerät der Inverse-Familie (inverse3, inverse3x, minverse) – Das Tutorial funktioniert unabhängig vom jeweiligen Modell unverändert.
  • 0 ist der 0-basierte Index dieser Familie – im Tutorial wird immer nur die erste Inverse behandelt.

Das Retargeting erfolgt durch eine einzige Zeichenfolgenänderung:

/verse_grip/0/config/basis?session=... # target first wired VerseGrip
/*verse_grip/*/config/basis?session=... # target every grip, wired + wireless
/inverse3/A14/config/mount?session=... # target Inverse3 with id A14

Siehe Selektoren – Geräteselektor für die vollständige Syntax. Um ein Menü zur Geräteauswahl zu erstellen, anstatt die Werte fest zu programmieren, führen Sie eine Aufzählung mit GET /devices?session=<selector> (Anleitung 00) und den ausgewählten device_id in die Konfigurationspfade.

POST-Konfiguration – Basis, Voreinstellung, Einbindung

Drei Schlüssel, gleiche Anfrageform, unterschiedliches Body-Schema. Jeder POST-Aufruf gibt eine 200 mit dem resultierenden Wert in data, oder 404 falls der Sitzungs-/Geräte-Selektor keine Übereinstimmung gefunden hat.

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

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

Das Senden einer Anfrage löscht die andere auf dem Gerät. Das Tutorial geht darauf nicht ausdrücklich ein – jede POST-Anfrage ist in sich geschlossen, und der Server löst den Konflikt. Siehe Tutorial 07 für dieselbe Regel auf der WebSocket-Seite.

DELETE reset — drei Aufrufe

reset führt pro Konfigurationsschlüssel einen DELETE-Befehl aus. Jeder gibt 200 mit dem nun als Standardwert festgelegten 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"))

Die Drehung des Reittiers festlegen

transform.rotation ist ein Einheitsquaternion im Speicher. Das Tutorial speichert die Drehung als intrinsisches Euler-Tripel Z-Y-X (Neigung um die X-Achse, Gierung um die Z-Achse, Rollbewegung um die Y-Achse – alle Winkel) und bildet das Quaternion bei jedem POST neu.

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

Hamilton-Quaternion, rechtshändig, Skalar zuerst (w) – gleiche Konvention wie im restlichen Dienst, siehe quaternion. Die Reihenfolge der Kompositionen ist Z-Y-X intrinsisch (q = q_z * q_y * q_x): Zuerst die Neigung um die X-Achse, dann die Rollbewegung um die Y-Achse und schließlich die Gierbewegung um die Z-Achse.

Das Tutorial gibt in jeder Statuszeile das abgeleitete Quaternion zusammen mit dem Euler-Tupel aus, sodass Sie die Zusammensetzung ü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 – das Erste mount POST überschreibt alles, was vorher da war.

Eingabemodell (kurz)

Das HTTP-Verhalten ist entscheidend; die Benutzererfahrung der Tastatur ist zweitrangig. Zwei bewusste Abstriche:

  • Python verwendet die keyboard Paket – plattformübergreifend, unterstützt die Wiederholung bei gedrückter Taste nativ. Pfeiltasten, Page Up / Page Downund = / - die Achsen der Halterung verschieben, während sie festgehalten werden; B und P pro Zyklus und bei steigender Flanke voreingestellt.
  • C++ Verwendungszwecke std::getline(std::cin, ...) und eine kompakte Token-Grammatik (x+20, sx-5, u+10) – weniger ergonomisch für ständige Anpassungen, aber ohne #ifdef- plattformspezifische Konsolen-APIs.

Quelle

Im Lieferumfang des SDK-Installationsprogramms enthalten

Tutorial 08 wird ebenfalls lokal mit dem SDK installiert – schau mal unter tutorials/08-haply-inverse-http-remote-config/ im Installationsverzeichnis des Dienstes.

Verwandte Themen: Sitzungen – Fernsteuerung · Selektoren · Gerätekonfiguration · Basis-Permutation · Mount & Arbeitsbereich · JSON-Konventionen · Tutorial 00 – Geräteliste · Tutorial 07 – Basis & Mount (WebSocket-Version)