@@ -73,7 +73,7 @@ def __init__(
7373 port : str ,
7474 motors : dict [str , Motor ],
7575 calibration : dict [str , MotorCalibration ] | None = None ,
76- can_interface : str = "socketcan " ,
76+ can_interface : str = "auto " ,
7777 ):
7878 """
7979 Initialize the Damiao motors bus.
@@ -82,7 +82,7 @@ def __init__(
8282 port: CAN interface name (e.g., "can0" for Linux, "/dev/cu.usbmodem*" for macOS)
8383 motors: Dictionary mapping motor names to Motor objects
8484 calibration: Optional calibration data
85- can_interface: CAN interface type - "socketcan" (Linux) or "slcan" (macOS/serial)
85+ can_interface: CAN interface type - "auto" (default), " socketcan" (Linux), or "slcan" (macOS/serial)
8686 """
8787 super ().__init__ (port , motors , calibration )
8888 self .port = port
@@ -94,14 +94,18 @@ def __init__(
9494 self ._motor_can_ids = {}
9595 self ._recv_id_to_motor = {}
9696
97- # Store motor types
97+ # Store motor types and recv IDs
9898 self ._motor_types = {}
9999 for name , motor in self .motors .items ():
100100 if hasattr (motor , "motor_type" ):
101101 self ._motor_types [name ] = motor .motor_type
102102 else :
103103 # Default to DM4310 if not specified
104104 self ._motor_types [name ] = MotorType .DM4310
105+
106+ # Map recv_id to motor name for filtering responses
107+ if hasattr (motor , "recv_id" ):
108+ self ._recv_id_to_motor [motor .recv_id ] = name
105109
106110 @property
107111 def is_connected (self ) -> bool :
@@ -206,18 +210,20 @@ def configure_motors(self) -> None:
206210 def _enable_motor (self , motor : NameOrID ) -> None :
207211 """Enable a single motor."""
208212 motor_id = self ._get_motor_id (motor )
213+ recv_id = self ._get_motor_recv_id (motor )
209214 data = [0xFF ] * 7 + [CAN_CMD_ENABLE ]
210215 msg = can .Message (arbitration_id = motor_id , data = data , is_extended_id = False )
211216 self .canbus .send (msg )
212- self ._recv_motor_response ()
217+ self ._recv_motor_response (expected_recv_id = recv_id )
213218
214219 def _disable_motor (self , motor : NameOrID ) -> None :
215220 """Disable a single motor."""
216221 motor_id = self ._get_motor_id (motor )
222+ recv_id = self ._get_motor_recv_id (motor )
217223 data = [0xFF ] * 7 + [CAN_CMD_DISABLE ]
218224 msg = can .Message (arbitration_id = motor_id , data = data , is_extended_id = False )
219225 self .canbus .send (msg )
220- self ._recv_motor_response ()
226+ self ._recv_motor_response (expected_recv_id = recv_id )
221227
222228 def enable_torque (self , motors : str | list [str ] | None = None , num_retry : int = 0 ) -> None :
223229 """Enable torque on selected motors."""
@@ -250,26 +256,54 @@ def set_zero_position(self, motors: str | list[str] | None = None) -> None:
250256 motors = self ._get_motors_list (motors )
251257 for motor in motors :
252258 motor_id = self ._get_motor_id (motor )
259+ recv_id = self ._get_motor_recv_id (motor )
253260 data = [0xFF ] * 7 + [CAN_CMD_SET_ZERO ]
254261 msg = can .Message (arbitration_id = motor_id , data = data , is_extended_id = False )
255262 self .canbus .send (msg )
256- self ._recv_motor_response ()
263+ self ._recv_motor_response (expected_recv_id = recv_id )
257264 time .sleep (0.01 )
258265
259- def _refresh_motor (self , motor : NameOrID ) -> None :
260- """Refresh motor status."""
266+ def _refresh_motor (self , motor : NameOrID ) -> Optional [ can . Message ] :
267+ """Refresh motor status and return the response ."""
261268 motor_id = self ._get_motor_id (motor )
269+ recv_id = self ._get_motor_recv_id (motor )
262270 data = [motor_id & 0xFF , (motor_id >> 8 ) & 0xFF , CAN_CMD_REFRESH , 0 , 0 , 0 , 0 , 0 ]
263271 msg = can .Message (arbitration_id = CAN_PARAM_ID , data = data , is_extended_id = False )
264272 self .canbus .send (msg )
265- self ._recv_motor_response ()
273+ return self ._recv_motor_response (expected_recv_id = recv_id )
266274
267- def _recv_motor_response (self , timeout : float = 0.1 ) -> Optional [can .Message ]:
268- """Receive a response from a motor."""
275+ def _recv_motor_response (self , expected_recv_id : Optional [int ] = None , timeout : float = 0.5 ) -> Optional [can .Message ]:
276+ """
277+ Receive a response from a motor.
278+
279+ Args:
280+ expected_recv_id: If provided, only return messages from this CAN ID
281+ timeout: Timeout in seconds
282+
283+ Returns:
284+ CAN message if received, None otherwise
285+ """
269286 try :
270- msg = self .canbus .recv (timeout = timeout )
271- if msg :
272- return msg
287+ start_time = time .time ()
288+ messages_seen = []
289+ while time .time () - start_time < timeout :
290+ msg = self .canbus .recv (timeout = 0.01 ) # Short timeout for polling
291+ if msg :
292+ messages_seen .append (f"0x{ msg .arbitration_id :02X} " )
293+ # If no filter specified, return any message
294+ if expected_recv_id is None :
295+ return msg
296+ # Otherwise, only return if it matches the expected recv_id
297+ if msg .arbitration_id == expected_recv_id :
298+ return msg
299+ else :
300+ logger .debug (f"Ignoring message from CAN ID 0x{ msg .arbitration_id :02X} , expected 0x{ expected_recv_id :02X} " )
301+
302+ # Log what we saw for debugging
303+ if messages_seen :
304+ logger .warning (f"Received { len (messages_seen )} message(s) from IDs { set (messages_seen )} , but expected 0x{ expected_recv_id :02X} " )
305+ else :
306+ logger .warning (f"No CAN messages received (expected from 0x{ expected_recv_id :02X} )" )
273307 except Exception as e :
274308 logger .debug (f"Failed to receive CAN message: { e } " )
275309 return None
@@ -325,7 +359,8 @@ def _mit_control(
325359
326360 msg = can .Message (arbitration_id = motor_id , data = data , is_extended_id = False )
327361 self .canbus .send (msg )
328- self ._recv_motor_response ()
362+ recv_id = self ._get_motor_recv_id (motor )
363+ self ._recv_motor_response (expected_recv_id = recv_id )
329364
330365 def _float_to_uint (self , x : float , x_min : float , x_max : float , bits : int ) -> int :
331366 """Convert float to unsigned integer for CAN transmission."""
@@ -384,12 +419,15 @@ def read(
384419 raise DeviceNotConnectedError (f"{ self } is not connected." )
385420
386421 # Refresh motor to get latest state
387- self ._refresh_motor (motor )
388-
389- # Read response
390- msg = self ._recv_motor_response ()
422+ msg = self ._refresh_motor (motor )
391423 if msg is None :
392- raise ConnectionError (f"No response from motor { motor } " )
424+ motor_id = self ._get_motor_id (motor )
425+ recv_id = self ._get_motor_recv_id (motor )
426+ raise ConnectionError (
427+ f"No response from motor '{ motor } ' (send ID: 0x{ motor_id :02X} , recv ID: 0x{ recv_id :02X} ). "
428+ f"Check that: 1) Motor is powered (24V), 2) CAN wiring is correct, "
429+ f"3) Motor IDs are configured correctly using Damiao Debugging Tools"
430+ )
393431
394432 motor_type = self ._motor_types .get (motor , MotorType .DM4310 )
395433 position_degrees , velocity_deg_per_sec , torque , t_mos , t_rotor = self ._decode_motor_state (msg .data , motor_type )
@@ -578,6 +616,14 @@ def _get_motor_name(self, motor: NameOrID) -> str:
578616 if m .id == motor :
579617 return name
580618 raise ValueError (f"Unknown motor ID: { motor } " )
619+
620+ def _get_motor_recv_id (self , motor : NameOrID ) -> Optional [int ]:
621+ """Get motor recv_id from name or ID."""
622+ motor_name = self ._get_motor_name (motor )
623+ motor_obj = self .motors .get (motor_name )
624+ if motor_obj and hasattr (motor_obj , "recv_id" ):
625+ return motor_obj .recv_id
626+ return None
581627
582628 @cached_property
583629 def is_calibrated (self ) -> bool :
0 commit comments