Skip to content

Commit 405f36d

Browse files
committed
Remove async parameter queries that cause threading issues
1 parent e886fdd commit 405f36d

File tree

2 files changed

+34
-102
lines changed

2 files changed

+34
-102
lines changed

coffee_ws/src/coffee_voice_agent_ui/coffee_voice_agent_ui/voice_agent_monitor_app.py

Lines changed: 26 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ def __init__(self):
159159
except:
160160
pass # Icon not critical
161161

162+
# Initialize configuration with reasonable defaults first
163+
self._initialize_simple_config()
164+
162165
# Create central widget and layout
163166
central_widget = QWidget()
164167
self.setCentralWidget(central_widget)
@@ -204,6 +207,13 @@ def _setup_ui(self, central_widget):
204207
main_layout.setColumnStretch(0, 1) # Status/Emotion column
205208
main_layout.setColumnStretch(1, 2) # Conversation/Tools column (wider)
206209
main_layout.setColumnStretch(2, 1) # Analytics/Controls column
210+
211+
# Initialize conversation widget with default configuration
212+
if hasattr(self, 'last_config_update'):
213+
self.conversation_widget.update_configuration(
214+
self.last_config_update['config_data'],
215+
"disconnected" # Start as disconnected
216+
)
207217

208218
def _init_ros(self):
209219
"""Initialize ROS2 node and connections"""
@@ -232,14 +242,6 @@ def _init_ros(self):
232242
self.ros_executor.add_node(self.ros_node)
233243
self.ros_thread = threading.Thread(target=self.ros_executor.spin, daemon=True)
234244
self.ros_thread.start()
235-
236-
# Query and distribute initial configuration (with delay for bridge startup)
237-
QTimer.singleShot(2000, self._query_and_distribute_config) # Wait 2 seconds for bridge to initialize
238-
239-
# Set up periodic config refresh in case bridge connects later
240-
self.config_refresh_timer = QTimer()
241-
self.config_refresh_timer.timeout.connect(self._retry_config_if_fallback)
242-
self.config_refresh_timer.start(10000) # Check every 10 seconds
243245

244246
def _setup_timers(self):
245247
"""Set up update timers for the UI"""
@@ -253,86 +255,16 @@ def _setup_timers(self):
253255
self.analytics_timer.timeout.connect(self._update_analytics)
254256
self.analytics_timer.start(1000) # 1 FPS for analytics
255257

256-
def _query_and_distribute_config(self):
257-
"""Query configuration parameters from bridge and distribute to widgets"""
258-
try:
259-
# Query parameters from the voice agent bridge
260-
config_data = {}
261-
config_source = "fallback"
262-
263-
# Try to get parameters from bridge using parameter client
264-
try:
265-
from rclpy.parameter_client import AsyncParameterClient
266-
267-
# Create parameter client to query bridge node
268-
param_client = AsyncParameterClient(self.ros_node, 'voice_agent_bridge')
269-
270-
# Wait for bridge node to be available (timeout after 5 seconds)
271-
if param_client.wait_for_services(timeout_sec=5.0):
272-
# Query timeout parameters from bridge
273-
parameter_names = ['user_response_timeout', 'max_conversation_time', 'config_received']
274-
275-
# Use async parameter client - get future and wait for result
276-
future = param_client.get_parameters(parameter_names)
277-
278-
# Spin until the future is complete (with timeout)
279-
import time
280-
start_time = time.time()
281-
timeout = 5.0
282-
283-
while not future.done() and (time.time() - start_time) < timeout:
284-
rclpy.spin_once(self.ros_node, timeout_sec=0.1)
285-
286-
if future.done():
287-
parameters = future.result().values
288-
289-
config_data = {
290-
'user_response_timeout': parameters[0].double_value if parameters[0].type == 3 else 15.0, # PARAMETER_DOUBLE = 3
291-
'max_conversation_time': parameters[1].double_value if parameters[1].type == 3 else 180.0
292-
}
293-
294-
# Check if we got real configuration from agent using config_received flag
295-
config_received = parameters[2].bool_value if parameters[2].type == 1 else False # PARAMETER_BOOL = 1
296-
if config_received:
297-
config_source = "agent" # Bridge has received agent configuration
298-
else:
299-
config_source = "fallback" # Bridge still using defaults
300-
301-
self.ros_node.get_logger().info(f"Retrieved configuration from bridge: {config_data} (source: {config_source})")
302-
else:
303-
raise Exception("Parameter query timed out")
304-
else:
305-
raise Exception("Bridge node not available")
306-
307-
except Exception as e:
308-
# Parameters not available, use fallback values
309-
self.ros_node.get_logger().warn(f"Could not query bridge parameters: {e}, using fallback configuration")
310-
config_data = {
311-
'user_response_timeout': 15.0,
312-
'max_conversation_time': 180.0
313-
}
314-
config_source = "fallback"
315-
316-
# Distribute configuration to widgets
317-
if hasattr(self, 'conversation_widget'):
318-
self.conversation_widget.update_configuration(config_data, config_source)
319-
self.ros_node.get_logger().info("Updated conversation widget configuration")
320-
321-
# Add configuration info to analytics data
322-
self.last_config_update = {
323-
'config_data': config_data,
324-
'config_source': config_source,
325-
'timestamp': datetime.now()
326-
}
327-
328-
except Exception as e:
329-
self.ros_node.get_logger().error(f"Error in configuration query and distribution: {e}")
330-
331-
def _retry_config_if_fallback(self):
332-
"""Retry configuration query if still using fallback values"""
333-
if (hasattr(self, 'last_config_update') and
334-
self.last_config_update.get('config_source') == 'fallback'):
335-
self._query_and_distribute_config()
258+
def _initialize_simple_config(self):
259+
"""Initialize configuration with reasonable defaults"""
260+
self.last_config_update = {
261+
'config_data': {
262+
'user_response_timeout': 15.0,
263+
'max_conversation_time': 180.0
264+
},
265+
'config_source': "fallback",
266+
'timestamp': datetime.now()
267+
}
336268

