fix(robots): add retries while recording motor ranges (#4126)

* Add retries while recording motor ranges

* fix(motors): throttle calibration reads consistently

---------

Co-authored-by: tom-doerr <tomdoerr96@gmail.com>
This commit is contained in:
Steven Palma
2026-07-23 18:41:48 +02:00
committed by GitHub
parent a993af9c51
commit f59eae4e27
3 changed files with 27 additions and 11 deletions
+9 -5
View File
@@ -23,6 +23,7 @@ from __future__ import annotations
import abc import abc
import logging import logging
import time
from collections.abc import Sequence from collections.abc import Sequence
from contextlib import contextmanager from contextlib import contextmanager
from dataclasses import dataclass from dataclasses import dataclass
@@ -818,13 +819,13 @@ class SerialMotorsBus(MotorsBusBase):
""" """
motor_names = self._get_motors_list(motors) motor_names = self._get_motors_list(motors)
start_positions = self.sync_read("Present_Position", motor_names, normalize=False) start_positions = self.sync_read("Present_Position", motor_names, normalize=False, num_retry=5)
mins = start_positions.copy() mins = start_positions.copy()
maxes = start_positions.copy() maxes = start_positions.copy()
user_pressed_enter = False user_pressed_enter = False
while not user_pressed_enter: while not user_pressed_enter:
positions = self.sync_read("Present_Position", motor_names, normalize=False) positions = self.sync_read("Present_Position", motor_names, normalize=False, num_retry=5)
mins = {motor: min(positions[motor], min_) for motor, min_ in mins.items()} mins = {motor: min(positions[motor], min_) for motor, min_ in mins.items()}
maxes = {motor: max(positions[motor], max_) for motor, max_ in maxes.items()} maxes = {motor: max(positions[motor], max_) for motor, max_ in maxes.items()}
@@ -837,9 +838,12 @@ class SerialMotorsBus(MotorsBusBase):
if enter_pressed(): if enter_pressed():
user_pressed_enter = True user_pressed_enter = True
if display_values and not user_pressed_enter: if not user_pressed_enter:
# Move cursor up to overwrite the previous output if display_values:
move_cursor_up(len(motor_names) + 3) # Move cursor up to overwrite the previous output
move_cursor_up(len(motor_names) + 3)
# Throttle reads even when the live table is disabled.
time.sleep(0.02)
same_min_max = [motor for motor in motor_names if mins[motor] == maxes[motor]] same_min_max = [motor for motor in motor_names if mins[motor] == maxes[motor]]
if same_min_max: if same_min_max:
+9 -3
View File
@@ -405,12 +405,18 @@ def test_record_ranges_of_motion(mock_motors, dummy_motors):
read_pos_stub = mock_motors.build_sequential_sync_read_stub( read_pos_stub = mock_motors.build_sequential_sync_read_stub(
*X_SERIES_CONTROL_TABLE["Present_Position"], positions *X_SERIES_CONTROL_TABLE["Present_Position"], positions
) )
with patch("lerobot.motors.motors_bus.enter_pressed", side_effect=[False, True]): bus = DynamixelMotorsBus(port=mock_motors.port, motors=dummy_motors)
bus = DynamixelMotorsBus(port=mock_motors.port, motors=dummy_motors) bus.connect(handshake=False)
bus.connect(handshake=False)
with (
patch("lerobot.motors.motors_bus.enter_pressed", side_effect=[False, True]),
patch("lerobot.motors.motors_bus.time.sleep") as mock_sleep,
patch.object(bus, "sync_read", wraps=bus.sync_read) as mock_sync_read,
):
mins, maxes = bus.record_ranges_of_motion(display_values=False) mins, maxes = bus.record_ranges_of_motion(display_values=False)
assert mock_motors.stubs[read_pos_stub].calls == 3 assert mock_motors.stubs[read_pos_stub].calls == 3
assert all(call.kwargs["num_retry"] == 5 for call in mock_sync_read.call_args_list)
mock_sleep.assert_called_once_with(0.02)
assert mins == expected_mins assert mins == expected_mins
assert maxes == expected_maxes assert maxes == expected_maxes
+9 -3
View File
@@ -509,12 +509,18 @@ def test_record_ranges_of_motion(mock_motors, dummy_motors):
stub = mock_motors.build_sequential_sync_read_stub( stub = mock_motors.build_sequential_sync_read_stub(
*STS_SMS_SERIES_CONTROL_TABLE["Present_Position"], positions *STS_SMS_SERIES_CONTROL_TABLE["Present_Position"], positions
) )
with patch("lerobot.motors.motors_bus.enter_pressed", side_effect=[False, True]): bus = FeetechMotorsBus(port=mock_motors.port, motors=dummy_motors)
bus = FeetechMotorsBus(port=mock_motors.port, motors=dummy_motors) bus.connect(handshake=False)
bus.connect(handshake=False)
with (
patch("lerobot.motors.motors_bus.enter_pressed", side_effect=[False, True]),
patch("lerobot.motors.motors_bus.time.sleep") as mock_sleep,
patch.object(bus, "sync_read", wraps=bus.sync_read) as mock_sync_read,
):
mins, maxes = bus.record_ranges_of_motion(display_values=False) mins, maxes = bus.record_ranges_of_motion(display_values=False)
assert mock_motors.stubs[stub].calls == 3 assert mock_motors.stubs[stub].calls == 3
assert all(call.kwargs["num_retry"] == 5 for call in mock_sync_read.call_args_list)
mock_sleep.assert_called_once_with(0.02)
assert mins == expected_mins assert mins == expected_mins
assert maxes == expected_maxes assert maxes == expected_maxes