Haply Inverse SDK
Il s'agit Haply Inverse SDK est une interface indépendante du langage, combinant WebSocket et HTTP, destinée aux appareils haptiques d Haply, parmi lesquels Inverse3Inverse3x, Minverse, VerseGrip et Wireless VerseGrip. L’interface fonctionne comme un service local qui se charge de la détection des appareils, de la communication série, de la surveillance de la sécurité et de la diffusion en continu de l’état – de sorte que votre application n’ait qu’à communiquer via un socket avec JSON.
Parmi les fonctionnalités, on trouve :
- Détection et gestion des appareils – répertorie les appareils Haply connectés et les configure automatiquement via une API HTTP REST.
- Diffusion en temps réel de l'état de l'appareil – fournit l'état de l'appareil aux fréquences requises pour la commande haptique (de l'ordre de plusieurs kHz) via WebSockets.
- Traitement des commandes – exécute les commandes de force et de position avec une grande précision pour un retour haptique précis.
- Fonctionnement en arrière-plan: s'exécute en tant que service local et maintient les appareils en état de fonctionnement sans intervention de l'utilisateur.
Installation avec un concentrateu Haply
Le moyen le plus simple de commencer est le Haply Hub , une application de bureau permettant d’installer, d’exécuter, de configurer, de tester et de surveiller vos appareils Haply. L’application maintient le micrologiciel à jour, intègre Inverse Service et propose des démos qui vous permettent de tester votre matériel avant même d’écrire la moindre ligne de code.

Hub Haply
Téléchargez la dernière version d'Haply Hub
Téléchargez et installez le hub, connectez votre appareil, puis le hub vous guidera tout au long des mises à jour éventuelles du micrologiciel. Une fois l'installation terminée, Inverse Service s'exécute automatiquement en arrière-plan dès que le hub est ouvert.
Vous pouvez également installer une version spécifique d'Inverse Service sans le Hub en tant que service système (Windows) ou démon (Linux / macOS). Vous trouverez le lien vers le programme d'installation et les instructions dans la section « Exécution du service ».
Exemple succinct
Établissez une connexion avec le service, lisez la position du curseur du premier Inverse3 et envoyez un message « keepalive » avec une force nulle afin que le service continue à diffuser des trames d'état :
- Python
- JavaScript (Node)
- C++ (nlohmann)
- C++ (optimisé)
- 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();
}
Avec Glaze – l’une des bibliothèques JSON les plus rapides au monde – pour la réflexion à la compilation. À des fréquences tactiles (plusieurs kHz), chaque microseconde consacrée à l’analyse syntaxique du JSON est perdue pour la boucle de régulation ; c’est pourquoi cela est important. Ne déclarez que la structure minimale que vous lisez et écrivez ; tout 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();
}
Exemple concret avec 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(())
}
Modifiez les valeurs de force avec prudence. Des valeurs de force élevées apparaissant brusquement peuvent endommager l'appareil ou entraîner un comportement inattendu.
Consultez la page « Conventions JSON » pour en savoir plus sur l'enveloppe du message, les ports et les règles relatives au type de contenu.
Autres exemples
Vous trouverez sur la page « Tutoriels » des tutoriels complets sur Python, C++ (nlohmann) et C++ (Glaze) – traitant notamment du retour de force, du contrôle de position, des configurations multi-appareils, de la configuration des supports et des bases, ainsi que de la diffusion d'événements.