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"

# 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 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 Flagge → GET /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.

Beim Retargeting handelt es sich um eine Änderung an einer Zeichenkette – bestanden --device (der Pfad wird daraus gebildet):

/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 :left / :right Klasse wird angewendet vorher Indizierung — *inverse:left/0 ist „der 0.-te linkshändig „Inverse“, nicht „die 0-te Inverse, hoffentlich links“. Siehe Selektoren – Klassenfilter werden vor der Indizierung ausgewertet.

Tutorial 08 wird beendet, wenn das Zielgerät nicht vorhanden ist

Das Tutorial gibt eine GET …/config/basis beim Start. Wenn die Sitzung kein entsprechendes Gerät hat --device (z. B. --device '*inverse:left/0' im Gegensatz zu einer Konfiguration, die ausschließlich für Rechtshänder ausgelegt ist), antwortet der Dienst 404 no device matches selector '<sel>'; das Tutorial gibt Folgendes aus Device '<sel>' not found in session '<sel>'. und beendet sich mit dem Status 1. Es wird keine Tastaturschleife mit veralteten Standardwerten aufgerufen.

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)