fix(robots): retry LeKiwi bus reads on transient Feetech errors (#4283)

Same fix as #4207 (SO follower/leader), applied to LeKiwi, which reads its
motors through the same Feetech bus and had the same gap: `sync_read` was
called without `num_retry`, so a single corrupted status packet raised
ConnectionError and took the control loop down.

Adds `num_read_retries` (default 2, matching #4207) to LeKiwiConfig and
forwards it at all three read sites. Two of them are Present_Position, as in
#4207; the third is the Present_Velocity read of the omniwheel base, which is
LeKiwi-specific and is where this was observed in the field:

    File "lerobot/robots/lekiwi/lekiwi.py", line 351, in get_observation
      base_wheel_vel = self.bus.sync_read("Present_Velocity", self.base_motors)
    ConnectionError: Failed to sync read 'Present_Velocity' on ids=[7, 8, 9]
    after 1 tries. [TxRxResult] There is no status packet!

All nine servos pinged 20/20 with the bus idle immediately afterwards, so this
is transient corruption under load rather than a wiring fault.

Relates to #4207. Refs #3131.
This commit is contained in:
Xingdong Zuo
2026-08-02 05:50:31 +09:00
committed by GitHub
parent 8135a8a8d1
commit adccdea1cf
2 changed files with 15 additions and 3 deletions
@@ -58,6 +58,12 @@ class LeKiwiConfig(RobotConfig):
# Set to `True` for backward compatibility with previous policies/dataset
use_degrees: bool = True
# Number of extra attempts when a `sync_read` of the motors fails. Feetech buses can occasionally
# return a corrupted status packet ("Incorrect status packet!"), especially when several joints move
# at once, which otherwise aborts the control loop. Retries are immediate (no sleep) and only happen on
# failure, so the steady-state read cost is unchanged.
num_read_retries: int = 2
@dataclass
class LeKiwiHostConfig:
+9 -3
View File
@@ -347,8 +347,12 @@ class LeKiwi(Robot):
def get_observation(self) -> RobotObservation:
# Read actuators position for arm and vel for base
start = time.perf_counter()
arm_pos = self.bus.sync_read("Present_Position", self.arm_motors)
base_wheel_vel = self.bus.sync_read("Present_Velocity", self.base_motors)
arm_pos = self.bus.sync_read(
"Present_Position", self.arm_motors, num_retry=self.config.num_read_retries
)
base_wheel_vel = self.bus.sync_read(
"Present_Velocity", self.base_motors, num_retry=self.config.num_read_retries
)
base_vel = self._wheel_raw_to_body(
base_wheel_vel["base_left_wheel"],
@@ -397,7 +401,9 @@ class LeKiwi(Robot):
# Cap goal position when too far away from present position.
# /!\ Slower fps expected due to reading from the follower.
if self.config.max_relative_target is not None:
present_pos = self.bus.sync_read("Present_Position", self.arm_motors)
present_pos = self.bus.sync_read(
"Present_Position", self.arm_motors, num_retry=self.config.num_read_retries
)
goal_present_pos = {key: (g_pos, present_pos[key]) for key, g_pos in arm_goal_pos.items()}
arm_safe_goal_pos = ensure_safe_goal_position(goal_present_pos, self.config.max_relative_target)
arm_goal_pos = arm_safe_goal_pos