Haply Inverse SDK
Haply Inverse SDK Il s'agit du dispositif tactile de Haply (Inverse3,Inverse3x,Minverse,VerseGripet Wireless VerseGrip). Fonctionnant comme un service local chargé de la détection des périphériques, de la communication série, de la surveillance de la sécurité et de la diffusion en continu de l'état, il permet aux applications de communiquer simplement via des sockets au format JSON.
Les principales fonctionnalités sont les suivantes :
- Détection et gestion des appareils — Permet de répertorier et de configurer automatiquement les appareils Haply connectés via l'API REST HTTP.
- Diffusion en temps réel de l'état — L'état de l'appareil est transmis via WebSocket à une fréquence de contrôle tactile (de l'ordre de quelques kHz).
- Traitement des commandes — Pour un retour tactile précis, les commandes de force et de position sont exécutées avec une grande fidélité.
- Fonctionnement en arrière-plan — S'exécute en tant que service local et maintient l'appareil prêt à l'emploi sans intervention de l'utilisateur.
Haply Installation sur Hub
La façon la plus simple de commencer est deHaply d'utiliser Hub . Il s'agit d'une application de bureau permettant d'installer, d'exécuter, de configurer, de tester et de surveiller les appareils Haply. Elle maintient le micrologiciel à jour, intègre le service Inverse et propose des démos permettant de valider 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
Téléchargez et installez le Hub, puis connectez votre appareil. Le Hub vous guidera tout au long de la mise à jour du micrologiciel. Une fois l'installation terminée, Inverse Service s'exécutera automatiquement en arrière-plan lorsque le Hub sera ouvert.
Il est également possible d'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 ».
Exemples rapides
Se connecter au service, lire la position initiale du curseur d'Inverse3, puis envoyer un message de maintien de connexion (keep-alive) de force nulle afin que le service continue à diffuser les 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();
}
Pour la réflexion à la compilation, nous utilisonsGlaze. Il s'agit de l'une des bibliothèques JSON les plus rapides au monde. À des fréquences tactiles (de l'ordre de quelques kHz), chaque microseconde consacrée à l'analyse du JSON correspond à une microseconde soustraite à la boucle de contrôle ; ce point est donc crucial. Veuillez déclarer uniquement les formes minimales à lire et à écrire. Tout 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();
}
tokio-tungstenite + serde_json Il s'agit d'un exemple illustratif 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(())
}
Veuillez faire preuve d'une grande prudence lorsque vous modifiez la valeur de la force. Le réglage brusque d'une valeur de force trop élevée peut endommager l'appareil ou entraîner un fonctionnement imprévu.
Pour en savoir plus sur les règles relatives à l'enveloppe, aux ports et aux types de contenu des messages, veuillez consulter la pageconsacrée aux spécifications JSON.
Autres exemples
Pour découvrir des tutoriels avancés, développés en Python, C++ (nlohmann) et C++ (Glaze), traitant du retour de force, du contrôle de position, des configurations multi-périphériques, des paramètres « mount » et « basis », ainsi que de la diffusion d'événements, veuillez consulter la pagedes tutoriels.