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 as e:
00198 print('serialization error: %s' % e)
00199 if old_expression is not None:
00200 publisher_info['expressions'][topic_name] = old_expression
00201 else:
00202 del publisher_info['expressions'][topic_name]
00203 return '%s %s: %s' % (expression, error_prefix, e)
00204 return expression
00205 else:
00206 return '%s %s evaluating as "%s"' % (expression, error_prefix, slot_type.__name__)
00207
00208 def _extract_array_info(self, type_str):
00209 array_size = None
00210 if '[' in type_str and type_str[-1] == ']':
00211 type_str, array_size_str = type_str.split('[', 1)
00212 array_size_str = array_size_str[:-1]
00213 if len(array_size_str) > 0:
00214 array_size = int(array_size_str)
00215 else:
00216 array_size = 0
00217
00218 return type_str, array_size
00219
00220 def _create_message_instance(self, type_str):
00221 base_type_str, array_size = self._extract_array_info(type_str)
00222
00223 base_message_type = roslib.message.get_message_class(base_type_str)
00224 if base_message_type is None:
00225 print('Could not create message of type "%s".' % base_type_str)
00226 return None
00227
00228 if array_size is not None:
00229 message = []
00230 for _ in range(array_size):
00231 message.append(base_message_type())
00232 else:
00233 message = base_message_type()
00234 return message
00235
00236 def _evaluate_expression(self, expression, slot_type):
00237 successful_eval = True
00238
00239 try:
00240
00241 value = eval(expression, {}, self._eval_locals)
00242 except Exception:
00243 successful_eval = False
00244
00245 if slot_type is str:
00246 if successful_eval:
00247 value = str(value)
00248 else:
00249
00250 value = str(expression)
00251 successful_eval = True
00252
00253 elif successful_eval:
00254 type_set = set((slot_type, type(value)))
00255
00256
00257 if type_set <= set((list, tuple)) or type_set <= set((int, float)):
00258
00259 value = slot_type(value)
00260
00261 if successful_eval and isinstance(value, slot_type):
00262 return True, value
00263 else:
00264 qWarning('Publisher._evaluate_expression(): failed to evaluate expression: "%s" as Python type "%s"' % (expression, slot_type.__name__))
00265
00266 return False, None
00267
00268 def _fill_message_slots(self, message, topic_name, expressions, counter):
00269 if topic_name in expressions and len(expressions[topic_name]) > 0:
00270
00271
00272 if hasattr(message, '_type'):
00273 message_type = message._type
00274 else:
00275 message_type = type(message)
00276
00277 self._eval_locals['i'] = counter
00278 success, value = self._evaluate_expression(expressions[topic_name], message_type)
00279 if not success:
00280 value = message_type()
00281 return value
00282
00283
00284 elif hasattr(message, '__slots__'):
00285 for slot_name in message.__slots__:
00286 value = self._fill_message_slots(getattr(message, slot_name), topic_name + '/' + slot_name, expressions, counter)
00287 if value is not None:
00288 setattr(message, slot_name, value)
00289
00290 elif type(message) in (list, tuple) and (len(message) > 0):
00291 for index, slot in enumerate(message):
00292 value = self._fill_message_slots(slot, topic_name + '[%d]' % index, expressions, counter)
00293
00294 if not hasattr(message[0], '__slots__') and value is not None:
00295 message[index] = value
00296
00297 return None
00298
00299 @Slot(int)
00300 def publish_once(self, publisher_id):
00301 publisher_info = self._publishers.get(publisher_id, None)
00302 if publisher_info is not None:
00303 publisher_info['counter'] += 1
00304 self._fill_message_slots(publisher_info['message_instance'], publisher_info['topic_name'], publisher_info['expressions'], publisher_info['counter'])
00305 publisher_info['publisher'].publish(publisher_info['message_instance'])
00306
00307 @Slot(int)
00308 def remove_publisher(self, publisher_id):
00309 publisher_info = self._publishers.get(publisher_id, None)
00310 if publisher_info is not None:
00311 publisher_info['timer'].stop()
00312 publisher_info['publisher'].unregister()
00313 del self._publishers[publisher_id]
00314
00315 def save_settings(self, plugin_settings, instance_settings):
00316 publisher_copies = []
00317 for publisher in self._publishers.values():
00318 publisher_copy = {}
00319 publisher_copy.update(publisher)
00320 publisher_copy['enabled'] = False
00321 del publisher_copy['timer']
00322 del publisher_copy['message_instance']
00323 del publisher_copy['publisher']
00324 publisher_copies.append(publisher_copy)
00325 instance_settings.set_value('publishers', repr(publisher_copies))
00326
00327 def restore_settings(self, plugin_settings, instance_settings):
00328 publishers = eval(instance_settings.value('publishers', '[]'))
00329 for publisher in publishers:
00330 self._add_publisher(publisher)
00331
00332 def clean_up_publishers(self):
00333 self._widget.publisher_tree_widget.model().clear()
00334 for publisher_info in self._publishers.values():
00335 publisher_info['timer'].stop()
00336 publisher_info['publisher'].unregister()
00337 self._publishers = {}
00338
00339 def shutdown_plugin(self):
00340 self._widget.shutdown_plugin()
00341 self.clean_up_publishers()