Add controller haptic feedback to G1 teleoperation environments#6637
Add controller haptic feedback to G1 teleoperation environments#6637rwiltz wants to merge 2 commits into
Conversation
ffc2ad8 to
11f8240
Compare
Greptile SummaryThis PR introduces end-to-end controller haptic feedback for the G1 teleoperation environments, wiring per-hand
Confidence Score: 5/5Safe to merge — the haptic path is entirely opt-in and degrades gracefully to no-ops on devices that don't support vibration or envs without a haptic config. The change adds a new opt-in output path that is unreachable unless the env config carries a No files require special attention — the two nits (inline duplication in Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Script as teleop_se3_agent / record_demos
participant Driver as HapticFeedbackDriver
participant Sensor as ContactSensor (scene)
participant Device as IsaacTeleopDevice
participant LC as TeleopSessionLifecycle
participant Sink as HapticSink (isaacteleop)
participant XR as OpenXR (xrApplyHapticFeedback)
Note over Script: Each sim frame
Script->>Script: "action = teleop_interface.advance()"
alt action is None OR teleoperation paused
Script->>Driver: stop()
Driver->>Device: send_haptic(endpoint, 0.0) x2
Device->>LC: push_haptic(endpoint, 0.0) x2
Note over LC: _haptic_forces zeroed
else teleoperation active
Script->>Script: env.step(actions)
Note over Sensor: Physics updates contact data
Script->>Driver: update()
Driver->>Sensor: data.net_forces_w[0]
Sensor-->>Driver: [num_bodies, 3] tensor
Driver->>Driver: sum of per-body force norms → scalar
Driver->>Device: send_haptic(endpoint, force)
Device->>LC: push_haptic(endpoint, force)
Note over LC: _haptic_forces[endpoint] = force
end
Note over Script: Next advance() → step()
LC->>LC: _build_external_inputs()
LC->>LC: wrap _haptic_forces in TactileVector TensorGroup
LC->>Sink: external_inputs injected
Sink->>Sink: TactileVectorToControllerPulse (gain/deadband/sat)
Sink->>XR: xrApplyHapticFeedback (amplitude, freq, duration)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Script as teleop_se3_agent / record_demos
participant Driver as HapticFeedbackDriver
participant Sensor as ContactSensor (scene)
participant Device as IsaacTeleopDevice
participant LC as TeleopSessionLifecycle
participant Sink as HapticSink (isaacteleop)
participant XR as OpenXR (xrApplyHapticFeedback)
Note over Script: Each sim frame
Script->>Script: "action = teleop_interface.advance()"
alt action is None OR teleoperation paused
Script->>Driver: stop()
Driver->>Device: send_haptic(endpoint, 0.0) x2
Device->>LC: push_haptic(endpoint, 0.0) x2
Note over LC: _haptic_forces zeroed
else teleoperation active
Script->>Script: env.step(actions)
Note over Sensor: Physics updates contact data
Script->>Driver: update()
Driver->>Sensor: data.net_forces_w[0]
Sensor-->>Driver: [num_bodies, 3] tensor
Driver->>Driver: sum of per-body force norms → scalar
Driver->>Device: send_haptic(endpoint, force)
Device->>LC: push_haptic(endpoint, force)
Note over LC: _haptic_forces[endpoint] = force
end
Note over Script: Next advance() → step()
LC->>LC: _build_external_inputs()
LC->>LC: wrap _haptic_forces in TactileVector TensorGroup
LC->>Sink: external_inputs injected
Sink->>Sink: TactileVectorToControllerPulse (gain/deadband/sat)
Sink->>XR: xrApplyHapticFeedback (amplitude, freq, duration)
Reviews (3): Last reviewed commit: "Merge branch 'develop' into rwiltz/gr1_e..." | Re-trigger Greptile |
Vibrate the XR motion controller when a teleoperated G1 hand applies force to an object, closing the loop on grasp feel during teleop and demo recording. Introduce a device-agnostic feedback seam in isaaclab_teleop: a HapticFeedbackReceiver protocol (kept off the input-only DeviceBase), a HapticFeedbackCfg, and a HapticFeedbackDriver that reads per-hand contact forces from the scene. IsaacTeleopDevice implements the protocol by building an isaacteleop HapticSink (ControllerHapticDevice fed by TactileVectorToControllerPulse) that reuses the existing controller tracker, and feeds per-step force through the same external-input path already used for the anchor transform. Wire it into both G1 locomanipulation teleop examples: add per-hand finger ContactSensors and a haptic_feedback config to the locomanipulation and fixed-base-upper-body-IK envs, and drive the shared driver from teleop_se3_agent.py and record_demos.py via the existing hasattr-based capability discovery. Because the device re-emits the cached force every frame, the driver also zeroes both endpoints on reset and on every non-stepping frame (teleop paused or session not started) so a pulse never persists with a stale grip force. The haptic sink is skipped in MCAP replay sessions, since there is no live controller to drive during scripted playback. Document the feature in the Isaac Teleop guide and API reference.
11f8240 to
f25ee34
Compare
Description
Adds controller haptic feedback to the IsaacTeleop stack: the XR motion
controller vibrates when a teleoperated G1 hand applies force to an object,
giving operators grasp feedback during teleoperation and demo recording.
Introduces a small device-agnostic feedback seam in
isaaclab_teleop— aHapticFeedbackReceiverprotocol (kept off the input-onlyDeviceBase), aHapticFeedbackCfg, and aHapticFeedbackDriverthat reads per-hand contactforces from the scene.
IsaacTeleopDeviceimplements the protocol by buildingan
isaacteleopHapticSink(ControllerHapticDevice+TactileVectorToControllerPulse)that reuses the existing controller tracker, and feeds per-step force through the
same external-input path already used for the anchor transform.
Both G1 locomanipulation teleop examples (
IsaacContrib-PickPlace-Locomanipulation-G1-Absand
IsaacContrib-PickPlace-FixedBaseUpperBodyIK-G1-Abs) gain per-hand fingerContactSensors and ahaptic_feedbackconfig;teleop_se3_agent.pyandrecord_demos.pydrive the shared driver via the existinghasattr-basedcapability discovery.
Requires
isaacteleopwith the controller haptic device (ControllerHapticDevice,TactileVectorToControllerPulse,HapticSink).Fixes # (issue)
Type of change
Checklist
pre-commitchecks with./isaaclab.sh --formatconfig/extension.tomlfileCONTRIBUTORS.mdor my name already exists there