Zum Hauptinhalt springen

08. Konfigurator für Fernsitzungen

Umeine Sitzung, die bereits an anderer Stelle ausgeführt wird– beispielsweise in einer anderen Anwendung, einer Unity-Szene oder einer „Haply Hub“-Demo –, neu zu konfigurieren, müssen Sie einen HTTP-REST-Aufruf an das entsprechende Gerät senden. In diesem Tutorial werden keine WebSockets geöffnet: Mit GET-, POST- und DELETE-Anfragen können Sie die Basis, die Arbeitsbereichsvoreinstellungen oder die Mount-Transformation ändern, während das haptische Feedback in der anderen Anwendung weiterhin gerendert wird.

Anwendungsszenarien​

  • Führen Sie in Echtzeit Feinabstimmungen an der laufenden Demo durch. Starten Sie die „ Haply Hub Orb“-Demo und führen Sie dieses Tutorial in einem zweiten Terminal aus, um die Verschiebung der Basisachsen umzuschalten, die Arbeitsbereichsvoreinstellungen zu ändern oder die „mount“-Transformation fein abzustimmen – das Koordinatensystem von Orb passt sich sofort an, ohne dass die Demo angehalten werden muss.
  • Kalibrierung des Arbeitsbereichs pro Benutzer. Die haptischen Szenarien sollen auf dem Host weiterlaufen und es den Bedienern im selben Netzwerk ermöglichen, Daten zu übertragen mount Verschiebung / Drehung / Skalierung, um den virtuellen Arbeitsbereich an den Schreibtisch des Benutzers anzupassen.
  • Optionsmenü mit Geräteauswahl. Die gleichen HTTP-Hilfsfunktionen können aufgerufen werden GET /devices(siehe Tutorial 00) um Geräte aufzulisten und ein interaktives Menü zu erstellen – nach Auswahl eines Geräts kann dieses neu konfiguriert werden –, ohne den WebSocket in der Sitzung zu berühren. In diesem Tutorial wird Folgendes abgefragt: /sessions und fest codiert *inverse/0, aber stattdessen wurde auf /devices Die Auswahl des Treibers ist lediglich eine lokale Änderung.
  • Skriptgesteuerte Neukonfiguration. Automatische Ausführung der Vorbereitungsschritte (Einrichtung von „basis“ + Voreinstellungen + Einbindung) vor Beginn der Aufzeichnung einer Sitzung, sodass diese Einstellungen nicht in jedem Client einzeln vorgenommen werden müssen.

Voraussetzungen​

Tutorial 08 dient zur Neukonfigurationeiner bereits laufenden Sitzung. Du benötigst eine aktive Haptic-Sitzung – entweder ein anderes Tutorial, eine Unity-Szene oder eine „ Haply -Hub“-Demo.

Der schnellste Weg, eine Sitzung zu starten

Öffnen Sie den „Haply Hub“, startenSie die Orb-Demo und legen Sie diese direkt als Ziel fest:

./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 im Tutorial 08 zwischen „basis“, den Voreinstellungen oder der Feinabstimmung der „mount“-Transformation hin- und herwechseln, verschiebt sich das Koordinatensystem von „Orb“ in Echtzeit visuell.

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 aktuellen Werte für „basis“, „preset“ und „mount“ der Sitzung aus und wartet dann auf Tasteneingaben – jede Tasteneingabe löst genaueinen REST-Aufruf aus.

Den Namen des Profils in der Simulation festlegen

Sitzungen, für die kein Profilname festgelegt wurde, können nur über eine numerische ID angegeben werden – wobei sich diese ID bei jeder Ausführung ändert. Bitte lassen Sie die Hauptanwendung in ihrer ersten Nachricht Folgendes aufrufen: session.configure.profile.name, danach kann man bei jedem Durchlauf den gleichen Selektor wiederverwenden, zum Beispiel --session :my_profile:0。Siehe Sitzung — Profilname。

Tastenbelegung​

