Skip to content

Commit d9ee7a7

Browse files
committed
Update the dynamixel UI to be non-blocking when ROS service is not launched
1 parent 1c0a22b commit d9ee7a7

File tree

1 file changed

+35
-8
lines changed
  • coffee_ws/src/python_dynamixel_ui/python_dynamixel_ui

1 file changed

+35
-8
lines changed

coffee_ws/src/python_dynamixel_ui/python_dynamixel_ui/ui.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

261288
class DynamixelUINode(Node):

0 commit comments

Comments
 (0)