Haply Inverse SDK
《 Haply Inverse SDK 是一个面向Haply 设备的语言无关型 WebSocket + HTTP 接口——即 Inverse3、Inverse3x、 Minverse、VerseGrip 以及Wireless VerseGrip。它作为本地服务运行,负责设备发现、串行通信、安全监控和状态流传输——因此您的应用程序只需通过套接字传输 JSON 数据即可。
其功能包括:
- 设备发现与管理— 通过 HTTP REST API 自动列出并配置已连接的Haply 。
- 实时状态流——通过 WebSockets 以触觉控制速率(数千赫兹)传输设备状态。
- 命令处理——以高精度执行力反馈和位置控制命令,从而实现精准的触觉反馈。
- 后台运行——作为本地服务运行,无需用户干预即可保持设备处于待机状态。
使用Haply 进行安装
最简单的入门方式是 Haply ——这是一款用于安装、运行、配置、测试和监控您的Haply 桌面应用程序。它能保持固件最新,集成了 Inverse Service,并附带演示程序,让您在编写任何代码之前就能验证硬件是否正常。

Haply Hub
下载最新版本的Haply Hub
下载并安装 Hub,连接您的设备,Hub 将引导您完成固件更新。安装完成后,只要 Hub 处于开启状态,Inverse 服务就会在后台自动运行。
您也可以在不使用 Hub 的情况下,将 Inverse Service 的特定版本安装为系统服务(Windows)或守护进程(Linux / macOS)。有关安装程序的链接和说明,请参阅“运行服务”。
快速示例
连接到服务,读取第一个Inverse3 的光标位置,并发送一个零力保持活动信号,以便服务继续流式传输状态帧:
- Python
- JavaScript(Node)
- C++ (nlohmann)
- C++ (Glaze)
- Rust
import asyncio, json, websockets
async def main():
async with websockets.connect("ws://localhost:10001") as ws:
# Handshake: register profile and send a zero-force keepalive
first_state = json.loads(await ws.recv())
device_id = first_state["inverse3"][0]["device_id"]
keepalive = {"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_force": {"vector": {"x": 0, "y": 0, "z": 0}}}
}]}
while True:
state = json.loads(await ws.recv())
pos = state["inverse3"][0]["state"]["cursor_position"]
print(f"pos: {pos}")
await ws.send(json.dumps(keepalive))
asyncio.run(main())
import WebSocket from 'ws'
const ws = new WebSocket('ws://localhost:10001')
let keepalive
ws.on('message', (msg) => {
const state = JSON.parse(msg)
if (!keepalive) {
const deviceId = state.inverse3[0].device_id
keepalive = JSON.stringify({
inverse3: [
{
device_id: deviceId,
commands: { set_cursor_force: { vector: { x: 0, y: 0, z: 0 } } },
},
],
})
}
console.log('pos:', state.inverse3[0].state.cursor_position)
ws.send(keepalive)
})
#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()) {
keepalive = {{"inverse3", nlohmann::json::array({{
{"device_id", state["inverse3"][0]["device_id"]},
{"commands", {{"set_cursor_force",
{{"vector", {{"x", 0}, {"y", 0}, {"z", 0}}}}}}}
}})}};
}
auto pos = state["inverse3"][0]["state"]["cursor_position"];
std::cout << "pos: " << pos << "\n";
ws.send(keepalive.dump());
};
ws.open("ws://localhost:10001");
std::cin.get();
}
使用Glaze进行编译时 JSON 反射。声明您读写所需的最小结构;其余部分将被忽略。
#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()) {
keepalive.inverse3.push_back({state.inverse3[0].device_id});
keepalive.inverse3[0].commands.set_cursor_force = set_cursor_force_cmd{};
}
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);
std::string out; (void)glz::write_json(keepalive, out);
ws.send(out);
};
ws.open("ws://localhost:10001");
std::cin.get();
}
使用...的示例 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<()> {
let (mut ws, _) = connect_async("ws://localhost:10001").await?;
let mut keepalive: Option<String> = None;
while let Some(Ok(Message::Text(msg))) = ws.next().await {
let state: serde_json::Value = serde_json::from_str(&msg)?;
let device_id = state["inverse3"][0]["device_id"].as_str().unwrap();
if keepalive.is_none() {
keepalive = Some(json!({
"inverse3": [{
"device_id": device_id,
"commands": { "set_cursor_force": { "vector": { "x": 0, "y": 0, "z": 0 } } }
}]
}).to_string());
}
println!("pos: {}", state["inverse3"][0]["state"]["cursor_position"]);
ws.send(Message::Text(keepalive.clone().unwrap())).await?;
}
Ok(())
}
请谨慎调整力值。突然设置过高的力值可能会损坏设备或导致设备出现异常行为。
有关消息信封、端口和内容类型的规则,请参阅JSON 规范页面。
更多示例
如需了解 Python、C++(nlohmann)和 C++(Glaze)的完整教程——涵盖力反馈、位置控制、多设备设置、安装/基座配置以及事件流——请参阅“教程”页面。