TeslasuitDocumentation
Frameworks

Haptic proximity radar (continuous direction and distance)

A continuous, distance-modulated haptic example. A draggable target sits on a radar disc around the wearer: its direction selects which body zone fires and its distance sets the intensity.


What it shows#

This is the continuous analogue of the discrete arrow-key cues in Haptic navigation. The operator sees a top-down view of the wearer at the centre of a disc and drags a target anywhere inside it. The suit fires a continuous haptic cue whose:

  • Channel (which body zone fires) is selected by the angle from the wearer to the target.
  • Intensity (pulse width × amplitude) is driven by the radial distance: close = strong, far = silent.

Capabilities demonstrated:

  • HapticLibrary subclass: ProximityHaptics declares four named CustomPlayable slots: belly, back, left_shoulder, right_shoulder.
  • Per-slot intensity multipliers: the strategy writes amplitude_mult and pulse_width_mult on each slot every cycle, instead of only toggling IsMuted.
  • Custom ControlMessage: ProximityControlMessage carries the target position (obj_x, obj_y) and a master intensity.
  • Haptic only output: no EMS is produced; self.ems_output is pre-muted in the strategy's __init__.

Project layout#

examples/haptic_proximity_radar/
├── main.py                          ── orchestrator.launch()
├── proximity_radar_strategy.py      ── ProximityRadarStrategy(ControlStrategyBase)
├── proximity_radar_types.py         ── ProximityControlMessage + ProximityHaptics
└── gui/
    ├── main.py                      ── GUI process entry point
    └── radar_tab.py                 ── radar disc, draggable target, zone table

Concept#

                 N (forward)
                 belly
                   │
       W ──────────●──────────  E  (right shoulder)
   (left shoulder) │
                   │
                  back
                   S
  • The wearer is the dot at the disc centre.
  • The target can be dragged anywhere within radius R.
  • Direction picks the body zone, with a cosine-weighted blend on the boundaries so adjacent zones crossfade smoothly.
  • Distance sets the strength of the cue. At r ≥ 0.95·R all zones are muted.

Signal mapping#

Let (x, y) be the target position relative to the wearer and R the disc radius.

  • r = clamp(|p| / R, 0, 1)
  • θ = atan2(y, x) (0 = right / east; π/2 = forward / north)
  • prox = clamp(1 − r, 0, 1)
  • Per zone with centre angle θ_zone:
    • w_zone = max(0, cos(θ − θ_zone))²
    • amp_mult = master · prox · w_zone
    • pw_mult = 0.5 + 0.5 · prox
    • IsMuted = (prox == 0) or (w_zone < ε)

Cardinal positions excite exactly one zone; 45° positions excite the two neighbours at equal weight (about 0.5 each).


Bone / channel mapping (Teslasuit 4R)#

Same map as Haptic navigation, verified on the reference suit:

Body areabone_idChannelsDirection (θ_zone)
Abdomen (belly)80..7forward (+π/2)
Back110..7backward (−π/2)
Left upper-arm front120..2left (+π)
Right upper-arm front140..2right (0)

Running it#

python -m examples.haptic_proximity_radar.main

A top-down radar window appears with a draggable target. Drag it around the disc to change which body zone fires and how strong the cue is.

KeyAction
DragMove the target
Arrow keysNudge the target (Shift + arrow for a larger step)
SpaceToggle orbit mode
0Re-centre the target
, / .Lower / raise master intensity
EscStop all zones

Press Ctrl-C in the launching shell to exit.


Safety notes#

This example drives haptic output only; EMS output is muted for the whole session. Esc stops all zones immediately, and the cues are also stopped when the window loses focus. Start with a low master intensity and raise it only if the wearer reports the cue as too subtle.

See Safety for the framework's safety guarantees.


See also#