TasteBedienung
BZykluswechsel – Basiswechsel
PZyklische Umschaltung zwischen Arbeitsbereichsvoreinstellungen
W / E / RBefestigungsmodus auswählen – Position (mm) / Drehung (°) / Skalierung (%)
← / →Im aktuellen Modus entlang −X / +X schreiten
↑ / ↓Im aktuellen Modus entlang der +Y-/−Y-Achse schreiten
Page Up / Page DownIm aktuellen Modus entlang +Z / −Z schreiten
= / -Gleichzeitige einheitliche Skalierung aller drei Achsen ± (immer verfügbar)
DeleteDELETE Basis + Voreinstellung + Halterung — Auf die Werkseinstellungen zurücksetzen
HHilfe anzeigen
EscBeenden (Ctrl+C (Das geht auch.)

HTTP-Verben – GET, POST, DELETE​

In diesem Tutorial werden die drei HTTP-Verben verwendet – und zwar ausschließlich diese drei. Jeder Aufruf gibt einen Standardwert zurück. JSON-Antwortformat(im Erfolgsfall) {"ok": true, "data": {...}}, im Fehlerfall {"ok": false, "error": "..."}), und gibt einen der folgenden drei Statuscodes zurück:200 Erfolg,400 Fehler im Anfrageformat,404 Der Selektor hat keine Treffer gefunden.

VerbVerwendungszweckVerwendete Pfade
GETAktuellen Status abrufen – Sitzungsliste, Abfrage einer bestimmten Sitzung, aktuelle Konfigurationswerte/sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=...
POSTKonfigurationswerte ersetzen – Anfragetext im JSON-Format/<device_selector>/config/{basis,preset,mount}?session=...
DELETEDie Konfigurationswerte auf die Werkseinstellungen zurücksetzen/<device_selector>/config/{basis,preset,mount}?session=...

HTTP-Hilfsfunktionen​

Dies ist eine schlanke Kapselung der drei oben genannten Verben, wodurch sich der Rest dieses Tutorials eher wie Geschäftslogik liest:

Verwendung von Python requests.Session() Realisierung der Wiederverwendung von HTTP-Dauerverbindungen (wodurch die Latenz pro Anfrage von ca. 50 ms auf ca. 5 ms gesenkt werden kann):

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​

gemäß --session Die Parameter bestimmen den Ausführungsweg:

  • Es wurden --session SELECTOR → Einmal senden GET /sessions/<SELECTOR>。200 → Direkt verwenden;404 → Mit Fehlermeldung beenden.
  • Es wurden keine Parameter angegeben. → GET /sessions(Liste) → Sitzungen mit Profilnamen anzeigen → Eingabe eines Indexes anfordern → Endgültigen Selektor erstellen (vorrangig verwenden, sofern verfügbar) :profile:0, andernfalls wird auf #id).

SELECTOR Unterstützung Selektor – Sitzungsselektor Alle darin definierten Formen::profile:instance、#id、:-1、:0、reiner Profilname oder in der Form co.haply.hub::*:0 的 Profil-Platzhalter für Namen Muster. In diesem Tutorial wird diese Zeichenfolge unverändert weitergeleitet; die Auswertung erfolgt auf dem Server.

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 bestimmtes Gerät beschränkt. In diesem Tutorial werden Selektoren inder Form „Serienplatzhalter + Index“verwendet:

