Haply Inverse Service provides control over Haply's Inverse devices and accessories. This article provides an overview of the Haply Inverse Service.

The Haply Inverse Service runs in the background detecting, communicating, and controlling Haply Inverse devices. It gives access to the devices via HTTP and Websocket interfaces. Moreover, bundled with the service is a dashboard to show the digital twin of the device that can be accessed via a tray icon application.

## Important files

On Windows:
* The configuration files are located in: \%PROGRAMDATA\%\\Haply\\Inverse\\*-config.json
* The logs files are located in: \%PROGRAMDATA\%\\Haply\\Inverse\\*-log.log

On Ubuntu:
* The configuration files are located in: /etc/haply-inverse-service/*-config.json
* The logs files are located in: /var/log/haply-inverse-service/*-log.log

On OSX:
* The configuration files are located in: $TMPDIR/Haply/Inverse/*-config.json
* The logs files are located in: $TMPDIR/Haply/Inverse/*-log.log

## JSON Format Versions

The service currently supports two different `JSON` formats: `3.0` and `3.1`. Both versions are available concurrently, ensuring backward compatibility. If you are using the `3.0` format in your simulations, no changes are required—it will continue to work as before.
However, you are encouraged to update to the `3.1.x` version to take advantage of its enhancements and improved performance for game engine integration.

### Key Details
- **`3.0` Version**
  - Fully documented in the **`3.0.x` documentation pages**.
  - `HTTP` accessible by default on **<http://localhost:10000/3.0/>**.
  - `Websockets` accessible by default on **port `10000`**.

- **`3.1` Version**
  - Fully documented in the **`3.1.x` documentation pages**.
  - `HTTP` accessible by default on **<http://localhost:10000/3.1/>**.
  - `Websockets` accessible by default on **port `10001`**.
  - Offers improvements in functionality and faster integration with game engines.

Feel free to upgrade to the `3.1` format at your convenience to leverage its benefits without any disruption to your existing workflows.

## Interfaces


Both the HTTP and Websocket interfaces use `JSON` formatted payloads.

:::info

When reading the position of the end-effector, you must send a force value to the device (even if the force values are all zeros).

:::

### HTTP

By default the HTTP service starts on: `http://localhost:10000`. Note: the port can be changed in the configuration.

#### Dashboard

The dashboard is available at: `http://localhost:10000`

#### Version

Method: GET

URL: `http://localhost:10000/3.1/version`

Response example:
```json
{
    "build_time": "2024-08-07T16:01:53Z",
    "git_branch": "main",
    "git_describe": "3.0.0-2-gce34c39e",
    "git_hash": "ce34c39e",
    "git_tag": "3.0.0",
    "project_name": "haply-inverse-service",
    "project_version": "3.0.0.0"
}
```

#### Devices

Method: GET

URL: `http://localhost:10000/3.1/devices`

Response example:
```json
{
    "inverse3": [
        {
            "device_id": "04BA",
            "config": {
                "type": "inverse3",
                "device_info": {
                    "firmware_version": 1,
                    "hardware_version": 7,
                    "id": "04BA",
                    "model": 4,
                    "uuid": ""
                },
                "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"
            },
            "state": {
                "battery_level": 0.42000008,
                "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
            }
        }
    ]
}
```

#### Force Scale

Method: POST

URL: `http://localhost:10000/3.1/force_scale`

Body example:
```json
{
    "force_scale": 0.5
}
```

Response example:
```json
{
    "ok": true
}
```

#### Gravity Compensation

Method: POST

URL: `http://localhost:10000/3.1/gravity_compensation`

Body example:
```json
{
    "device_id": "049D",
    "enable": true,
    "gravity_scaling_factor": 0.8
}
```

Response example:
```json
{
    "ok": true
}
```

#### Torque scaling

Method: POST

URL: `http://localhost:10000/3.1/torque_scaling`

Body example:
```json
{
    "device_id": "049D",
    "enable": true
}
```

Response example:
```json
{
    "ok": true
}
```

#### Device Handedness

Method: POST

URL: `http://localhost:10000/3.1/device_handedness`

Body example:
```json
{
    "device_id": "049D",
    "handedness": "right"
}
```

Response example:
```json
{
    "ok": true
}
```

#### Save Configuration

Only works with Inverse3 devices. Warning: Avoid saving the configuration too often, since there is a limited number of save that can be applied to any given device.

Method: POST

URL: `http://localhost:10000/3.1/save_configuration`

Body example:
```json
{
    "device_id": "049D"
}
```

Response example:
```json
{
    "ok": true
}
```

#### Serial Enable

Enable or disable all the serial communication. When the serial communication is disabled it is impossible to send commands to the devices.

Method: POST

URL: `http://localhost:10000/3.1/serial_enable`

Body example:
```json
{
    "enable": true
}
```

Response example:
```json
{
    "ok": true
}
```

#### Grip Vertical Stopper

Enable or disable the grip vertical stopper _experimental_ feature. This feature will check the wireless VerseGrip orientation to detect when the VerseGrip is dropped. When it triggers, it sets the Inverse3 into position control in its current position so that the end effector doesn't drop. It is disabled automatically when the VerseGrip is picked up again.

:::warning

As an _experimental_ feature it is _disabled by default_ and needs to be _explicitly enabled_ to activate.

:::

Method: POST

URL: `http://localhost:10000/3.1/experimental/features/grip_dropped_simulation_stopper`

Body example:
```json
{
    "enable": true,
    "hall_effect_threshold": 17
}
```

Response example:
```json
{
    "ok": true
}
```

### Websocket

The default websocket URL is `ws://localhost:10001`. Note: the port can be changed in the configuration.


#### Initial Message

The service sends a message containing the complete device list when a websocket is connected. The initial message has the following `JSON` format:
```json
{
    "inverse3": [
        {
            "device_id": "04BA",
            "config": {
                "type": "inverse3",
                "device_info": {
                    "firmware_version": 1,
                    "hardware_version": 7,
                    "id": "04BA",
                    "model": 4,
                    "uuid": ""
                },
                "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"
            },
            "state": {
                "battery_level": 0.42000008,
                "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
            }
        }
    ]
}
```

#### State Update Message

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

If you wish to know the state of the machine, you must send it a message beforehand, such as a force value (even if the values are zeros). This is particularly important if using our devices as an input device, such as tracking position and not applying force. The state update message has the following `JSON` format:
```json
{
    "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.42000008,
                "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
            }
        }
    ]
}
```

#### Command Message

In order to send commands to the `inverse3` the client has to send a command message. Here is an example:
```json
{
    "inverse3": [
        {
            "device_id": "049D",
            "commands": {
                "set_cursor_force": {
                    "values": {
                        "x": 1.0,
                        "y": 2.0,
                        "z": 3.0
                    }
                }
            }
        }
    ],
    "verse_grip": [],
    "wireless_verse_grip": []
}
```

Also, it is possible to send commands to multiple devices in a single message. here is an example:
```json
{
    "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
                    }
                }
            }
        }
    ],
    "verse_grip": [],
    "wireless_verse_grip": []
}
```

The following commands are currently available on the `inverse3`:

##### Set Cursor Position
```json
{
    "inverse3": [
        {
            "device_id": "049D",
            "commands": {
                "set_cursor_position": {
                    "values": {
                        "x": 1.0,
                        "y": 2.0,
                        "z": 3.0
                    }
                }
            }
        }
    ],
    "verse_grip": [],
    "wireless_verse_grip": []
}
```

##### Set Cursor Force
```json
{
    "inverse3": [
        {
            "device_id": "049D",
            "commands": {
                "set_cursor_force": {
                    "values": {
                        "x": 1.0,
                        "y": 2.0,
                        "z": 3.0
                    }
                }
            }
        }
    ],
    "verse_grip": [],
    "wireless_verse_grip": []
}
```

##### Set Angular Position
```json
{
    "inverse3": [
        {
            "device_id": "049D",
            "commands": {
                "set_cursor_force": {
                    "values": {
                        "x": 1.0,
                        "y": 2.0,
                        "z": 3.0
                    }
                }
            }
        }
    ],
    "verse_grip": [],
    "wireless_verse_grip": []
}
```

##### Set Angular Torque
```json
{
    "inverse3": [
        {
            "device_id": "049D",
            "commands": {
                "set_angular_torque": {
                    "values": {
                        "a0": 1.0,
                        "a1": 2.0,
                        "a2": 3.0
                    }
                }
            }
        }
    ],
    "verse_grip": [],
    "wireless_verse_grip": []
}
```

##### Probing commands
`probe_cursor_position`, `probe_angular_position` for the inverse3, and `probe_orientation` for verse
grips all contains no command data.
```json
{
    "inverse3": [
        {
            "device_id": "049D",
            "commands": {
                "probe_cursor_position": {},
                "probe_angular_position": {}
            }
        }
    ],
    "verse_grip": [
        {
            "device_id": "049D",
            "commands": {
                "probe_orientation": {}
            }
        }
    ],
    "wireless_verse_grip": [
        {
            "device_id": "049D",
            "commands": {
                "probe_orientation": {}
            }
        }
    ]
}
```
