Haply Inverse SDK
Es Haply Inverse SDK ist eine sprachunabhängige WebSocket- und HTTP-Schnittstelle für haptische Geräte Haply — die Inverse3, Inverse3x, Minverse, VerseGrip und Wireless VerseGrip. Es läuft als lokaler Dienst, der die Geräteerkennung, die serielle Kommunikation, die Sicherheitsüberwachung und die Statusübermittlung übernimmt, sodass Ihre Anwendung lediglich über einen Socket mit JSON kommunizieren muss.
Zu den Funktionen gehören:
- Geräteerkennung und -verwaltung – erfasst und konfiguriert automatisch die angeschlossenen Geräte Haply s über eine HTTP-REST-API.
- Echtzeit-Statusübertragung – übermittelt den Status der Geräte an die haptischen Kontrollfrequenzen (mehrere kHz) über WebSockets.
- Auftragsabwicklung – führt Kraft- und Positionsbefehle mit hoher Genauigkeit aus und sorgt so für präzises haptisches Feedback.
- Lauf im Hintergrund – wird als lokaler Dienst ausgeführt und hält die Geräte betriebsbereit, ohne dass ein Eingreifen des Benutzers erforderlich ist.
Installation mit dem „ Haply “-Hub
Der einfachste Einstieg ist die Verwendung des Haply Hubzu nutzen, eine Desktop-Anwendung, mit der Sie Ihre Geräte installieren, ausführen, konfigurieren, testen und überwachen können Haply. Sie hält die Firmware auf dem neuesten Stand, bündelt den Inverse Service und enthält Demos, mit denen Sie Ihre Hardware überprüfen können, bevor Sie auch nur eine einzige Zeile Code schreiben.

