Skip to main content
Version: 3.5.x

05. Position Control

Drives the Inverse3 cursor toward a target position via set_cursor_position. The interaction model differs by language — C++ uses one-shot random targets, Python uses continuous keyboard-driven motion.

What you'll learn:

  • Using set_cursor_position for position-mode control
  • Two different interaction models for the same underlying command
  • Clamping the target to a workspace sphere — Minverse uses a smaller radius than Inverse3
  • Setting a workspace preset so the origin sits at workspace centre

Workflow

C++ (random-target model)

  1. Start a background input thread that reads line-buffered keystrokes (n, +, -, q) from stdin.
  2. Open the WebSocket. On the first state frame, register the session profile and set configure.preset: arm_front_centered. Generate the first random target inside a sphere (rejection sampling, radius 0.08 m).
  3. Each tick, send a set_cursor_position command to the current target. The cursor smoothly tracks it — the service rate-limits and interpolates.
  4. When the user types n + ENTER, the input thread generates a new random target. + / - adjust speed; q quits.

Python (hold-to-move model)

  1. Open the WebSocket. On the first state frame, check status.calibrated — prompt the user if the device isn't calibrated yet.
  2. Read config.type to choose the workspace radius (minverse = 0.04 m, anything else = 0.10 m).
  3. Register the session profile and set configure.preset: arm_front_centered.
  4. Each tick: poll keyboard state (W/A/S/D/Q/E), update the target position by SPEED along each pressed axis, clamp to the workspace sphere, and send set_cursor_position. R resets the target to the origin.

Parameters

NameDefault (C++)Default (Python)Purpose
workspace_radius / RADIUS_INVERSE30.08 m0.10 m (Inverse3) / 0.04 m (Minverse)Target sphere radius
speed_step / SPEED0.01 / press0.00005 m / tickStep per interaction
PRINT_EVERY_MS100Telemetry throttle (Python)
Session profileco.haply.inverse.tutorials:position-controlsameIdentifies in Haply Hub
Calibration check (Python)

The Python variant checks status.calibrated from the first state frame and prompts the user if the device isn't calibrated. The C++ variant assumes calibration is already complete.

State fields read

  • data.inverse3[0].device_id — for building the command
  • data.inverse3[0].state.cursor_position — telemetry
  • (Python, first frame only) data.inverse3[0].config.type — selects Inverse3 vs Minverse radius
  • (Python, first frame only) data.inverse3[0].status.calibrated — prompts the user if false

Send / receive

Communication workflow

  • C++ runs a background stdin thread that writes std::atomic<float> targets; the WebSocket thread reads them each tick. On n + ENTER, the input thread generates a new random target; on q, both threads shut down.
  • Python is single-threaded async — the WebSocket loop polls keyboard state each tick and updates position directly.

The Inverse-API payloads are the same: first tick carries the session profile + configure.preset, subsequent ticks carry only set_cursor_position.

Single async loop. Keyboard polling (handle_keys) runs inline every tick — no threads. config.type and status.calibrated are read once from the first state frame.

async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)

if first_message:
first_message = False
device_id = data["inverse3"][0]["device_id"]
radius = get_workspace_radius(data["inverse3"][0].get("config", {}))
# Handshake: profile + preset (one-shot)
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": device_id,
"configure": {"preset": {"preset": "arm_front_centered"}},
}],
}
else:
# Per tick: update position from keyboard (classic polling, not shown), send command
position = handle_keys(position, radius)
request_msg = {
"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_position": {"position": position}},
}],
}

await websocket.send(json.dumps(request_msg))

Source: Python · C++ · C++ Glaze

Related: Control Commands (set_cursor_position) · Mount & Workspace (presets) · Types (vec3) · Tutorial 06 (Combined)