Haply Inverse SDK
Haply Inverse SDKIl s'agit d'une interface WebSocket + HTTP indépendante du langage de programmation, permettant de contrôler les dispositifs haptiques Haply (Inverse3, Inverse3x, Minverse, VerseGrip, Wireless VerseGrip). Fonctionnant en tant que service local, il gère la navigation dans les appareils, les communications série, la surveillance de sécurité et la diffusion en continu de l'état ; ainsi, les applications n'ont qu'à utiliser JSON via des sockets.
Les principales fonctionnalités sont les suivantes :
- Recherche et gestion des appareils — Répertorie et configure automatiquement les appareils connectés à Haply via l'API REST HTTP.
- Diffusion en temps réel de l'état — Transmission de l'état de l'appareil via WebSockets à une fréquence de contrôle haptique (de l'ordre de quelques kHz).
- Traitement des commandes — Exécute les commandes de force et de position avec une grande fidélité afin d'assurer un retour haptique précis.
- Fonctionnement en arrière-plan — Il s'exécute en tant que service local et maintient l'appareil prêt à l'emploi sans intervention de l'utilisateur.
Haply Installation du hub
La manière la plus simple de commencerest d’utiliser**Haply Hub** — une application de bureau ( Haply ) qui permet d’installer, d’exécuter, de configurer, de tester et de surveiller vos appareils. Elle vous permet de maintenir votre micrologiciel à jour et comprend un ensemble de services Inverse ainsi que des démos, ce qui vous permet de tester le matériel avant même d’écrire la moindre ligne de code.

Hub Haply
Haply Télécharger la dernière version de Hub
Une fois le Hub téléchargé et installé, connectez votre appareil : le Hub vous guidera alors dans la mise à jour du micrologiciel. Une fois l'installation terminée, le service Inverses'exécutera automatiquement en arrière-plan dès que le Hub sera ouvert.
Même sans Hub, vous pouvez installer une version spécifique d'Inverse Service en tant que service système (Windows) ou démon (Linux / macOS). Pour obtenir le lien vers le programme d'installation et les instructions, veuillez consulter la documentation relative à l'exécution du service.
Exemple simple
Connectez-vous au service pour lire la position du premier curseur d'Inverse3, puis envoyez un message « keepalive » de force 0 afin que le service continue à diffuser des trames d'état :
- Python
- JavaScript (Node)
- C++ (nlohmann)
- C++ (optimisation)
- Rouille
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();
}
Glazeest l'une des bibliothèques JSON les plus rapides au monde ; elleutilise la réflexion à la compilation. À des fréquences haptiques (de l'ordre de quelques kHz), chaque microseconde consacrée à l'analyse de JSON est une microseconde soustraite à la boucle de contrôle, ce qui est crucial. Ne déclarez que le strict minimum nécessaire à la lecture et à l'écriture. Le reste est ignoré.
#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-tungsteniteWa serde_jsonIl s'agit d'un exemple à titre indicatif utilisant [...].
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(())
}
Faites preuve de prudence lorsque vous modifiez la valeur de la force. L'application soudaine d'une force trop élevée peut endommager l'appareil ou provoquer un fonctionnement imprévu.
Pour les règles relatives à l'enveloppe du message, au port et au type de contenu, veuillez consulter la page consacrée à la spécification JSON.
Exemples supplémentaires
Pour consulter un tutoriel complet utilisant Python, C++ (nlohmann) et C++ (Glaze) (incluant le retour de force, le contrôle de position, la configuration de plusieurs appareils, la configuration du montage et de la base, ainsi que la diffusion d'événements), veuillez vous reporter à la page des tutoriels.