支持中心/SDK 与 API 文档

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 示例代码


              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())
            

JavaScript 示例代码


              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)
              })
            

C++ 示例代码


              #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();
              }
            

使用Glaze(全球最快的 JSON 库之一)进行编译时反射。在触觉反馈级别的速率(数千赫兹)下,解析 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()) {
                          // 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.


              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(())
              }
            
警告

请谨慎调整力值。突然设置过高的力值可能会损坏设备或导致设备出现异常行为。

信息

有关消息信封、端口和内容类型的规则,请参阅JSON 规范页面。

更多示例

如需了解 Python、C++(nlohmann)和 C++(Glaze)的完整教程——涵盖力反馈、位置控制、多设备设置、安装/基座配置以及事件流——请参阅“教程”页面。