Haply Hub
Die neueste Version des „ Haply -Hubs“ herunterladen
Laden Sie den Hub herunter und installieren Sie ihn, schließen Sie Ihr Gerät an, und der Hub führt Sie Schritt für Schritt durch die Firmware-Updates. Nach der Installation wird der Inverse Service automatisch im Hintergrund ausgeführt, sobald der Hub geöffnet wird.
Sie können eine bestimmte Version des Inverse Service auch als Systemdienst (Windows) oder Daemon (Linux/macOS) installieren, ohne den Hub zu nutzen. Unter „Ausführen des Dienstes“ finden Sie den Link zum Installationsprogramm sowie eine Anleitung.
Ein kurzes Beispiel
Stellen Sie eine Verbindung zum Dienst her, lesen Sie die Cursorposition des ersten „ Inverse3 “ aus und senden Sie eine Halteanweisung mit dem Wert Null, damit der Dienst weiterhin die Status-Frames überträgt:
- Python
- JavaScript (Node)
- C++ (nlohmann)
- C++ (optimiert)
- Rost
import asyncio, json, websockets
async def main():
# Connect to the Haply Inverse service WebSocket
async with websockets.connect("ws://localhost:10001") as ws:
# Read the first state frame to discover the device id
first_state = json.loads(await ws.recv())
device_id = first_state["inverse3"][0]["device_id"]
# Build a zero-force keepalive command targeting that device
keepalive = {"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_force": {"vector": {"x": 0, "y": 0, "z": 0}}}
}]}
# Realtime loop: one send per tick, then read the resulting state
while True:
await ws.send(json.dumps(keepalive))
state = json.loads(await ws.recv())
pos = state["inverse3"][0]["state"]["cursor_position"]
print(f"pos: {pos}")
asyncio.run(main())
import WebSocket from 'ws'
// Connect to the Haply Inverse service WebSocket
const ws = new WebSocket('ws://localhost:10001')
let keepalive
ws.on('message', (msg) => {
const state = JSON.parse(msg)
if (!keepalive) {
// Read the first state frame to discover the device id
const deviceId = state.inverse3[0].device_id
// Build a zero-force keepalive command targeting that device
keepalive = JSON.stringify({
inverse3: [
{
device_id: deviceId,
commands: { set_cursor_force: { vector: { x: 0, y: 0, z: 0 } } },
},
],
})
ws.send(keepalive)
return
}
// Realtime loop: one send per tick, then read the resulting state
ws.send(keepalive)
console.log('pos:', state.inverse3[0].state.cursor_position)
})
#include <external/libhv.h>
#include <nlohmann/json.hpp>
int main() {
hv::WebSocketClient ws;
nlohmann::json keepalive;
ws.onmessage = [&](const std::string& msg) {
auto state = nlohmann::json::parse(msg);
if (keepalive.is_null()) {
// Read the first state frame to discover the device id
auto device_id = state["inverse3"][0]["device_id"];
// Build a zero-force keepalive command targeting that device
keepalive = {{"inverse3", nlohmann::json::array({{
{"device_id", device_id},
{"commands", {{"set_cursor_force",
{{"vector", {{"x", 0}, {"y", 0}, {"z", 0}}}}}}}
}})}};
ws.send(keepalive.dump());
return;
}
// Realtime loop: one send per tick, then read the resulting state
ws.send(keepalive.dump());
auto pos = state["inverse3"][0]["state"]["cursor_position"];
std::cout << "pos: " << pos << "\n";
};
// Connect to the Haply Inverse service WebSocket
ws.open("ws://localhost:10001");
std::cin.get();
}
Durch den Einsatz von Glaze – einer der schnellsten JSON-Bibliotheken der Welt – für die Reflection zur Kompilierungszeit. Bei haptischen Frequenzen (in der Größenordnung von mehreren kHz) ist jede Mikrosekunde, die für die Analyse von JSON aufgewendet wird, eine Mikrosekunde, die der Regelschleife entzogen wird, weshalb dieser Punkt so wichtig ist. Definieren Sie nur die minimale Struktur, die Sie lesen und schreiben; der Rest wird ignoriert.
#include <external/libhv.h>
#include <glaze/glaze.hpp>
struct vec3 { float x{}, y{}, z{}; };
struct inverse_state { vec3 cursor_position; };
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message { std::vector<inverse_device> inverse3; };
struct set_cursor_force_cmd { vec3 vector; };
struct device_cmd { std::string device_id;
struct { std::optional<set_cursor_force_cmd> set_cursor_force; } commands; };
struct commands_message { std::vector<device_cmd> inverse3; };
int main() {
hv::WebSocketClient ws;
commands_message keepalive;
ws.onmessage = [&](const std::string& msg) {
devices_message state{};
if (glz::read_json(state, msg)) return;
if (keepalive.inverse3.empty()) {
// Read the first state frame to discover the device id
auto device_id = state.inverse3[0].device_id;
// Build a zero-force keepalive command targeting that device
keepalive.inverse3.push_back({device_id});
keepalive.inverse3[0].commands.set_cursor_force = set_cursor_force_cmd{};
std::string out; (void)glz::write_json(keepalive, out);
ws.send(out);
return;
}
// Realtime loop: one send per tick, then read the resulting state
std::string out; (void)glz::write_json(keepalive, out);
ws.send(out);
printf("pos: %f %f %f\n", state.inverse3[0].state.cursor_position.x,
state.inverse3[0].state.cursor_position.y,
state.inverse3[0].state.cursor_position.z);
};
// Connect to the Haply Inverse service WebSocket
ws.open("ws://localhost:10001");
std::cin.get();
}
Anschauliches Beispiel unter Verwendung von tokio-tungstenite + serde_json.
use futures_util::{SinkExt, StreamExt};
use serde_json::json;
use tokio_tungstenite::{connect_async, tungstenite::Message};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Connect to the Haply Inverse service WebSocket
let (mut ws, _) = connect_async("ws://localhost:10001").await?;
// Read the first state frame to discover the device id
let first = match ws.next().await {
Some(Ok(Message::Text(m))) => m,
_ => anyhow::bail!("no initial state frame"),
};
let first_state: serde_json::Value = serde_json::from_str(&first)?;
let device_id = first_state["inverse3"][0]["device_id"].as_str().unwrap();
// Build a zero-force keepalive command targeting that device
let keepalive = json!({
"inverse3": [{
"device_id": device_id,
"commands": { "set_cursor_force": { "vector": { "x": 0, "y": 0, "z": 0 } } }
}]
}).to_string();
// Realtime loop: one send per tick, then read the resulting state
loop {
ws.send(Message::Text(keepalive.clone())).await?;
let msg = match ws.next().await {
Some(Ok(Message::Text(m))) => m,
_ => break,
};
let state: serde_json::Value = serde_json::from_str(&msg)?;
println!("pos: {}", state["inverse3"][0]["state"]["cursor_position"]);
}
Ok(())
}
Passen Sie die Kraftwerte mit Bedacht an. Plötzliche Erhöhungen der Kraftwerte können das Gerät beschädigen oder zu unerwartetem Verhalten führen.
Weitere Informationen zu den JSON-Konventionen für Nachrichten-Envelopes, Ports und Regeln für Inhaltstypen finden Sie auf der entsprechenden Seite.
Weitere Beispiele
Umfassende Tutorials zu Python, C++ (nlohmann) und C++ (Glaze) – zu den Themen Force Feedback, Positionssteuerung, Konfigurationen mit mehreren Geräten, Einrichtung der Montage und des Basisreferenzpunkts sowie zum Ereignisfluss – finden Sie auf der Seite „Tutorials“.