service_caller_widget.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Copyright (c) 2011, Dorian Scholz, TU Darmstadt
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #   * Redistributions of source code must retain the above copyright
00011 #     notice, this list of conditions and the following disclaimer.
00012 #   * Redistributions in binary form must reproduce the above
00013 #     copyright notice, this list of conditions and the following
00014 #     disclaimer in the documentation and/or other materials provided
00015 #     with the distribution.
00016 #   * Neither the name of the TU Darmstadt nor the names of its
00017 #     contributors may be used to endorse or promote products derived
00018 #     from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
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         # create context for the expression eval statement
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('current_service_name', self._service_info['service_name'])
00084         instance_settings.set_value('splitter_orientation', self.splitter.orientation())
00085 
00086     def restore_settings(self, plugin_settings, instance_settings):
00087         current_service_name = instance_settings.value('current_service_name', None)
00088         if current_service_name:
00089             current_service_index = self.service_combo_box.findData(
00090                 current_service_name, Qt.DisplayRole)
00091             if current_service_index != -1:
00092                 self.service_combo_box.setCurrentIndex(current_service_index)
00093 
00094         if int(instance_settings.value('splitter_orientation', Qt.Vertical)) == int(Qt.Vertical):
00095             self.splitter.setOrientation(Qt.Vertical)
00096         else:
00097             self.splitter.setOrientation(Qt.Horizontal)
00098 
00099     def trigger_configuration(self):
00100         new_orientation = \
00101             Qt.Vertical if self.splitter.orientation() == Qt.Horizontal else Qt.Horizontal
00102         self.splitter.setOrientation(new_orientation)
00103 
00104     @Slot()
00105     def on_refresh_services_button_clicked(self):
00106         service_names = rosservice.get_service_list()
00107         self._services = {}
00108         for service_name in service_names:
00109             try:
00110                 self._services[service_name] = rosservice.get_service_class_by_name(service_name)
00111                 # qDebug('ServiceCaller.on_refresh_services_button_clicked(): found
00112                 # service %s using class %s' % (service_name,
00113                 # self._services[service_name]))
00114             except (rosservice.ROSServiceException, rosservice.ROSServiceIOException) as e:
00115                 qWarning(
00116                     'ServiceCaller.on_refresh_services_button_clicked(): could not get class of service %s:\n%s' %
00117                     (service_name, e))
00118             except Exception as e:
00119                 qWarning(
00120                     'ServiceCaller.on_refresh_services_button_clicked(): failed to load class of service %s:\n%s' %
00121                     (service_name, e))
00122 
00123         self.service_combo_box.clear()
00124         self.service_combo_box.addItems(sorted(self._services.keys()))
00125 
00126     @Slot(str)
00127     def on_service_combo_box_currentIndexChanged(self, service_name):
00128         self.request_tree_widget.clear()
00129         self.response_tree_widget.clear()
00130         service_name = str(service_name)
00131         if not service_name:
00132             return
00133 
00134         self._service_info = {}
00135         self._service_info['service_name'] = service_name
00136         self._service_info['service_class'] = self._services[service_name]
00137         self._service_info['service_proxy'] = rospy.ServiceProxy(
00138             service_name, self._service_info['service_class'])
00139         self._service_info['expressions'] = {}
00140         self._service_info['counter'] = 0
00141 
00142         # recursively create widget items for the service request's slots
00143         request_class = self._service_info['service_class']._request_class
00144         top_level_item = self._recursive_create_widget_items(
00145             None, service_name, request_class._type, request_class())
00146 
00147         # add top level item to tree widget
00148         self.request_tree_widget.addTopLevelItem(top_level_item)
00149 
00150         # resize columns
00151         self.request_tree_widget.expandAll()
00152         for i in range(self.request_tree_widget.columnCount()):
00153             self.request_tree_widget.resizeColumnToContents(i)
00154 
00155     def _recursive_create_widget_items(self, parent, topic_name, type_name, message, is_editable=True):
00156         item = QTreeWidgetItem(parent)
00157         if is_editable:
00158             item.setFlags(item.flags() | Qt.ItemIsEditable)
00159         else:
00160             item.setFlags(item.flags() & (~Qt.ItemIsEditable))
00161 
00162         if parent is None:
00163             # show full topic name with preceding namespace on toplevel item
00164             topic_text = topic_name
00165         else:
00166             topic_text = topic_name.split('/')[-1]
00167 
00168         item.setText(self._column_index['service'], topic_text)
00169         item.setText(self._column_index['type'], type_name)
00170 
00171         item.setData(0, Qt.UserRole, topic_name)
00172 
00173         if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00174             for slot_name, type_name in zip(message.__slots__, message._slot_types):
00175                 self._recursive_create_widget_items(item, topic_name + '/' + slot_name,
00176                                                     type_name, getattr(message, slot_name), is_editable)
00177 
00178         elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
00179             type_name = type_name.split('[', 1)[0]
00180             for index, slot in enumerate(message):
00181                 self._recursive_create_widget_items(
00182                     item, topic_name + '[%d]' % index, type_name, slot, is_editable)
00183 
00184         else:
00185             item.setText(self._column_index['expression'], repr(message))
00186 
00187         return item
00188 
00189     @Slot('QTreeWidgetItem*', int)
00190     def request_tree_widget_itemChanged(self, item, column):
00191         column_name = self.column_names[column]
00192         new_value = str(item.text(column))
00193         # qDebug(
00194         #   'ServiceCaller.request_tree_widget_itemChanged(): %s : %s' %
00195         #   (column_name, new_value))
00196 
00197         if column_name == 'expression':
00198             topic_name = str(item.data(0, Qt.UserRole))
00199             self._service_info['expressions'][topic_name] = new_value
00200             # qDebug(
00201             #   'ServiceCaller.request_tree_widget_itemChanged(): %s expression: %s' %
00202             #   (topic_name, new_value))
00203 
00204     def fill_message_slots(self, message, topic_name, expressions, counter):
00205         if not hasattr(message, '__slots__'):
00206             return
00207         for slot_name in message.__slots__:
00208             slot_key = topic_name + '/' + slot_name
00209 
00210             # if no expression exists for this slot_key, continue with it's child slots
00211             if slot_key not in expressions:
00212                 self.fill_message_slots(getattr(message, slot_name), slot_key, expressions, counter)
00213                 continue
00214 
00215             expression = expressions[slot_key]
00216             if len(expression) == 0:
00217                 continue
00218 
00219             # get slot type
00220             slot = getattr(message, slot_name)
00221             if hasattr(slot, '_type'):
00222                 slot_type = slot._type
00223             else:
00224                 slot_type = type(slot)
00225 
00226             self._eval_locals['i'] = counter
00227             value = self._evaluate_expression(expression, slot_type)
00228             if value is not None:
00229                 setattr(message, slot_name, value)
00230 
00231     def _evaluate_expression(self, expression, slot_type):
00232         successful_eval = True
00233         successful_conversion = True
00234 
00235         try:
00236             # try to evaluate expression
00237             value = eval(expression, {}, self._eval_locals)
00238         except Exception:
00239             # just use expression-string as value
00240             value = expression
00241             successful_eval = False
00242 
00243         try:
00244             # try to convert value to right type
00245             value = slot_type(value)
00246         except Exception:
00247             successful_conversion = False
00248 
00249         if successful_conversion:
00250             return value
00251         elif successful_eval:
00252             qWarning(
00253                 'ServiceCaller.fill_message_slots(): can not convert expression to slot type: %s -> %s' %
00254                 (type(value), slot_type))
00255         else:
00256             qWarning('ServiceCaller.fill_message_slots(): failed to evaluate expression: %s' %
00257                      (expression))
00258 
00259         return None
00260 
00261     @Slot()
00262     def on_call_service_button_clicked(self):
00263         self.response_tree_widget.clear()
00264 
00265         request = self._service_info['service_class']._request_class()
00266         self.fill_message_slots(request, self._service_info['service_name'],
00267                                 self._service_info['expressions'], self._service_info['counter'])
00268         try:
00269             response = self._service_info['service_proxy'](request)
00270         except rospy.ServiceException as e:
00271             qWarning('ServiceCaller.on_call_service_button_clicked(): request:\n%r' % (request))
00272             qWarning('ServiceCaller.on_call_service_button_clicked(): error calling service "%s":\n%s' %
00273                      (self._service_info['service_name'], e))
00274             top_level_item = QTreeWidgetItem()
00275             top_level_item.setText(self._column_index['service'], 'ERROR')
00276             top_level_item.setText(self._column_index['type'], 'rospy.ServiceException')
00277             top_level_item.setText(self._column_index['expression'], str(e))
00278         else:
00279             # qDebug('ServiceCaller.on_call_service_button_clicked(): response: %r' % (response))
00280             top_level_item = self._recursive_create_widget_items(
00281                 None, '/', response._type, response, is_editable=False)
00282 
00283         self.response_tree_widget.addTopLevelItem(top_level_item)
00284         # resize columns
00285         self.response_tree_widget.expandAll()
00286         for i in range(self.response_tree_widget.columnCount()):
00287             self.response_tree_widget.resizeColumnToContents(i)
00288 
00289     @Slot('QPoint')
00290     def on_request_tree_widget_customContextMenuRequested(self, pos):
00291         self._show_context_menu(
00292             self.request_tree_widget.itemAt(pos), self.request_tree_widget.mapToGlobal(pos))
00293 
00294     @Slot('QPoint')
00295     def on_response_tree_widget_customContextMenuRequested(self, pos):
00296         self._show_context_menu(
00297             self.response_tree_widget.itemAt(pos), self.response_tree_widget.mapToGlobal(pos))
00298 
00299     def _show_context_menu(self, item, global_pos):
00300         if item is None:
00301             return
00302 
00303         # show context menu
00304         menu = QMenu(self)
00305         action_item_expand = menu.addAction(QIcon.fromTheme('zoom-in'), "Expand All Children")
00306         action_item_collapse = menu.addAction(QIcon.fromTheme('zoom-out'), "Collapse All Children")
00307         action = menu.exec_(global_pos)
00308 
00309         # evaluate user action
00310         if action in (action_item_expand, action_item_collapse):
00311             expanded = (action is action_item_expand)
00312 
00313             def recursive_set_expanded(item):
00314                 item.setExpanded(expanded)
00315                 for index in range(item.childCount()):
00316                     recursive_set_expanded(item.child(index))
00317             recursive_set_expanded(item)


rqt_service_caller
Author(s): Dorian Scholz
autogenerated on Thu Jun 6 2019 17:29:37