diff --git a/docs/source/inference.mdx b/docs/source/inference.mdx index 31405b5de..3546e0e03 100644 --- a/docs/source/inference.mdx +++ b/docs/source/inference.mdx @@ -155,6 +155,7 @@ Foot pedal input is also supported via `--strategy.input_device=pedal`. Configur | `--strategy.record_autonomous` | Record autonomous frames too (default: false) | | `--strategy.upload_every_n_episodes` | Push to Hub every N episodes (default: 5) | | `--strategy.input_device` | Input device: `keyboard` or `pedal` (default: keyboard) | +| `--strategy.smooth_handover` | Smoothly hand control over at pause / correction start (default: true). Disable for clutch-style teleops that re-reference at the current robot pose on engage | | `--teleop.type` | **Required.** Teleoperator type | ### Episodic (`--strategy.type=episodic`) diff --git a/src/lerobot/rollout/configs.py b/src/lerobot/rollout/configs.py index 639e2ba29..a0015cdfb 100644 --- a/src/lerobot/rollout/configs.py +++ b/src/lerobot/rollout/configs.py @@ -180,6 +180,14 @@ class DAggerStrategyConfig(RolloutStrategyConfig): # Target video file size in MB for episode rotation (record_autonomous # mode only). Defaults to DEFAULT_VIDEO_FILE_SIZE_IN_MB when None. target_video_file_size_mb: int | None = None + # Whether to turn on or off the smooth handover behavior at phase transitions: + # the leader is driven to the follower position on pause (teleops with + # `send_feedback` capability), and the follower is slid to the teleop pose when + # a correction starts (non-actuated teleops). Disable for clutch-style + # teleoperators (e.g. VR controllers) that re-reference at the current robot + # pose on engage: the handover is already continuous there, and the blocking + # interpolation only delays the start of the correction. + smooth_handover: bool = True input_device: str = "keyboard" keyboard: DAggerKeyboardConfig = field(default_factory=DAggerKeyboardConfig) pedal: DAggerPedalConfig = field(default_factory=DAggerPedalConfig) diff --git a/src/lerobot/rollout/strategies/dagger.py b/src/lerobot/rollout/strategies/dagger.py index 21d1e8e98..dec403ea9 100644 --- a/src/lerobot/rollout/strategies/dagger.py +++ b/src/lerobot/rollout/strategies/dagger.py @@ -623,8 +623,8 @@ class DAggerStrategy(RolloutStrategy): # State-machine transition side-effects # ------------------------------------------------------------------ - @staticmethod def _apply_transition( + self, old_phase: DAggerPhase, new_phase: DAggerPhase, engine, @@ -634,6 +634,10 @@ class DAggerStrategy(RolloutStrategy): ) -> None: """Execute side-effects for a validated phase transition, including smooth handovers. + The smooth handovers below can be disabled with + ``--strategy.smooth_handover=false`` (useful for clutch-style teleops + that re-reference at the current robot pose on engage). + AUTONOMOUS -> PAUSED (actuated teleop): Pause the engine, then drive the leader arm to the follower's last commanded position so the operator takes over without a jerk. @@ -657,7 +661,7 @@ class DAggerStrategy(RolloutStrategy): logger.info("Pausing engine - robot holds position") engine.pause() - if teleop_supports_feedback(teleop) and prev_action is not None: + if self.config.smooth_handover and teleop_supports_feedback(teleop) and prev_action is not None: # TODO(Maxime): prev_action is in robot action key space (output of robot_action_processor). # send_feedback expects teleop feedback key space. For homogeneous setups (e.g. SO-101 # leader + SO-101 follower) the keys are identical so this works. If the processor pipeline @@ -668,7 +672,11 @@ class DAggerStrategy(RolloutStrategy): elif old_phase == DAggerPhase.PAUSED and new_phase == DAggerPhase.CORRECTING: logger.info("Entering correction mode - human teleop control") - if not teleop_supports_feedback(teleop) and prev_action is not None: + if ( + self.config.smooth_handover + and not teleop_supports_feedback(teleop) + and prev_action is not None + ): logger.info("Smooth handover: sliding follower to teleop position") obs = robot.get_observation() teleop_action = teleop.get_action()