From e84e56e2351ca3a59b2b668a5d6636f9a422dbaa Mon Sep 17 00:00:00 2001 From: Martino Russi Date: Mon, 20 Jul 2026 12:26:11 +0200 Subject: [PATCH] fix(unitree_g1): make 'e' stop instant (zero-torque + hard exit) The 'e' key previously went through the graceful disconnect (soft-stop arm ramp + thread joins), which took several seconds while the 50Hz controller loop kept publishing over the single zero-torque. Now 'e' stops the controller loop, sends one zero-gain (limp) command, and os._exit immediately -> robot goes passive in ~50ms. Co-authored-by: Cursor --- .../robots/unitree_g1/run_g1_onboard.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/lerobot/robots/unitree_g1/run_g1_onboard.py b/src/lerobot/robots/unitree_g1/run_g1_onboard.py index a60cb921d..b3aa4d60f 100644 --- a/src/lerobot/robots/unitree_g1/run_g1_onboard.py +++ b/src/lerobot/robots/unitree_g1/run_g1_onboard.py @@ -46,6 +46,7 @@ Examples (on the robot): import argparse import json import logging +import os import signal import sys import threading @@ -166,15 +167,21 @@ def main() -> None: signal.signal(signal.SIGINT, lambda *_: stop.set()) signal.signal(signal.SIGTERM, lambda *_: stop.set()) - # Emergency stop key: 'e' + Enter -> skip the soft-stop arm ramp (make it a no-op), - # then trigger the normal shutdown, which goes straight to zero-torque and exits. + # Emergency stop key: 'e' + Enter -> go passive NOW. We stop the controller loop + # from re-publishing, send a single zero-gain (limp) command, and hard-exit the + # process. This deliberately skips the graceful disconnect (soft-stop arm ramp + + # thread joins), which is what made it take several seconds. def estop_listener() -> None: for line in sys.stdin: if line.strip().lower() == "e": - logger.warning("E-STOP ('e'): stopping immediately (no arm ramp).") - robot._soft_stop = lambda: None # skip the slow ramp-to-default - stop.set() - break + logger.warning("E-STOP ('e'): going passive NOW.") + try: + robot._shutdown_event.set() # stop the 50Hz controller loop publishing + time.sleep(0.05) # let it finish its current cycle + robot._send_zero_torque() # motors limp; nothing overwrites it now + except Exception as e: # noqa: BLE001 + logger.warning("E-stop zero-torque failed: %s", e) + os._exit(0) # immediate hard exit, no slow cleanup threading.Thread(target=estop_listener, daemon=True).start()