@@ -44,6 +44,7 @@ def __init__(self, motor_id, motor_name="Motor", default_angle=0, parent=None):
4444 self .circle_center = QPoint (self .radius + 20 , self .radius + 20 )
4545 self .dragging = False
4646 self .torque_enabled = False
47+ self .motor_connected = False # Track motor connection status
4748 self .setMinimumSize (2 * (self .radius + 40 ), 2 * (self .radius + 60 ))
4849
4950 # Create layout for the control buttons
@@ -85,11 +86,15 @@ def paintCircleWidget(self, event):
8586 painter = QPainter (self .circle_widget )
8687 painter .setRenderHint (QPainter .Antialiasing )
8788
88- # Draw circle
89- painter .setPen (QPen (Qt .black , 2 ))
90- if self .torque_enabled :
89+ # Draw circle with connection status
90+ if not self .motor_connected :
91+ painter .setPen (QPen (Qt .red , 3 )) # Red border when disconnected
92+ painter .setBrush (QBrush (QColor (255 , 220 , 220 ))) # Light red when disconnected
93+ elif self .torque_enabled :
94+ painter .setPen (QPen (Qt .black , 2 ))
9195 painter .setBrush (QBrush (QColor (230 , 230 , 255 ))) # Light blue when torque enabled
9296 else :
97+ painter .setPen (QPen (Qt .black , 2 ))
9398 painter .setBrush (QBrush (QColor (240 , 240 , 240 ))) # Light gray when disabled
9499 circle_rect = QRect (20 , 20 , 2 * self .radius , 2 * self .radius )
95100 painter .drawEllipse (circle_rect )
@@ -99,8 +104,11 @@ def paintCircleWidget(self, event):
99104 name_rect = QRect (20 , 2 * self .radius + 30 , 2 * self .radius , 30 )
100105 painter .drawText (name_rect , Qt .AlignCenter , self .motor_name )
101106
102- # Draw angle text
103- angle_text = f"{ self .angle :.1f} ° (Pos: { self .position } )"
107+ # Draw angle text with connection status
108+ if self .motor_connected :
109+ angle_text = f"{ self .angle :.1f} ° (Pos: { self .position } )"
110+ else :
111+ angle_text = f"{ self .angle :.1f} ° (DISCONNECTED)"
104112 painter .drawText (name_rect .translated (0 , 25 ), Qt .AlignCenter , angle_text )
105113
106114 # Draw line from center to edge (like a clock hand)
@@ -185,6 +193,7 @@ def set_position_from_motor(self, position):
185193 # Update UI from motor position
186194 self .position = position
187195 self .angle = (position * DEGREES_PER_POSITION ) % 360
196+ self .motor_connected = True # Mark motor as connected when we receive position
188197 self .circle_widget .update ()
189198
190199
@@ -237,8 +246,12 @@ def get_motor_position(self, motor_id, motor_widget):
237246 """Get position for a specific motor"""
238247 client = self .node .create_client (GetPosition , 'get_position' )
239248
240- while not client .wait_for_service (timeout_sec = 1.0 ):
241- self .node .get_logger ().info ('Waiting for get_position service...' )
249+ # Check if service is available with a short timeout (non-blocking)
250+ if not client .wait_for_service (timeout_sec = 0.5 ):
251+ self .node .get_logger ().warning (f'get_position service not available for motor ID { motor_id } . UI will show default positions.' )
252+ # Set a timer to retry later
253+ self .create_retry_timer (motor_id , motor_widget )
254+ return
242255
243256 request = GetPosition .Request ()
244257 request .id = motor_id
@@ -248,14 +261,28 @@ def get_motor_position(self, motor_id, motor_widget):
248261 lambda f : self .process_position_response (f , motor_widget )
249262 )
250263
264+ def create_retry_timer (self , motor_id , motor_widget ):
265+ """Create a timer to retry reading motor position later"""
266+ def retry_callback ():
267+ self .node .get_logger ().info (f'Retrying position read for motor ID { motor_id } ...' )
268+ # Cancel this timer first
269+ timer .cancel ()
270+ # Then retry the position read
271+ self .get_motor_position (motor_id , motor_widget )
272+
273+ # Create a one-shot timer to retry after 5 seconds
274+ timer = self .node .create_timer (5.0 , retry_callback )
275+
251276 def process_position_response (self , future , motor_widget ):
252277 """Process the response from the get_position service"""
253278 try :
254279 response = future .result ()
255280 self .node .get_logger ().info (f'Received position { response .position } for motor ID { motor_widget .motor_id } ' )
256281 motor_widget .set_position_from_motor (response .position )
257282 except Exception as e :
258- self .node .get_logger ().error (f'Service call failed: { e } ' )
283+ self .node .get_logger ().error (f'Service call failed for motor ID { motor_widget .motor_id } : { e } ' )
284+ # Retry after a delay if the service call failed
285+ self .create_retry_timer (motor_widget .motor_id , motor_widget )
259286
260287
261288class DynamixelUINode (Node ):
0 commit comments