337269
@pyqtSlot(AgentStatus)
338270
def _update_agent_status(self, status):
@@ -356,6 +288,12 @@ def _update_user_speech(self, speech):
356288
def _update_connection_status(self, connected):
357289
"""Update UI with connection status"""
358290
self.agent_status_widget.update_connection(connected)
291+
292+
# Update conversation widget configuration status based on connection
293+
if hasattr(self, 'conversation_widget') and hasattr(self, 'last_config_update'):
294+
config_data = self.last_config_update['config_data']
295+
source = "connected" if connected else "disconnected"
296+
self.conversation_widget.update_configuration(config_data, source)
359297

360298
@pyqtSlot(str)
361299
def _send_virtual_request(self, request_json):
@@ -402,8 +340,6 @@ def closeEvent(self, event):
402340
self.update_timer.stop()
403341
if hasattr(self, 'analytics_timer'):
404342
self.analytics_timer.stop()
405-
if hasattr(self, 'config_refresh_timer'):
406-
self.config_refresh_timer.stop()
407343

408344
# Clean up ROS
409345
if hasattr(self, 'ros_executor'):

coffee_ws/src/coffee_voice_agent_ui/coffee_voice_agent_ui/widgets/conversation_widget.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ def _setup_ui(self):
164164

165165
layout.addLayout(metrics_layout)
166166

167-
def update_configuration(self, config_data, source="agent"):
167+
def update_configuration(self, config_data, source="disconnected"):
168168
"""
169169
Update widget configuration from monitor
170170
171171
Args:
172172
config_data: Dictionary containing configuration values
173-
source: Source of configuration ("agent", "fallback", "parameter_override")
173+
source: Connection status ("connected", "disconnected")
174174
"""
175175
# Update timeout values
176176
if 'user_response_timeout' in config_data:
@@ -183,19 +183,15 @@ def update_configuration(self, config_data, source="agent"):
183183
self.config_source = source
184184
self.config_timestamp = datetime.now()
185185

186-
# Update configuration status display
187-
if source == "agent":
188-
status_text = f"Agent ({self.user_response_timeout:.0f}s, {self.max_conversation_time/60:.0f}m)"
186+
# Update configuration status display based on connection
187+
if source == "connected":
188+
status_text = f"Bridge Connected ({self.user_response_timeout:.0f}s, {self.max_conversation_time/60:.0f}m)"
189189
self.config_status_label.setText(status_text)
190-
self.config_status_label.setStyleSheet("color: #28a745; font-size: 10px;") # Green for agent
191-
elif source == "parameter_override":
192-
status_text = f"Override ({self.user_response_timeout:.0f}s, {self.max_conversation_time/60:.0f}m)"
193-
self.config_status_label.setText(status_text)
194-
self.config_status_label.setStyleSheet("color: #007bff; font-size: 10px;") # Blue for override
190+
self.config_status_label.setStyleSheet("color: #28a745; font-size: 10px;") # Green for connected
195191
else:
196-
status_text = f"Fallback ({self.user_response_timeout:.0f}s, {self.max_conversation_time/60:.0f}m)"
192+
status_text = f"Bridge Disconnected ({self.user_response_timeout:.0f}s, {self.max_conversation_time/60:.0f}m)"
197193
self.config_status_label.setText(status_text)
198-
self.config_status_label.setStyleSheet("color: #ffc107; font-size: 10px;") # Yellow for fallback
194+
self.config_status_label.setStyleSheet("color: #dc3545; font-size: 10px;") # Red for disconnected
199195

200196
def update_agent_state(self, status: AgentStatus):
201197
"""Update conversation state from agent status"""

0 commit comments

Comments
 (0)