@@ -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