Skip to main content
Version: 3.5.x

Simulation Channel

Overview

The Simulation Channel is a high-frequency, bidirectional WebSocket channel used to exchange device state and send session or device commands.

Default URL: ws://localhost:10001

The port can be changed in the configuration.

Core contract (important):

  • When a client connects, the service sends an initial inventory message containing the complete list of devices.
  • After that, the server sends exactly one State Update message for each message received from the client.
  • Each State Update contains the state (and status) of all devices.

:::info Modules This page documents the core Simulation Channel protocol and commands.

Additional modules/features can register and extend the system with their own commands and/or state fields. Those are documented separately in the modules section. :::

Protocol Rules

One response per client message

The service emits one State Update message containing the state of all devices for each message received from the client.

This means:

  • If you need a fresh snapshot of device state, you must send something (a session command, a probe command, or a device command).
  • The channel behaves like a "tick" loop driven by client messages.

Polling without applying forces (probe commands)

If you want to observe state changes without applying forces or changing simulation parameters, use probing commands:

  • probe_position for inverse3
  • probe_orientation for Verse grips

Probing commands contain no command data and simply force device information queries so positional/orientation data is up to date on the next State Update.

Configuration vs state

  • The Initial Inventory message includes device config, state, and status.
  • Regular State Update messages include device state and status.

If you need a snapshot including configuration again, use session.force_render_full_state.

Coordinate Systems

Haply uses a right-handed coordinate system with Z-Up by default.

Two session-level controls affect how coordinates are interpreted and returned:

Message Formats

This section describes the high-level envelope and message types. Full examples are provided later in this document.

Device groups

Messages are grouped by device type at the top level (e.g. inverse3, verse_grip, wireless_verse_grip). Each device type key maps to an array of per-device objects.

Initial Inventory (server → client)

Sent once immediately after a WebSocket connection is established.

Each entry includes:

  • device_id
  • config
  • state
  • status

See: Example: Initial Inventory Payload

State Update (server → client)

Sent once for each client message.

Each entry includes:

  • device_id
  • state
  • status

See: Example: State Update Payload

Session command envelope (client → server)

Session commands are actions that apply to the current connection/session and are not device-specific.

{
"session": {
"<command_name>": {
"...": "..."
}
}
}

Device command envelope (client → server)

Device commands are sent under the device type key as an array, allowing commands to be sent to one or more devices in a single message.

{
"<device_type>": [
{
"device_id": "<id>",
"commands": {
"<command_name>": {
"...": "..."
}
}
}
]
}

You can include multiple entries to command multiple devices of the same type in a single message. Note that commands is a dictionnary and can handle contain multiple command for a given device, but only one per command type.


Command Reference

Session

Force Render Full State

Request a snapshot of all device states and configurations.

{
"session": {
"force_render_full_state": {}
}
}

Set Coordinate Origin

Set the coordinate origin for all devices.

{
"session": {
"set_coordinate_origin": {
"coordinate_origin": "workspace_center"
}
}
}

Supported values:

  • device_base (default)
  • workspace_center

If you set the origin to workspace_center, the (0, 0, 0) position is moved to the center of the device workspace, which varies based on device type.

Set Basis

Set the basis mapping for the current session. The basis mapping instructs the transformation from Haply’s coordinate system to yours for the entire session.

After setting the basis:

  • All states returned from the system are represented in that basis.
  • All values you send as part of other commands are interpreted in that basis.

The mapping is defined relative to Haply coordinates and is expressed as a permutation of X, Y, Z, optionally prefixed by + or -. Examples of valid values:

  • XYZ, ZYX
  • +Y-Z+X, X-ZY

Interpretation example:

  • YZX means Y is right, Z is forward, X is up.

Example for a left-handed Z-up system like Unreal (X-YZ):

{
"session": {
"set_basis": {
"basis": {
"permutation": "X-YZ"
}
}
}
}

All devices commands

Probing (all devices)

Use probing commands to request up-to-date positional/orientation information without applying forces or other simulation changes.

  • inverse3: probe_position
  • Verse grips (verse_grip, wireless_verse_grip): probe_orientation
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"probe_position": {}
}
}
],
"verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
],
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
]
}

Inverse3 commands

To send commands to an inverse3 device, include an entry with a matching device_id under the inverse3 key.

Command one device:

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

Command multiple devices in one message:

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
},
{
"device_id": "049E",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

Set Cursor Position

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

Set Cursor Force

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"values": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}

Set Angular Position

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"values": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}

Set Angular Torque

{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torque": {
"values": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}

Extended Verse Grip

The set_extension_data command is part of the extended protocol for Verse grips, used with grip versions that implement the board extension communication protocol.

Set Grip Extension Data

Supported data lengths:

  • Up to 20 bytes upstream (client → device).
  • Up to 12 bytes downstream (device → client), returned in the State Update message as state.extension_data.

Data specification:

  • Array length: 20 bytes
  • Value range: each value is 0–255
{
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"set_extension_data": {
"extension_data": [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
}
}
}
]
}

Examples

Initial Inventory Payload

The service sends a message containing the complete device list when a WebSocket is connected. The initial message has the following JSON format:

{
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"device_info": {
"minor_version": 1,
"major_version": 7,
"id": "04BA",
"model": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"port": "COM13",
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 1
},
"handedness": "right",
"torque_scaling": {
"enabled": true
}
},
"state": {
"angular_position": {
"a0": -69.31704,
"a1": 137.62952,
"a2": 19.832787
},
"angular_velocity": {
"a0": 0,
"a1": 0,
"a2": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"mode": "idle"
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"config": {
"port": "COM3",
"type": "verse_grip"
},
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"config": {
"port": "COM6",
"type": "wireless_verse_grip",
"major_version": 1,
"minor_version": 4,
"hardware_version": 1
},
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
]
}

State Update Payload

The service will send one state update message containing the state of all devices for each message received.

If you wish to know the state of the machine, you must send it a message beforehand (for example a probe command or a force value, even if the values are zeros). This is particularly important when using devices as input sources (e.g., tracking position) without applying forces.

The state update message has the following JSON format:

{
"inverse3": [
{
"device_id": "04BA",
"state": {
"angular_position": {
"a0": -69.31704,
"a1": 137.62952,
"a2": 19.832787
},
"angular_velocity": {
"a0": 0,
"a1": 0,
"a2": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"mode": "idle"
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
}
},
"status": {
"ready": true
}
}
],
"custom_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"extension_data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
"status": {
"ready": true
}
}
]
}