Skip to content

Commit 0664add

Browse files
committed
faster canbus
1 parent a7391e8 commit 0664add

File tree

1 file changed

+121
-15
lines changed

1 file changed

+121
-15
lines changed

src/lerobot/motors/damiao/damiao.py

Lines changed: 121 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,13 @@ def _refresh_motor(self, motor: NameOrID) -> Optional[can.Message]:
322322
self.canbus.send(msg)
323323
return self._recv_motor_response(expected_recv_id=recv_id)
324324

325-
def _recv_motor_response(self, expected_recv_id: Optional[int] = None, timeout: float = 0.5) -> Optional[can.Message]:
325+
def _recv_motor_response(self, expected_recv_id: Optional[int] = None, timeout: float = 0.001) -> Optional[can.Message]:
326326
"""
327327
Receive a response from a motor.
328328
329329
Args:
330330
expected_recv_id: If provided, only return messages from this CAN ID
331-
timeout: Timeout in seconds
331+
timeout: Timeout in seconds (default: 1ms for high-speed operation)
332332
333333
Returns:
334334
CAN message if received, None otherwise
@@ -337,7 +337,7 @@ def _recv_motor_response(self, expected_recv_id: Optional[int] = None, timeout:
337337
start_time = time.time()
338338
messages_seen = []
339339
while time.time() - start_time < timeout:
340-
msg = self.canbus.recv(timeout=0.01) # Short timeout for polling
340+
msg = self.canbus.recv(timeout=0.0001) # 100us timeout for fast polling
341341
if msg:
342342
messages_seen.append(f"0x{msg.arbitration_id:02X}")
343343
# If no filter specified, return any message
@@ -349,14 +349,43 @@ def _recv_motor_response(self, expected_recv_id: Optional[int] = None, timeout:
349349
else:
350350
logger.debug(f"Ignoring message from CAN ID 0x{msg.arbitration_id:02X}, expected 0x{expected_recv_id:02X}")
351351

352-
# Log what we saw for debugging
353-
if messages_seen:
354-
logger.warning(f"Received {len(messages_seen)} message(s) from IDs {set(messages_seen)}, but expected 0x{expected_recv_id:02X}")
355-
else:
356-
logger.warning(f"No CAN messages received (expected from 0x{expected_recv_id:02X})")
352+
# Only log warnings if we're in debug mode to reduce overhead
353+
if logger.isEnabledFor(logging.DEBUG):
354+
if messages_seen:
355+
logger.debug(f"Received {len(messages_seen)} message(s) from IDs {set(messages_seen)}, but expected 0x{expected_recv_id:02X}")
356+
else:
357+
logger.debug(f"No CAN messages received (expected from 0x{expected_recv_id:02X})")
357358
except Exception as e:
358359
logger.debug(f"Failed to receive CAN message: {e}")
359360
return None
361+
362+
def _recv_all_responses(self, expected_recv_ids: list[int], timeout: float = 0.002) -> dict[int, can.Message]:
363+
"""
364+
Efficiently receive responses from multiple motors at once.
365+
Uses the OpenArms pattern: collect all available messages within timeout.
366+
367+
Args:
368+
expected_recv_ids: List of CAN IDs we expect responses from
369+
timeout: Total timeout in seconds (default: 2ms)
370+
371+
Returns:
372+
Dictionary mapping recv_id to CAN message
373+
"""
374+
responses = {}
375+
expected_set = set(expected_recv_ids)
376+
start_time = time.time()
377+
378+
try:
379+
while len(responses) < len(expected_recv_ids) and (time.time() - start_time) < timeout:
380+
msg = self.canbus.recv(timeout=0.0001) # 100us poll timeout
381+
if msg and msg.arbitration_id in expected_set:
382+
responses[msg.arbitration_id] = msg
383+
if len(responses) == len(expected_recv_ids):
384+
break # Got all responses, exit early
385+
except Exception as e:
386+
logger.debug(f"Error receiving responses: {e}")
387+
388+
return responses
360389

361390
def _mit_control(
362391
self,
@@ -528,14 +557,55 @@ def sync_read(
528557
normalize: bool = True,
529558
num_retry: int = 0,
530559
) -> Dict[str, Value]:
531-
"""Read the same value from multiple motors simultaneously."""
560+
"""
561+
Read the same value from multiple motors simultaneously.
562+
Uses batched operations: sends all refresh commands, then collects all responses.
563+
This is MUCH faster than sequential reads (OpenArms pattern).
564+
"""
532565
motors = self._get_motors_list(motors)
533566
result = {}
534567

568+
# Step 1: Send refresh commands to ALL motors first (no waiting)
569+
for motor in motors:
570+
motor_id = self._get_motor_id(motor)
571+
data = [motor_id & 0xFF, (motor_id >> 8) & 0xFF, CAN_CMD_REFRESH, 0, 0, 0, 0, 0]
572+
msg = can.Message(arbitration_id=CAN_PARAM_ID, data=data, is_extended_id=False)
573+
self.canbus.send(msg)
574+
575+
# Step 2: Collect all responses at once (batch receive)
576+
expected_recv_ids = [self._get_motor_recv_id(motor) for motor in motors]
577+
responses = self._recv_all_responses(expected_recv_ids, timeout=0.003) # 3ms total timeout
578+
579+
# Step 3: Parse responses
535580
for motor in motors:
536581
try:
537-
value = self.read(data_name, motor, normalize=normalize, num_retry=num_retry)
582+
recv_id = self._get_motor_recv_id(motor)
583+
msg = responses.get(recv_id)
584+
585+
if msg is None:
586+
logger.warning(f"No response from motor '{motor}' (recv ID: 0x{recv_id:02X})")
587+
result[motor] = 0.0
588+
continue
589+
590+
motor_type = self._motor_types.get(motor, MotorType.DM4310)
591+
position_degrees, velocity_deg_per_sec, torque, t_mos, t_rotor = self._decode_motor_state(msg.data, motor_type)
592+
593+
# Return requested data
594+
if data_name == "Present_Position":
595+
value = position_degrees
596+
elif data_name == "Present_Velocity":
597+
value = velocity_deg_per_sec
598+
elif data_name == "Present_Torque":
599+
value = torque
600+
elif data_name == "Temperature_MOS":
601+
value = t_mos
602+
elif data_name == "Temperature_Rotor":
603+
value = t_rotor
604+
else:
605+
raise ValueError(f"Unknown data_name: {data_name}")
606+
538607
result[motor] = value
608+
539609
except Exception as e:
540610
logger.warning(f"Failed to read {data_name} from {motor}: {e}")
541611
result[motor] = 0.0
@@ -550,14 +620,50 @@ def sync_write(
550620
normalize: bool = True,
551621
num_retry: int = 0,
552622
) -> None:
553-
"""Write different values to multiple motors simultaneously. Positions are always in degrees."""
623+
"""
624+
Write different values to multiple motors simultaneously. Positions are always in degrees.
625+
Uses batched operations: sends all commands first, then collects responses (OpenArms pattern).
626+
"""
554627
if data_name == "Goal_Position":
555-
# Use MIT control for position commands (values in degrees)
628+
# Step 1: Send all MIT control commands first (no waiting)
556629
for motor, value_degrees in values.items():
557-
# Use reasonable default gains for position control
558-
self._mit_control(motor, 10.0, 0.5, value_degrees, 0, 0)
630+
motor_id = self._get_motor_id(motor)
631+
motor_name = self._get_motor_name(motor)
632+
motor_type = self._motor_types.get(motor_name, MotorType.DM4310)
633+
634+
# Convert degrees to radians
635+
position_rad = np.radians(value_degrees)
636+
637+
# Default gains for position control
638+
kp, kd = 10.0, 0.5
639+
640+
# Get motor limits and encode parameters
641+
pmax, vmax, tmax = MOTOR_LIMIT_PARAMS[motor_type]
642+
kp_uint = self._float_to_uint(kp, 0, 500, 12)
643+
kd_uint = self._float_to_uint(kd, 0, 5, 12)
644+
q_uint = self._float_to_uint(position_rad, -pmax, pmax, 16)
645+
dq_uint = self._float_to_uint(0, -vmax, vmax, 12)
646+
tau_uint = self._float_to_uint(0, -tmax, tmax, 12)
647+
648+
# Pack data
649+
data = [0] * 8
650+
data[0] = (q_uint >> 8) & 0xFF
651+
data[1] = q_uint & 0xFF
652+
data[2] = dq_uint >> 4
653+
data[3] = ((dq_uint & 0xF) << 4) | ((kp_uint >> 8) & 0xF)
654+
data[4] = kp_uint & 0xFF
655+
data[5] = kd_uint >> 4
656+
data[6] = ((kd_uint & 0xF) << 4) | ((tau_uint >> 8) & 0xF)
657+
data[7] = tau_uint & 0xFF
658+
659+
msg = can.Message(arbitration_id=motor_id, data=data, is_extended_id=False)
660+
self.canbus.send(msg)
661+
662+
# Step 2: Collect all responses at once
663+
expected_recv_ids = [self._get_motor_recv_id(motor) for motor in values.keys()]
664+
self._recv_all_responses(expected_recv_ids, timeout=0.002) # 2ms timeout
559665
else:
560-
# Fall back to individual writes
666+
# Fall back to individual writes for other data types
561667
for motor, value in values.items():
562668
self.write(data_name, motor, value, normalize=normalize, num_retry=num_retry)
563669

0 commit comments

Comments
 (0)