Haply Inverse SDK
Haply Inverse SDKist eine sprachunabhängige WebSocket- und HTTP-Schnittstelle, die mit haptischen Geräten von Haply (Inverse3, Inverse3x, Minverse, VerseGrip, Wireless VerseGrip) ermöglicht. Es läuft als lokaler Dienst und übernimmt die Geräteerkennung, serielle Kommunikation, Sicherheitsüberwachung und Status-Streaming, sodass Anwendungen lediglich JSON über Sockets verwenden müssen.
Die wichtigsten Funktionen sind folgende:
- Geräteerkennung und -verwaltung – Automatische Auflistung und Konfiguration der über die HTTP-REST-API verbundenen Haply -Geräte.
- Echtzeit-Statusübertragung – Der Gerätestatus wird über WebSockets mit einer haptischen Abtastrate im Bereich von mehreren kHz übertragen.
- Befehlsausführung — Kraft- und Positionsbefehle werden mit hoher Genauigkeit ausgeführt, um präzises haptisches Feedback zu gewährleisten.
- Hintergrundbetrieb – Wird als lokaler Dienst ausgeführt und hält das Gerät ohne Benutzereingriff betriebsbereit.
Haply Hub installieren
Der einfachste Einstiegist**Haply Hub** — eine Desktop-Anwendung unter Haply zur Installation, Ausführung, Konfiguration, zum Testen und zur Überwachung des Geräts. Sie hält die Firmware auf dem neuesten Stand und enthält das Inverse Service-Paket sowie Demos, mit denen Sie die Hardware testen können, bevor Sie mit dem Programmieren beginnen.

Haply Hub
Haply Die neueste Version von Hub herunterladen
Nachdem Sie den Hub heruntergeladen und installiert haben und das Gerät angeschlossen haben, führt der Hub Sie durch den Firmware-Update-Vorgang. Sobald die Installation abgeschlossen ist, wird der Inverse Serviceautomatisch im Hintergrund ausgeführt, solange der Hub geöffnet ist.
Auch ohne Hub können Sie eine bestimmte Version von Inverse Service als Systemdienst (Windows) oder als Daemon (Linux/macOS) installieren. Links zu den Installationsprogrammen sowie eine Anleitung finden Sie in der Dokumentation zur Ausführung des Dienstes.
Ein einfaches Beispiel
Stellen Sie eine Verbindung zum Dienst her, lesen Sie die Position des ersten Inverse3-Cursors aus und senden Sie ein Keepalive mit der Stärke 0, damit der Dienst weiterhin Status-Frames überträgt:
- 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();
}
Glazeist eine der schnellsten JSON-Bibliotheken der Welt und wird für die Reflexion zur Kompilierungszeit verwendet. Im haptischen Bereich (im Bereich von einigen kHz) ist dies von entscheidender Bedeutung, da jede Mikrosekunde, die für das Parsen von JSON benötigt wird, der Regelschleife verloren geht. Deklarieren Sie nur die für das Lesen und Schreiben unbedingt erforderlichen Elemente. 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();
}
tokio-tungsteniteWow serde_jsonDies 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 vorsichtig, wenn Sie den Kraftwert ändern. Wenn Sie plötzlich einen hohen Kraftwert einstellen, kann dies zu Schäden am Gerät oder zu unerwarteten Funktionsstörungen führen.
Informationen zu den Regeln für „Message Envelope“, „Port“ und „Content-Type“ finden Sie auf der Seite zur JSON-Spezifikation.
Weitere Beispiele
Umfassende Tutorials zur Nutzung von Python, C++ (nlohmann) und C++ (Glaze) (einschließlich Kraft-Feedback, Positionssteuerung, Konfiguration mehrerer Geräte, Mount-/Basis-Konfiguration und Event-Streaming) finden Sie auf der Tutorial-Seite.