Haply Inverse SDK
Haply Inverse SDK ist das taktile Gerät von Haply (Inverse3,Inverse3x,Minverse,VerseGripund Wireless VerseGrip) darstellt. Da es als lokaler Dienst fungiert, der die Erkennung der Geräte, die serielle Kommunikation, die Sicherheitsüberwachung und das Status-Streaming übernimmt, müssen Anwendungen lediglich über Sockets mittels JSON kommunizieren.
Die wichtigsten Funktionen sind folgende:
- Geräteerkennung und -verwaltung – Über die HTTP-REST-API werden verbundene Haply-Geräte automatisch aufgelistet und konfiguriert.
- Echtzeit-Status-Streaming – Der Gerätestatus wird über WebSocket mit einer taktilen Übertragungsrate (im Bereich von einigen kHz) übertragen.
- Befehlsverarbeitung – Für präzises taktiles Feedback werden Kraft- und Positionsbefehle mit hoher Genauigkeit ausgeführt.
- Hintergrundbetrieb – Wird als lokaler Dienst ausgeführt und hält das Gerät ohne Benutzereingriff in einem betriebsbereiten Zustand.
Haply Installation auf dem Hub
Der einfachste Weg, um anzufangen, istHaply Hub zu verwenden. Dabei handelt es sich um eine Desktop-Anwendung zur Installation, Ausführung, Konfiguration, zum Testen und zur Überwachung von Haply-Geräten. Sie hält die Firmware auf dem neuesten Stand, enthält den Inverse Service und bietet Demos, mit denen Sie die Hardware überprüfen können, noch bevor Sie auch nur eine einzige Zeile Code schreiben.

Haply Hub
Haply Die neueste Version von Hub herunterladen
Laden Sie den Hub herunter, installieren Sie ihn und schließen Sie das Gerät an. Der Hub führt Sie durch die Firmware-Aktualisierung. Nach der Installation wird der Inverse Service automatisch im Hintergrund ausgeführt, solange der Hub geöffnet ist.
Sie können eine bestimmte Version des Inverse Service auch ohne Verwendung von Hub als Systemdienst (Windows) oder als Daemon (Linux/macOS) installieren. Links zum Installationsprogramm und eine Anleitung finden Sie unter„Dienst ausführen “.
Kurzes Beispiel
Stellen Sie eine Verbindung zum Dienst her, lesen Sie die Position des ersten Inverse3-Cursors aus und senden Sie ein Keep-Alive-Signal mit der Stärke Null, damit der Dienst das Streaming der Status-Frames fortsetzt:
- Python
- JavaScript (Node)
- C++ (nlohmann)
- C++ (Optimierung)
- 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();
}
Für die Reflektion zur Kompilierungszeit wirdGlaze verwendet. Dies ist eine der schnellsten JSON-Bibliotheken weltweit. Bei taktilen Frequenzen (im Bereich von einigen kHz) ist dies von Bedeutung, da jede Mikrosekunde, die für die Analyse von JSON aufgewendet wird, der Regelschleife entsprechend eine Mikrosekunde entzogen wird. Deklarieren Sie bitte nur die minimal erforderlichen Strukturen zum Lesen und Schreiben. Alles andere 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();
}
tokio-tungstenite + serde_json Dies ist ein Beispiel zur Veranschaulichung der Verwendung von .
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(())
}
Seien Sie bitte äußerst vorsichtig, wenn Sie den Kraftwert ändern. Die plötzliche Einstellung eines sehr hohen Kraftwerts kann zu Schäden am Gerät oder zu unerwartetem Verhalten führen.
Informationen zu den Regeln für den Umschlag, die Ports und den Inhaltstyp von Nachrichten finden Sie auf der Seitemit den JSON-Spezifikationen.
Weitere Beispiele
Aufder Tutorial-Seite finden Sie umfangreiche Tutorials zu den Themen Kraft-Feedback, Positionssteuerung, Konfiguration mehrerer Geräte, Mount-/Basis-Einstellungen und Event-Streaming, die in Python, C++ (nlohmann) und C++ (Glaze) erstellt wurden.