00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import division
00034 import math
00035 import os
00036 import random
00037 import time
00038
00039 from python_qt_binding import loadUi
00040 from python_qt_binding.QtCore import Qt, Slot, qWarning
00041 from python_qt_binding.QtGui import QIcon
00042 from python_qt_binding.QtWidgets import QMenu, QTreeWidgetItem, QWidget
00043
00044 import rospkg
00045 import rospy
00046 import genpy
00047 import rosservice
00048
00049 from rqt_py_common.extended_combo_box import ExtendedComboBox
00050
00051
00052 class ServiceCallerWidget(QWidget):
00053 column_names = ['service', 'type', 'expression']
00054
00055 def __init__(self):
00056 super(ServiceCallerWidget, self).__init__()
00057 self.setObjectName('ServiceCallerWidget')
00058
00059
00060 self._eval_locals = {}
00061 for module in (math, random, time):
00062 self._eval_locals.update(module.__dict__)
00063 self._eval_locals['genpy'] = genpy
00064 del self._eval_locals['__name__']
00065 del self._eval_locals['__doc__']
00066
00067 rp = rospkg.RosPack()
00068 ui_file = os.path.join(rp.get_path('rqt_service_caller'), 'resource', 'ServiceCaller.ui')
00069 loadUi(ui_file, self, {'ExtendedComboBox': ExtendedComboBox})
00070 self.refresh_services_button.setIcon(QIcon.fromTheme('view-refresh'))
00071 self.call_service_button.setIcon(QIcon.fromTheme('call-start'))
00072
00073 self._column_index = {}
00074 for column_name in self.column_names:
00075 self._column_index[column_name] = len(self._column_index)
00076
00077 self._service_info = None
00078 self.on_refresh_services_button_clicked()
00079
00080 self.request_tree_widget.itemChanged.connect(self.request_tree_widget_itemChanged)
00081
00082 def save_settings(self, plugin_settings, instance_settings):
00083 instance_settings.set_value('splitter_orientation', self.splitter.orientation())
00084
00085 def restore_settings(self, plugin_settings, instance_settings):
00086 if int(instance_settings.value('splitter_orientation', Qt.Vertical)) == int(Qt.Vertical):
00087 self.splitter.setOrientation(Qt.Vertical)
00088 else:
00089 self.splitter.setOrientation(Qt.Horizontal)
00090
00091 def trigger_configuration(self):
00092 new_orientation = Qt.Vertical if self.splitter.orientation() == Qt.Horizontal else Qt.Horizontal
00093 self.splitter.setOrientation(new_orientation)
00094
00095 @Slot()
00096 def on_refresh_services_button_clicked(self):
00097 service_names = rosservice.get_service_list()
00098 self._services = {}
00099 for service_name in service_names:
00100 try:
00101 self._services[service_name] = rosservice.get_service_class_by_name(service_name)
00102
00103 except (rosservice.ROSServiceException, rosservice.ROSServiceIOException) as e:
00104 qWarning('ServiceCaller.on_refresh_services_button_clicked(): could not get class of service %s:\n%s' % (service_name, e))
00105 except Exception as e:
00106 qWarning('ServiceCaller.on_refresh_services_button_clicked(): failed to load class of service %s:\n%s' % (service_name, e))
00107
00108 self.service_combo_box.clear()
00109 self.service_combo_box.addItems(sorted(self._services.keys()))
00110
00111 @Slot(str)
00112 def on_service_combo_box_currentIndexChanged(self, service_name):
00113 self.request_tree_widget.clear()
00114 self.response_tree_widget.clear()
00115 service_name = str(service_name)
00116 if not service_name:
00117 return
00118
00119 self._service_info = {}
00120 self._service_info['service_name'] = service_name
00121 self._service_info['service_class'] = self._services[service_name]
00122 self._service_info['service_proxy'] = rospy.ServiceProxy(service_name, self._service_info['service_class'])
00123 self._service_info['expressions'] = {}
00124 self._service_info['counter'] = 0
00125
00126
00127 request_class = self._service_info['service_class']._request_class
00128 top_level_item = self._recursive_create_widget_items(None, service_name, request_class._type, request_class())
00129
00130
00131 self.request_tree_widget.addTopLevelItem(top_level_item)
00132
00133
00134 self.request_tree_widget.expandAll()
00135 for i in range(self.request_tree_widget.columnCount()):
00136 self.request_tree_widget.resizeColumnToContents(i)
00137
00138 def _recursive_create_widget_items(self, parent, topic_name, type_name, message, is_editable=True):
00139 item = QTreeWidgetItem(parent)
00140 if is_editable:
00141 item.setFlags(item.flags() | Qt.ItemIsEditable)
00142 else:
00143 item.setFlags(item.flags() & (~Qt.ItemIsEditable))
00144
00145 if parent is None:
00146
00147 topic_text = topic_name
00148 else:
00149 topic_text = topic_name.split('/')[-1]
00150
00151 item.setText(self._column_index['service'], topic_text)
00152 item.setText(self._column_index['type'], type_name)
00153
00154 item.setData(0, Qt.UserRole, topic_name)
00155
00156 if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00157 for slot_name, type_name in zip(message.__slots__, message._slot_types):
00158 self._recursive_create_widget_items(item, topic_name + '/' + slot_name, type_name, getattr(message, slot_name), is_editable)
00159
00160 elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
00161 type_name = type_name.split('[', 1)[0]
00162 for index, slot in enumerate(message):
00163 self._recursive_create_widget_items(item, topic_name + '[%d]' % index, type_name, slot, is_editable)
00164
00165 else:
00166 item.setText(self._column_index['expression'], repr(message))
00167
00168 return item
00169
00170 @Slot('QTreeWidgetItem*', int)
00171 def request_tree_widget_itemChanged(self, item, column):
00172 column_name = self.column_names[column]
00173 new_value = str(item.text(column))
00174
00175
00176 if column_name == 'expression':
00177 topic_name = str(item.data(0, Qt.UserRole))
00178 self._service_info['expressions'][topic_name] = new_value
00179
00180
00181 def fill_message_slots(self, message, topic_name, expressions, counter):
00182 if not hasattr(message, '__slots__'):
00183 return
00184 for slot_name in message.__slots__:
00185 slot_key = topic_name + '/' + slot_name
00186
00187
00188 if slot_key not in expressions:
00189 self.fill_message_slots(getattr(message, slot_name), slot_key, expressions, counter)
00190 continue
00191
00192 expression = expressions[slot_key]
00193 if len(expression) == 0:
00194 continue
00195
00196
00197 slot = getattr(message, slot_name)
00198 if hasattr(slot, '_type'):
00199 slot_type = slot._type
00200 else:
00201 slot_type = type(slot)
00202
00203 self._eval_locals['i'] = counter
00204 value = self._evaluate_expression(expression, slot_type)
00205 if value is not None:
00206 setattr(message, slot_name, value)
00207
00208 def _evaluate_expression(self, expression, slot_type):
00209 successful_eval = True
00210 successful_conversion = True
00211
00212 try:
00213
00214 value = eval(expression, {}, self._eval_locals)
00215 except Exception:
00216
00217 value = expression
00218 successful_eval = False
00219
00220 try:
00221
00222 value = slot_type(value)
00223 except Exception:
00224 successful_conversion = False
00225
00226 if successful_conversion:
00227 return value
00228 elif successful_eval:
00229 qWarning('ServiceCaller.fill_message_slots(): can not convert expression to slot type: %s -> %s' % (type(value), slot_type))
00230 else:
00231 qWarning('ServiceCaller.fill_message_slots(): failed to evaluate expression: %s' % (expression))
00232
00233 return None
00234
00235 @Slot()
00236 def on_call_service_button_clicked(self):
00237 self.response_tree_widget.clear()
00238
00239 request = self._service_info['service_class']._request_class()
00240 self.fill_message_slots(request, self._service_info['service_name'], self._service_info['expressions'], self._service_info['counter'])
00241 try:
00242 response = self._service_info['service_proxy'](request)
00243 except rospy.ServiceException as e:
00244 qWarning('ServiceCaller.on_call_service_button_clicked(): request:\n%r' % (request))
00245 qWarning('ServiceCaller.on_call_service_button_clicked(): error calling service "%s":\n%s' % (self._service_info['service_name'], e))
00246 top_level_item = QTreeWidgetItem()
00247 top_level_item.setText(self._column_index['service'], 'ERROR')
00248 top_level_item.setText(self._column_index['type'], 'rospy.ServiceException')
00249 top_level_item.setText(self._column_index['expression'], str(e))
00250 else:
00251
00252 top_level_item = self._recursive_create_widget_items(None, '/', response._type, response, is_editable=False)
00253
00254 self.response_tree_widget.addTopLevelItem(top_level_item)
00255
00256 self.response_tree_widget.expandAll()
00257 for i in range(self.response_tree_widget.columnCount()):
00258 self.response_tree_widget.resizeColumnToContents(i)
00259
00260 @Slot('QPoint')
00261 def on_request_tree_widget_customContextMenuRequested(self, pos):
00262 self._show_context_menu(self.request_tree_widget.itemAt(pos), self.request_tree_widget.mapToGlobal(pos))
00263
00264 @Slot('QPoint')
00265 def on_response_tree_widget_customContextMenuRequested(self, pos):
00266 self._show_context_menu(self.response_tree_widget.itemAt(pos), self.response_tree_widget.mapToGlobal(pos))
00267
00268 def _show_context_menu(self, item, global_pos):
00269 if item is None:
00270 return
00271
00272
00273 menu = QMenu(self)
00274 action_item_expand = menu.addAction(QIcon.fromTheme('zoom-in'), "Expand All Children")
00275 action_item_collapse = menu.addAction(QIcon.fromTheme('zoom-out'), "Collapse All Children")
00276 action = menu.exec_(global_pos)
00277
00278
00279 if action in (action_item_expand, action_item_collapse):
00280 expanded = (action is action_item_expand)
00281
00282 def recursive_set_expanded(item):
00283 item.setExpanded(expanded)
00284 for index in range(item.childCount()):
00285 recursive_set_expanded(item.child(index))
00286 recursive_set_expanded(item)
00287