/*inverse/0/config/<key>
  • *inverse Kompatibel mit beliebigen Geräten der Inverse-Serie (inverse3、inverse3x、minverse) — Unabhängig vom konkreten Modell lässt sich diese Anleitung ohne Änderungen ausführen.
  • 0 Dies ist der Index dieser Serie, der bei 0 beginnt – in diesem Tutorial wird nur das erste Inverse-Gerät behandelt.

Um das Zielgerät zu wechseln, muss lediglich eine Zeichenfolge geändert werden – übergeben --device Parameter (der Pfad wird anhand dieses Parameters generiert):

/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 im IndexZuvorIn Kraft treten – „—“*inverse:left/0„bedeutet ‚der 0.‘“linke Hand „inverse“ und nicht „das 0. inverse, hoffentlich das linke“. Siehe Selektoren – Klassenfilter werden vor der Indizierung ausgewertet。

Tutorial 08: Beenden, wenn das Zielgerät fehlt

Das Tutorial gibt beim Start eine Meldung aus GET …/config/basis Anfrage. Falls in der Sitzung keine mit --device Passende Geräte (z. B. bei Verwendung in einer Umgebung, in der nur rechtshändige Geräte vorhanden sind) --device '*inverse:left/0'), gibt der Dienst Folgendes zurück: 404 no device matches selector '<sel>'; Die Anleitung wird ausgedruckt Device '<sel>' not found in session '<sel>'. und mit dem Status 1 Beenden. Es wird nicht mit veralteten Standardwerten in den Tastaturzyklus eingetreten.

Die vollständige Syntax finden Sie unter Selektor – Geräteauswahl。Um das Geräteauswahlmenü zu erstellen, anstatt es fest zu programmieren, verwenden Sie GET /devices?session=<selector>(Tutorial 00) Geräte auflisten und das ausgewählte device_id Geben Sie den Konfigurationspfad ein.

POST-Konfiguration — basis, preset, mount​

Die drei Anfragen haben denselben Aufbau, unterscheiden sich jedoch in der Struktur des Request-Körpers. Jede POST-Anfrage gibt Folgendes zurück: 200, und außerdem data enthält den Ergebniswert; wenn der Sitzungs- oder Geräteselektor kein Objekt findet, wird 404。

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": { ... }}} — Eine Transformation, die erst nach der Normalisierung der Rückmeldung wirksam wird.

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 Exklusivität

Die Ausführung eines POST-Aufrufs für eine der beiden Optionen löscht die andere auf dem Gerät. In diesem Tutorial wird dies nicht ausdrücklich behandelt – jeder POST-Aufruf ist eigenständig, und die Lösung von Konflikten obliegt dem Server. Eine Erläuterung derselben Regel aus Sicht der WebSocket-Seite finden Sie in Tutorial 07.

DELETE Zurücksetzen — dreimaliger Aufruf​

reset Für jeden Konfigurationsposten wird jeweils eine DELETE-Anfrage gesendet. Bei jeder Anfrage wird Folgendes zurückgegeben: 200, und außerdem data Enthält die wiederhergestellten Standardwerte.

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

Kombiniertes Aufhängen und Drehen​

Bei der Übertragung,transform.rotation Es handelt sich um eine Quaternion-Einheit. In diesem Tutorial werden die Drehungen als Z-Y-X-Tupel von Euler-Winkeln (Pitch um die X-Achse, Yaw um die Z-Achse, Roll um die Y-Achse, jeweils in Grad) gespeichert und bei jedem POST wieder zu einer Quaternion zusammengesetzt.

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,
}
Konventionen für Quaternionen

Einheit: Hamilton-Quaternionen, Rechtshandregel, Skalar vorne (w) – In Übereinstimmung mit den Vereinbarungen in anderen Teilen des Dienstes, siehe quaternion。Die Reihenfolge der Kombinationen lautet Z-Y-X Innenrotation (intrinsisch)(q = q_z * q_y * q_x):Zuerst wird eine Nickbewegung (Pitch) um die X-Achse, dann eine Rollbewegung (Roll) um die Y-Achse und schließlich eine Gierbewegung (Yaw) um die Z-Achse ausgeführt.

Das Tutorial gibt in jeder Statuszeile sowohl die abgeleitete Quaternion als auch das Eulerwinkel-Tupel aus, damit Sie die Ergebnisse der Kombination überprüfen können, bevor sich das Gerät dreht. Unabhängig davon, welche Werte in der Sitzung bereits vorhanden sind, beginnt der lokale Eulerwinkel-Status bei (0, 0, 0) Los geht’s – das erste Mal mount POST Der ursprüngliche Wert wird überschrieben.

Eingabemodell (Kurzbeschreibung)​

Die Verbindungslogik von HTTP steht im Vordergrund, die Tastaturinteraktion ist nur zweitrangig. Hier gibt es zwei bewusst vorgenommene Vereinfachungen:

  • Python Verwendung keyboard Paket – plattformübergreifend, native Verarbeitung von wiederholten Langdruck-Eingaben. Pfeiltasten,Page Up / Page Down sowie = / - Die Befestigungsachse wird während des Gedrückthaltens schrittweise verschoben;B und P Wechselt bei der steigenden Flanke zwischen Basis und Voreinstellung hin und her.
  • C++ Verwendung std::getline(std::cin, ...) und die kompakte Token-Syntax (x+20、sx-5、u+10) – Für kontinuierliche Feineinstellungen ist es nicht besonders handlich, aber man muss nicht #ifdef Die Konsolen-APIs der einzelnen Plattformen werden voneinander getrennt, was die Portierung erleichtert.

Quellcode​

Im Lieferumfang des SDK-Installationsprogramms enthalten

Das Tutorial 08 wird ebenfalls zusammen mit dem SDK lokal installiert – es befindet sich im Installationsverzeichnis des Dienstes unter tutorials/08-haply-inverse-http-remote-config/ dort gefunden.

Verwandte Themen: Sitzungen – Fernsteuerung · Auswahlwerkzeug · Gerätekonfiguration · Basis-Ersetzung · Einbindungen und Arbeitsbereiche · JSON-Konventionen · Tutorial 00 – Geräteliste · Tutorial 07 – Basis und Einbindungen (WebSocket-Version)