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 random
00036 import time
00037 
00038 from python_qt_binding.QtCore import Slot, QSignalMapper, QTimer, qWarning
00039 
00040 import roslib
00041 import rospy
00042 import genpy
00043 from rqt_gui_py.plugin import Plugin
00044 from .publisher_widget import PublisherWidget
00045 from rqt_py_common.topic_helpers import get_field_type
00046 
00047 
00048 class Publisher(Plugin):
00049 
00050     def __init__(self, context):
00051         super(Publisher, self).__init__(context)
00052         self.setObjectName('Publisher')
00053 
00054         
00055         self._widget = PublisherWidget()
00056         self._widget.add_publisher.connect(self.add_publisher)
00057         self._widget.change_publisher.connect(self.change_publisher)
00058         self._widget.publish_once.connect(self.publish_once)
00059         self._widget.remove_publisher.connect(self.remove_publisher)
00060         self._widget.clean_up_publishers.connect(self.clean_up_publishers)
00061         if context.serial_number() > 1:
00062             self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
00063 
00064         
00065         self._eval_locals = {'i': 0}
00066         for module in (math, random, time):
00067             self._eval_locals.update(module.__dict__)
00068         self._eval_locals['genpy'] = genpy
00069         del self._eval_locals['__name__']
00070         del self._eval_locals['__doc__']
00071 
00072         self._publishers = {}
00073         self._id_counter = 0
00074 
00075         self._timeout_mapper = QSignalMapper(self)
00076         self._timeout_mapper.mapped[int].connect(self.publish_once)
00077 
00078         
00079         context.add_widget(self._widget)
00080 
00081     @Slot(str, str, float, bool)
00082     def add_publisher(self, topic_name, type_name, rate, enabled):
00083         publisher_info = {
00084             'topic_name': str(topic_name),
00085             'type_name': str(type_name),
00086             'rate': float(rate),
00087             'enabled': bool(enabled),
00088         }
00089         self._add_publisher(publisher_info)
00090 
00091     def _add_publisher(self, publisher_info):
00092         publisher_info['publisher_id'] = self._id_counter
00093         self._id_counter += 1
00094         publisher_info['counter'] = 0
00095         publisher_info['enabled'] = publisher_info.get('enabled', False)
00096         publisher_info['expressions'] = publisher_info.get('expressions', {})
00097 
00098         publisher_info['message_instance'] = self._create_message_instance(publisher_info['type_name'])
00099         if publisher_info['message_instance'] is None:
00100             return
00101 
00102         
00103         try:
00104             publisher_info['publisher'] = rospy.Publisher(publisher_info['topic_name'], type(publisher_info['message_instance']), queue_size=100)
00105         except TypeError:
00106             publisher_info['publisher'] = rospy.Publisher(publisher_info['topic_name'], type(publisher_info['message_instance']))
00107         publisher_info['timer'] = QTimer(self)
00108 
00109         
00110         self._publishers[publisher_info['publisher_id']] = publisher_info
00111         self._timeout_mapper.setMapping(publisher_info['timer'], publisher_info['publisher_id'])
00112         publisher_info['timer'].timeout.connect(self._timeout_mapper.map)
00113         if publisher_info['enabled'] and publisher_info['rate'] > 0:
00114             publisher_info['timer'].start(int(1000.0 / publisher_info['rate']))
00115 
00116         self._widget.publisher_tree_widget.model().add_publisher(publisher_info)
00117 
00118     @Slot(int, str, str, str, object)
00119     def change_publisher(self, publisher_id, topic_name, column_name, new_value, setter_callback):
00120         handler = getattr(self, '_change_publisher_%s' % column_name, None)
00121         if handler is not None:
00122             new_text = handler(self._publishers[publisher_id], topic_name, new_value)
00123             if new_text is not None:
00124                 setter_callback(new_text)
00125 
00126     def _change_publisher_topic(self, publisher_info, topic_name, new_value):
00127         publisher_info['enabled'] = (new_value and new_value.lower() in ['1', 'true', 'yes'])
00128         
00129         if publisher_info['enabled'] and publisher_info['rate'] > 0:
00130             publisher_info['timer'].start(int(1000.0 / publisher_info['rate']))
00131         else:
00132             publisher_info['timer'].stop()
00133         return None
00134 
00135     def _change_publisher_type(self, publisher_info, topic_name, new_value):
00136         type_name = new_value
00137         
00138         slot_value = self._create_message_instance(type_name)
00139 
00140         
00141         slot_path = topic_name[len(publisher_info['topic_name']):].strip('/').split('/')
00142         parent_slot = eval('.'.join(["publisher_info['message_instance']"] + slot_path[:-1]))
00143 
00144         
00145         slot_name = slot_path[-1]
00146         slot_index = parent_slot.__slots__.index(slot_name)
00147 
00148         
00149         if slot_value is None:
00150             qWarning('Publisher._change_publisher_type(): could not find type: %s' % (type_name))
00151             return parent_slot._slot_types[slot_index]
00152 
00153         else:
00154             
00155             parent_slot._slot_types[slot_index] = type_name
00156             setattr(parent_slot, slot_name, slot_value)
00157 
00158             self._widget.publisher_tree_widget.model().update_publisher(publisher_info)
00159 
00160     def _change_publisher_rate(self, publisher_info, topic_name, new_value):
00161         try:
00162             rate = float(new_value)
00163         except Exception:
00164             qWarning('Publisher._change_publisher_rate(): could not parse rate value: %s' % (new_value))
00165         else:
00166             publisher_info['rate'] = rate
00167             
00168             publisher_info['timer'].stop()
00169             if publisher_info['enabled'] and publisher_info['rate'] > 0:
00170                 publisher_info['timer'].start(int(1000.0 / publisher_info['rate']))
00171         
00172         return '%.2f' % publisher_info['rate']
00173 
00174     def _change_publisher_expression(self, publisher_info, topic_name, new_value):
00175         expression = str(new_value)
00176         if len(expression) == 0:
00177             if topic_name in publisher_info['expressions']:
00178                 del publisher_info['expressions'][topic_name]
00179                 
00180         else:
00181             slot_type, is_array = get_field_type(topic_name)
00182             if is_array:
00183                 slot_type = list
00184             
00185             error_prefix = '# error'
00186             error_prefix_pos = expression.find(error_prefix)
00187             if error_prefix_pos >= 0:
00188                 expression = expression[:error_prefix_pos]
00189             success, _ = self._evaluate_expression(expression, slot_type)
00190             if success:
00191                 old_expression = publisher_info['expressions'].get(topic_name, None)
00192                 publisher_info['expressions'][topic_name] = expression
00193                 
00194                 self._fill_message_slots(publisher_info['message_instance'], publisher_info['topic_name'], publisher_info['expressions'], publisher_info['counter'])
00195                 try:
00196                     publisher_info['message_instance']._check_types()
00197                 except Exception, e:
00198                     error_str = str(e)
00199                     print 'serialization error:', error_str
00200                     if old_expression is not None:
00201                         publisher_info['expressions'][topic_name] = old_expression
00202                     else:
00203                         del publisher_info['expressions'][topic_name]
00204                     return '%s %s: %s' % (expression, error_prefix, error_str)
00205                 return expression
00206             else:
00207                 return '%s %s evaluating as "%s"' % (expression, error_prefix, slot_type.__name__)
00208 
00209     def _extract_array_info(self, type_str):
00210         array_size = None
00211         if '[' in type_str and type_str[-1] == ']':
00212             type_str, array_size_str = type_str.split('[', 1)
00213             array_size_str = array_size_str[:-1]
00214             if len(array_size_str) > 0:
00215                 array_size = int(array_size_str)
00216             else:
00217                 array_size = 0
00218 
00219         return type_str, array_size
00220 
00221     def _create_message_instance(self, type_str):
00222         base_type_str, array_size = self._extract_array_info(type_str)
00223 
00224         base_message_type = roslib.message.get_message_class(base_type_str)
00225         if base_message_type is None:
00226             print 'Could not create message of type "%s".' % base_type_str
00227             return None
00228 
00229         if array_size is not None:
00230             message = []
00231             for _ in range(array_size):
00232                 message.append(base_message_type())
00233         else:
00234             message = base_message_type()
00235         return message
00236 
00237     def _evaluate_expression(self, expression, slot_type):
00238         successful_eval = True
00239 
00240         try:
00241             
00242             value = eval(expression, {}, self._eval_locals)
00243         except Exception:
00244             successful_eval = False
00245 
00246         if slot_type is str:
00247             if successful_eval:
00248                 value = str(value)
00249             else:
00250                 
00251                 value = str(expression)
00252             successful_eval = True
00253 
00254         elif successful_eval:
00255             type_set = set((slot_type, type(value)))
00256             
00257             
00258             if type_set <= set((list, tuple)) or type_set <= set((int, float)):
00259                 
00260                 value = slot_type(value)
00261 
00262         if successful_eval and isinstance(value, slot_type):
00263             return True, value
00264         else:
00265             qWarning('Publisher._evaluate_expression(): failed to evaluate expression: "%s" as Python type "%s"' % (expression, slot_type.__name__))
00266 
00267         return False, None
00268 
00269     def _fill_message_slots(self, message, topic_name, expressions, counter):
00270         if topic_name in expressions and len(expressions[topic_name]) > 0:
00271 
00272             
00273             if hasattr(message, '_type'):
00274                 message_type = message._type
00275             else:
00276                 message_type = type(message)
00277 
00278             self._eval_locals['i'] = counter
00279             success, value = self._evaluate_expression(expressions[topic_name], message_type)
00280             if not success:
00281                 value = message_type()
00282             return value
00283 
00284         
00285         elif hasattr(message, '__slots__'):
00286             for slot_name in message.__slots__:
00287                 value = self._fill_message_slots(getattr(message, slot_name), topic_name + '/' + slot_name, expressions, counter)
00288                 if value is not None:
00289                     setattr(message, slot_name, value)
00290 
00291         elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
00292             for index, slot in enumerate(message):
00293                 self._fill_message_slots(slot, topic_name + '[%d]' % index, expressions, counter)
00294 
00295         return None
00296 
00297     @Slot(int)
00298     def publish_once(self, publisher_id):
00299         publisher_info = self._publishers.get(publisher_id, None)
00300         if publisher_info is not None:
00301             publisher_info['counter'] += 1
00302             self._fill_message_slots(publisher_info['message_instance'], publisher_info['topic_name'], publisher_info['expressions'], publisher_info['counter'])
00303             publisher_info['publisher'].publish(publisher_info['message_instance'])
00304 
00305     @Slot(int)
00306     def remove_publisher(self, publisher_id):
00307         publisher_info = self._publishers.get(publisher_id, None)
00308         if publisher_info is not None:
00309             publisher_info['timer'].stop()
00310             publisher_info['publisher'].unregister()
00311             del self._publishers[publisher_id]
00312 
00313     def save_settings(self, plugin_settings, instance_settings):
00314         publisher_copies = []
00315         for publisher in self._publishers.values():
00316             publisher_copy = {}
00317             publisher_copy.update(publisher)
00318             publisher_copy['enabled'] = False
00319             del publisher_copy['timer']
00320             del publisher_copy['message_instance']
00321             del publisher_copy['publisher']
00322             publisher_copies.append(publisher_copy)
00323         instance_settings.set_value('publishers', repr(publisher_copies))
00324 
00325     def restore_settings(self, plugin_settings, instance_settings):
00326         publishers = eval(instance_settings.value('publishers', '[]'))
00327         for publisher in publishers:
00328             self._add_publisher(publisher)
00329 
00330     def clean_up_publishers(self):
00331         self._widget.publisher_tree_widget.model().clear()
00332         for publisher_info in self._publishers.values():
00333             publisher_info['timer'].stop()
00334             publisher_info['publisher'].unregister()
00335         self._publishers = {}
00336 
00337     def shutdown_plugin(self):
00338         self._widget.shutdown_plugin()
00339         self.clean_up_publishers()