Haply Inverse SDK
Haply Inverse SDK Es handelt sich um eine sprachunabhängige WebSocket- und HTTP-Schnittstelle zur Anbindung von Haply-Haptikgeräten, darunter Inverse3,Inverse3x,Minverse,VerseGrip und Wireless VerseGrip. Sie läuft als lokaler Dienst und übernimmt die Geräteerkennung, die serielle Kommunikation, die Sicherheitsüberwachung und die Statusübertragung, sodass Ihre Anwendung lediglich über Sockets mit JSON kommunizieren muss.
Zu den Funktionen gehören:
- Geräteerkennung und -verwaltung – Automatische Auflistung und Konfiguration der verbundenen Haply-Geräte über die HTTP-REST-API.
- Echtzeit-Statusübertragung – Übertragung des Gerätestatus über WebSocket mit einer taktilen Übertragungsrate (im kHz-Bereich).
- Befehlsverarbeitung – Präzise haptische Rückmeldung durch die Ausführung von Bewegungs- und Positionsbefehlen mit hoher Genauigkeit.
- Im Hintergrund ausführen – Wird als lokaler Dienst ausgeführt, sodass das Gerät ohne Benutzereingriff stets betriebsbereit bleibt.
Installation mit Haply Hub
Der einfachste Einstieg ist Haply Hub– eine Desktop-Anwendung zum Installieren, Ausführen, Konfigurieren, Testen und Überwachen von Haply-Geräten. Sie sorgt dafür, dass die Firmware immer auf dem neuesten Stand ist, verfügt über einen integrierten Inverse-Service und enthält Demo-Programme, mit denen Sie die Hardware testen können, bevor Sie überhaupt mit dem Programmieren beginnen.

Haply Hub
Laden Sie die neueste Version von Haply Hub herunter
Laden Sie den Hub herunter und installieren Sie ihn, schließen Sie Ihr Gerät an – der Hub führt Sie dann durch alle Firmware-Updates. Nach Abschluss der Installation läuftder Inverse Service automatisch im Hintergrund, solange der Hub eingeschaltet ist.
Sie können bestimmte Versionen des Inverse Service auch ohne Verwendung des Hubs als Systemdienst (Windows) oder als Daemon (Linux/macOS) installieren. Links zu den Installationsprogrammen und Anleitungen finden Sie unter„Dienst ausführen“.
Schnelles Beispiel
Stellen Sie eine Verbindung zum Dienst her, lesen Sie die Cursorposition des ersten „Inverse3“ aus und senden Sie ein Signal zur Aufrechterhaltung der Verbindung mit der Kraft „Null“, damit der Dienst weiterhin Status-Frames überträgt:
- Python
- JavaScript (Node)
- C++ (nlohmann)
- C++ (optimierte Version)
- 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();
}
Verwenden Sie Glaze– eine der schnellsten JSON-Bibliotheken der Welt – für Reflection zur Kompilierungszeit. Bei taktilen Aktualisierungsraten (im Bereich von mehreren Tausend Hertz) ist jede Mikrosekunde, die für das Parsen von JSON benötigt wird, Zeit, die dem Regelkreis entzogen wird; daher ist dies von großer Bedeutung. Deklarieren Sie bitte nur die minimalen Strukturen, die Sie lesen und schreiben müssen; 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();
}
Verwendung tokio-tungstenite + serde_json Beispiele (nur zur Veranschaulichung).
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(())
}
Bitte passen Sie die Kraftwerte mit Bedacht an. Plötzlich angewendete hohe Kraftwerte können das Gerät beschädigen oder zu unerwartetem Verhalten führen.
Informationen zu den Regeln für die Nachrichtenkapselung, Ports und den Inhaltstyp (Content-Type) finden Sie auf der Seite „JSON-Konventionen “.
Weitere Beispiele
Um die vollständigen Tutorials zu Python, C++ (nlohmann) und C++ (Glaze) einzusehen (die Themen wie Kraftrückmeldung, Positionssteuerung, Konfiguration mehrerer Geräte, Mount-/Basis-Konfiguration sowie Ereignisströme abdecken), besuchen Sie bittedie Tutorial-Seite.