Haply Inverse SDK
Haply Inverse SDK Il s'agit d'une interface WebSocket + HTTP indépendante du langage, destinée à se connecter aux appareils tactiles Haply, notamment Inverse3,Inverse3x,Minverse,VerseGrip et Wireless VerseGrip. Elle fonctionne sous la forme d’un service local et prend en charge la détection des périphériques, la communication série, la surveillance de la sécurité et la transmission des flux d’état ; votre application n’a donc qu’à communiquer via des sockets en utilisant le format JSON.
Ses fonctionnalités comprennent :
- Détection et gestion des appareils — Répertorier et configurer automatiquement les appareils Haply connectés via l'API REST HTTP.
- Transmission en temps réel de l'état de l'appareil — Transmission de l'état de l'appareil via WebSocket à une fréquence de mise à jour tactile (de l'ordre du kHz).
- Traitement des commandes — Exécution des commandes de mouvement et de position avec une grande précision, pour un retour tactile précis.
- Fonctionnement en arrière-plan — S'exécute en tant que service local, ce qui permet de maintenir l'appareil prêt à l'emploi sans intervention de l'utilisateur.
Installation à l'aide de Haply Hub
La façon la plus simple de commencer est Haply Hub, une application de bureau permettant d’installer, d’exécuter, de configurer, de tester et de surveiller les appareils Haply. Elle permet de maintenir le micrologiciel à jour, intègre Inverse Service et comprend des programmes de démonstration qui vous permettent de valider le matériel avant même d’écrire la moindre ligne de code.

Hub Haply
Télécharger la dernière version de Haply Hub
Téléchargez et installez le Hub, connectez votre appareil : le Hub vous guidera tout au long de la procédure de mise à jour du micrologiciel. Une fois l'installation terminée,le service Inverse s'exécutera automatiquement en arrière-plan tant que le Hub restera allumé.
Vous pouvez également installer une version spécifique d'Inverse Service en tant que service système (Windows) ou démon (Linux / macOS) sans passer par Hub. Pour obtenir le lien vers le programme d'installation et les instructions, consultez la section «Exécution du service».
Exemple rapide
Se connecter au service, lire la position du curseur du premier Inverse3 et envoyer un signal de maintien de connexion à force nulle afin que le service continue à envoyer des trames d'état :
- Python
- JavaScript (Node)
- C++ (nlohmann)
- C++ (version optimisée)
- 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();
}
Utilisez Glaze— l’une des bibliothèques JSON les plus rapides au monde — pour la réflexion en phase de compilation. À des fréquences de rafraîchissement tactiles (plusieurs milliers de hertz), chaque microseconde consacrée à l’analyse de JSON empiète sur le temps alloué à la boucle de contrôle ; c’est donc un point essentiel. Veillez à ne déclarer que la structure minimale dont vous avez besoin pour la lecture et l’écriture ; le reste sera 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();
}
Utilisation tokio-tungstenite + serde_json Exemples (à titre indicatif uniquement).
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(())
}
Veuillez régler la force avec précaution. L'application soudaine d'une force trop élevée pourrait endommager l'appareil ou entraîner un comportement imprévisible.
Pour en savoir plus sur les règles relatives à l'encapsulation des messages, aux ports et aux types de contenu (content-type), veuillez consulter la page « Conventions JSON ».
Plus d'exemples
Pour consulter les tutoriels complets sur Python, C++ (nlohmann) et C++ (Glaze) (couvrant le retour de force, le contrôle de position, la configuration multi-périphériques, la configuration « mount/basis » ainsi que le flux d'événements), veuillez vous rendresur la page des tutoriels.