Microtube Docs
Developers / Guides / Triggering feedback

Triggering feedback

Fire pressure and vibration on the right finger, at the right moment.

How feedback fires

Feedback comes from an event you raise — a touch, a grab, a release — or from a physics collider in your scene. Gate it behind one of those; never poll it every frame.

Most Unity scenes never call the API at all: drop HexRInteractableHaptics beside your interactable to feel it when grabbed, or SpecialHaptics on a trigger collider to feel it when touched. Use the calls below when you want explicit control.

Warning

Never send feedback in a tight loop. Calls to a disconnected glove are ignored, but flooding the driver every frame causes lag.

Pressure

A steady squeeze on one of six channels — five fingers and the palm. intensity is 0.1–1.0; speed is how fast it ramps there.

csharp
// The Pressure Controller on each hand. finger, on/off,
// intensity 0.1-1.0, ramp speed 0.1-1.0, bypass the hand-near check.
hand.CustomSingleHaptics(Haptics.Finger.Index, true, 0.8f, 1f, false);

// Let go.
hand.CustomSingleHaptics(Haptics.Finger.Index, false, 0f, 1f, false);

Leave the last argument false so a squeeze only fires when a hand is actually at the object.

python
F = Haptics.Finger

# finger, on/off, intensity 0.1-1.0, ramp speed 0.1-1.0
data = haptics.hexr_pressure(F.Index, True, 0.8, 1.0)
await client.write_gatt_char(CHAR_UUID, data)

# Let go.
data = haptics.hexr_pressure(F.Index, False, 0.0, 1.0)
await client.write_gatt_char(CHAR_UUID, data)

Every finger, its own value

Pass an array per argument instead of a single value and each channel gets its own setting, concatenated into one BLE write. The channel order is fixed — Thumb, Index, Middle, Ring, Pinky, Palm — and every array must be the same length as the finger list.

csharp
// One entry per channel. The order is fixed:
// Thumb, Index, Middle, Ring, Pinky, Palm.
Haptics.Finger[] fingers = { Haptics.Finger.Thumb,  Haptics.Finger.Index,
                           Haptics.Finger.Middle, Haptics.Finger.Ring,
                           Haptics.Finger.Pinky,  Haptics.Finger.Palm };

bool[]  states      = { true, true, true, true, true, true };
float[] intensities = { 0.3f, 0.9f, 0.9f, 0.6f, 0.4f, 0.8f };
float[] speeds      = { 1f,   1f,   1f,   1f,   1f,   1f   };

handler.BTSend(handler.haptics.HEXRPressure(fingers, states, intensities, speeds));

// Vibration takes per-finger frequency as well as intensity.
float[] freqs = { 0f, 12f, 12f, 6f, 0f, 3f };
handler.BTSend(handler.haptics.HEXRVibration(fingers, states, freqs, intensities));

Same value on every channel instead? The Pressure Controller has shortcuts: TriggerAllHapticsIncrease(pressure), TriggerCustomHapticsIncrease(bool[], pressure, speed), TriggerAllVibrations(frequency) and TriggerCustomlVibrations(bool[], intensity, frequency) — note the spelling. Their bool[] picks which channels fire, in the same fixed order.

python
F = Haptics.Finger
fingers = [F.Thumb, F.Index, F.Middle, F.Ring, F.Pinky, F.Palm]

# One entry per channel, same order as the list above.
data = haptics.hexr_pressure_multiple(
    fingers,
    [True] * 6,
    [0.3, 0.9, 0.9, 0.6, 0.4, 0.8],   # intensity
    [1.0] * 6)                              # ramp speed
await client.write_gatt_char(CHAR_UUID, data)

# Vibration takes seven lists -- still 0.1-2 Hz only.
data = haptics.hexr_vibrations_multiple(
    fingers, [True] * 6,
    [1.5] * 6, [0.8] * 6, [0.5] * 6, [1.0] * 6, [0.0] * 6)
await client.write_gatt_char(CHAR_UUID, data)
Arrays are not length-checked

A list shorter than the finger array throws IndexOutOfRangeException mid-frame, after part of the payload is already built. Build all of them from the same source of truth.

Vibration

The same squeeze, switched on and off repeatedly. frequency sets how fast it switches.

Frequency changes what intensity means

Below 5 Hz, intensity sets how hard each pulse presses. At 5 Hz and above, pressure is pinned at maximum and intensity sets the duty cycle instead.

FrequencyFeels likeWhat intensity changes
0.1–1 HzSlow, separated pulsesPressure of each pulse
1–5 HzDistinct, countable pulsesPressure of each pulse
5–40 HzContinuous buzzDuty cycle

Out-of-range values are clamped, not rejected. Pass false as the state to stop a channel.

csharp
// finger, on/off, frequency 0.1-40 Hz, intensity 0.1-1.0
byte[] data = handler.haptics.HEXRVibration(Haptics.Finger.Index, true, 12f, 0.7f);
handler.BTSend(data);

// Or through the Pressure Controller -- note that intensity
// comes before frequency here, not after it.
hand.CustomSingleVibrations(Haptics.Finger.Palm, true, 0.7f, 12f, false);

hand.RemoveAllVibrations();

Below 2 Hz a longer overload adds the pulse shape and a pressure to hold once it stops — HEXRVibration(finger, state, frequency, intensity, peakRatio, speed, endIntensity). Ranges are in Feedback parameters.

python
F = Haptics.Finger

# finger, on/off, frequency, intensity, peak ratio,
# ramp speed, and the pressure to hold once it stops
data = haptics.hexr_vibrations(F.Index, True, 1.5, 0.8, 0.5, 1.0, 0.0)
await client.write_gatt_char(CHAR_UUID, data)
Python is capped at 2 Hz

The library exposes only the low-frequency form, so hexr_vibrations clamps to 0.1–2 Hz. For a buzz rather than pulsing, drive the glove from Unity. Ranges are in the Python reference.