def get_slot_type(message_class, slot_path): """ Get the Python type of a specific slot in the given message class. If the field is an array, the type of the array's values are returned and the is_array flag is set to True. This is a static type check, so it works for unpublished topics and with empty arrays. :param message_class: message class type, ``type``, usually inherits from genpy.message.Message :param slot_path: path to the slot inside the message class, ``str``, i.e. '_header/_seq' :returns: field_type, is_array """ is_array = False fields = [f for f in slot_path.split('/') if f] for field_name in fields: slot_class_name = message_class.get_fields_and_field_types( )[field_name] array_index = slot_class_name.find('[') if array_index >= 0: is_array = True if is_primitive_type(slot_class_name[:array_index]): message_class = get_type_class(slot_class_name[:array_index]) else: message_class = get_message_class( slot_class_name[:array_index]) else: is_array = False if is_primitive_type(slot_class_name): message_class = get_type_class(slot_class_name) else: message_class = get_message_class(slot_class_name) return message_class, is_array
def _get_field_type(topic_names_and_types, target): # noqa: C901 """Testable helper function for get_field_type.""" logger = logging.get_logger('topic_helpers._get_field_type') delim = '/' tokenized_target = target.strip(delim).split(delim) for topic_name, types in topic_names_and_types: tokenized_topic_name = topic_name.strip(delim).split(delim) # If the target starts with the topic we have found a potential match if tokenized_target[:len(tokenized_topic_name )] == tokenized_topic_name: # If the target passed in was the topic address not the address of a field if tokenized_target == tokenized_topic_name: # If there is more than one type of topic on target if len(types) > 1: logger.warn( 'Ambiguous request. Multiple topic types found on: {}'. format(target)) return None, False # If the types array is empty then something weird has happend if len(types) == 0: logger.warn('No msg types found on: {}'.format(target)) return None, False # If there is only one msg type msg_class = get_message_class(types[0]) return msg_class, False else: # The topic must be a substring of the target # Get the address of the field in the messgage class field_address = target[len(topic_name):] # Iterate through the message types on the given topic and see if any match the # path that was provided for msg_type_str in types: try: msg_class = get_message_class(msg_type_str) field_type, is_array = get_slot_type( msg_class, field_address) return field_type, is_array except KeyError: pass logger.debug('faild to find field type: {}'.format(target)) return None, False
def fill_message_slots(self, message, topic_name, expressions, counter): # qDebug( # 'fill_message_slots(' # '\n\tmessage={}, \n\ttopic_name={}, \n\texpressions={}, \n\tcounter={})'.format( # message, topic_name, expressions, counter)) if type(message) in (list, set): for i, msg in enumerate(message): slot_key = topic_name + '[{}]'.format(i) if slot_key not in expressions: self.fill_message_slots(msg, slot_key, expressions, counter) continue expression = expressions[slot_key] if len(expression) == 0: continue self._eval_locals['i'] = counter slot_type_class = type(msg) is_array = False if slot_type_class in (list, tuple): is_array = True value = self._evaluate_expression(expression, None, is_array) if value is not None: message[i] = value elif hasattr(message, 'get_fields_and_field_types'): for slot_name, slot_type in message.get_fields_and_field_types( ).items(): slot_key = topic_name + '/' + slot_name # if no expression exists for this slot_key, continue with it's child slots if slot_key not in expressions: self.fill_message_slots(getattr(message, slot_name), slot_key, expressions, counter) continue expression = expressions[slot_key] if len(expression) == 0: continue self._eval_locals['i'] = counter slot_type_class = None slot_type_no_array = slot_type.split('[', 1)[0] is_array = slot_type.find('[') >= 0 if is_primitive_type(slot_type_no_array): slot_type_class = get_type_class(slot_type_no_array) else: slot_type_class = get_message_class(slot_type_no_array) value = self._evaluate_expression(expression, slot_type_class, is_array) if value is not None: try: setattr(message, slot_name, value) except AssertionError as e: qWarning('Failed to set {} to {}\n\t{}'.format( slot_name, value, e.__str__()))
def refresh(self, node): self.clear() topic_list = node.get_topic_names_and_types() for topic_path, topic_types in topic_list: topic_name = topic_path.strip('/') for topic_type in topic_types: message_instance = get_message_class(topic_type)() self.add_message(message_instance, topic_name, topic_type, topic_path)
def _update_thread_run(self): # update type_combo_box message_type_names = [] message_types = get_all_message_types() for package, message_types in message_types.items(): for message_type in message_types: base_type_str = package + '/msg/' + message_type message_class = get_message_class(base_type_str) if message_class is not None: message_type_names.append(base_type_str) self.type_combo_box.setItems.emit(sorted(message_type_names)) # update topic_combo_box topic_names_and_types = self._node.get_topic_names_and_types() self._topic_dict = dict(topic_names_and_types) self.topic_combo_box.setItems.emit(sorted(self._topic_dict.keys()))
def update_topics(self, node): """Update the topics contained in the dictionary with new information from a node.""" # NOTE: This has changed from ROS1 to ROS2 since ROS2 seems to support # multiple msg types on a single topic self.topic_dict = {} # If no node is passed in then we need to start rclpy and create a node # These flags are used to track these changes so that we can restore # state on completion topic_names_and_types = node.get_topic_names_and_types() for topic_name, topic_types in topic_names_and_types: self.topic_dict[topic_name] = [] for topic_type in topic_types: message = get_message_class(topic_type)() self.topic_dict[topic_name].append( self._recursive_create_field_dict(topic_type, message))
def _add_message(self): if self._msgs_combo.count() == 0: return msg = (self._package_combo.currentText() + '/' + self._msgs_combo.currentText()) self._logger.debug('_add_message msg={}'.format(msg)) if self._mode == message_helpers.MSG_MODE: msg_class = get_message_class(msg)() text_tree_root = 'Msg Root' self._messages_tree.model().add_message(msg_class, self.tr(text_tree_root), msg, msg) elif self._mode == message_helpers.SRV_MODE: msg_class = get_service_class(msg) self._messages_tree.model().add_message(msg_class.Request(), self.tr('Service Request'), msg + '/Request', msg + '/Request') self._messages_tree.model().add_message( msg_class.Response(), self.tr('Service Response'), msg + '/Response', msg + '/Response') elif self._mode == message_helpers.ACTION_MODE: action_class = get_action_class(msg) text_tree_root = 'Action Root' self._messages_tree.model().add_message(action_class.Goal(), self.tr('Action Goal'), msg + '/Goal', msg + '/Goal') self._messages_tree.model().add_message(action_class.Result(), self.tr('Action Result'), msg + '/Result', msg + '/Result') self._messages_tree.model().add_message(action_class.Feedback(), self.tr('Action Feedback'), msg + '/Feedback', msg + '/Feedback') self._messages_tree._recursive_set_editable( self._messages_tree.model().invisibleRootItem(), False)
def _create_message_instance(self, type_str): base_type_str, array_size = self._extract_array_info(type_str) try: base_message_type = get_message_class(base_type_str) except LookupError as e: qWarning( "Creating message type {} failed. Please check your spelling and that the " "message package has been built\n{}".format(base_type_str, e)) return None if base_message_type is None: return None if array_size is not None: message = [] for _ in range(array_size): message.append(base_message_type()) else: message = base_message_type() return message
def __init__(self, node, topic, start_time): self.name = topic self.start_time = start_time self.error = None self.node = node self.lock = threading.Lock() self.buff_x = [] self.buff_y = [] topic_type, real_topic, fields = get_topic_type(node, topic) if topic_type is not None: self.field_evals = generate_field_evals(fields) data_class = get_message_class(topic_type) self.sub = node.create_subscription( data_class, real_topic, self._ros_cb, qos_profile=QoSProfile(depth=10)) else: self.error = RosPlotException("Can not resolve topic type of %s" % topic)
def _refresh_msgs(self, package=None): if package is None or len(package) == 0: return self._msgs = [] if self._mode == message_helpers.MSG_MODE: msg_list = [ ''.join([package, '/', msg]) for msg in message_helpers.get_message_types(package) ] elif self._mode == message_helpers.SRV_MODE: msg_list = [ ''.join([package, '/', srv]) for srv in message_helpers.get_service_types(package) ] elif self._mode == message_helpers.ACTION_MODE: msg_list = [ ''.join([package, '/', action]) for action in message_helpers.get_action_types(package) ] self._logger.debug('_refresh_msgs package={} msg_list={}'.format( package, msg_list)) for msg in msg_list: if (self._mode == message_helpers.MSG_MODE): msg_class = get_message_class(msg) elif self._mode == message_helpers.SRV_MODE: msg_class = get_service_class(msg) elif self._mode == message_helpers.ACTION_MODE: msg_class = get_action_class(msg) self._logger.debug('_refresh_msgs msg_class={}'.format(msg_class)) if msg_class is not None: self._msgs.append(msg) self._msgs = [x.split('/')[1] for x in self._msgs] self._msgs_combo.clear() self._msgs_combo.addItems(self._msgs)
def update_topics(self, node): # Note: This has changed from ROS1->2 as ROS2 only allows nodes to query # information about the rosgraph such as topic names and node names self.model().clear() # If no node is passed in then we need to start rclpy and create a node # topic_helpers provides a convenience function for doing this topic_list = node.get_topic_names_and_types() for topic_path, topic_types in topic_list: for topic_type in topic_types: topic_name = topic_path.strip('/') message_class = get_message_class(topic_type) if message_class is None: qWarning( 'TopicCompleter.update_topics(): ' 'could not get message class for topic type "%s" on topic "%s"' % (topic_type, topic_path)) continue message_instance = message_class() self.model().add_message(message_instance, topic_name, topic_type, topic_path)
def _add_publisher(self, publisher_info): publisher_info['publisher_id'] = self._id_counter self._id_counter += 1 publisher_info['counter'] = 0 publisher_info['enabled'] = publisher_info.get('enabled', False) publisher_info['expressions'] = publisher_info.get('expressions', {}) publisher_info['message_instance'] = self._create_message_instance( publisher_info['type_name']) if publisher_info['message_instance'] is None: return msg_module = get_message_class(publisher_info['type_name']) if not msg_module: raise RuntimeError( 'The passed message type "{}" is invalid'.format( publisher_info['type_name'])) # Topic name provided was relative, remap to node namespace (if it was set) if not publisher_info['topic_name'].startswith('/'): publisher_info['topic_name'] = \ self._node.get_namespace() + publisher_info['topic_name'] # create publisher and timer publisher_info['publisher'] = self._node.create_publisher( msg_module, publisher_info['topic_name'], qos_profile=QoSProfile(depth=10)) publisher_info['timer'] = QTimer(self) # add publisher info to _publishers dict and create signal mapping self._publishers[publisher_info['publisher_id']] = publisher_info self._timeout_mapper.setMapping(publisher_info['timer'], publisher_info['publisher_id']) publisher_info['timer'].timeout.connect(self._timeout_mapper.map) if publisher_info['enabled'] and publisher_info['rate'] > 0: publisher_info['timer'].start(int(1000.0 / publisher_info['rate'])) self._widget.publisher_tree_widget.model().add_publisher( publisher_info)
def get_plot_fields(node, topic_name): topics = node.get_topic_names_and_types() real_topic = None for name, topic_types in topics: if name == topic_name[:len(name)]: real_topic = name topic_type_str = topic_types[0] if topic_types else None break if real_topic is None: message = "topic %s does not exist" % (topic_name) return [], message if topic_type_str is None: message = "no topic types found for topic %s " % (topic_name) return [], message if len(topic_name) < len(real_topic) + 1: message = 'no field specified in topic name "{}"'.format(topic_name) return [], message field_name = topic_name[len(real_topic) + 1:] message_class = message_helpers.get_message_class(topic_type_str) if message_class is None: message = 'message class "{}" is invalid'.format(topic_type_str) return [], message slot_type, is_array, array_size = _parse_type(topic_type_str) field_class = message_helpers.get_message_class(slot_type) fields = [f for f in field_name.split('/') if f] for field in fields: # parse the field name for an array index field, _, field_index = _parse_type(field) if field is None: message = "invalid field %s in topic %s" % (field, real_topic) return [], message field_names_and_types = field_class.get_fields_and_field_types() if field not in field_names_and_types: message = "no field %s in topic %s" % (field_name, real_topic) return [], message slot_type = field_names_and_types[field] slot_type, slot_is_array, array_size = _parse_type(slot_type) is_array = slot_is_array and field_index is None if topic_helpers.is_primitive_type(slot_type): field_class = topic_helpers.get_type_class(slot_type) else: field_class = message_helpers.get_message_class(slot_type) if field_class in (int, float, bool): topic_kind = 'boolean' if field_class == bool else 'numeric' if is_array: if array_size is not None: message = "topic %s is fixed-size %s array" % (topic_name, topic_kind) return ["%s[%d]" % (topic_name, i) for i in range(array_size)], message else: message = "topic %s is variable-size %s array" % (topic_name, topic_kind) return [], message else: message = "topic %s is %s" % (topic_name, topic_kind) return [topic_name], message else: if not topic_helpers.is_primitive_type(slot_type): numeric_fields = [] for slot, slot_type in field_class.get_fields_and_field_types( ).items(): slot_type, is_array, array_size = _parse_type(slot_type) slot_class = topic_helpers.get_type_class(slot_type) if slot_class in (int, float) and not is_array: numeric_fields.append(slot) message = "" if len(numeric_fields) > 0: message = "%d plottable fields in %s" % (len(numeric_fields), topic_name) else: message = "No plottable fields in %s" % (topic_name) return ["%s/%s" % (topic_name, f) for f in numeric_fields], message else: message = "Topic %s is not numeric" % (topic_name) return [], message
def _rightclick_menu(self, event): """ :type event: QEvent """ # QTreeview.selectedIndexes() returns 0 when no node is selected. # This can happen when after booting no left-click has been made yet # (ie. looks like right-click doesn't count). These lines are the # workaround for that problem. selected = self._messages_tree.selectedIndexes() if len(selected) == 0: return menu = QMenu() text_action = QAction(self.tr('View Text'), menu) menu.addAction(text_action) remove_action = QAction(self.tr('Remove message'), menu) menu.addAction(remove_action) action = menu.exec_(event.globalPos()) if action == text_action: self._logger.debug('_rightclick_menu selected={}'.format(selected)) selected_type = selected[1].data() # We strip any array information for loading the python classes selected_type_bare = selected_type if selected_type_bare.find('[') >= 0: selected_type_bare = selected_type_bare[:selected_type_bare. find('[')] # We use the number of '/' to determine of the selected type is a msg, action, srv, # or primitive type. # NOTE (mlautman - 2/4/19) this heuristic seems brittle and should be removed selected_type_bare_tokens_len = len(selected_type_bare.split('/')) # We only want the base class so we transform eg. pkg1/my_srv/Request -> pkg1/my_srv if selected_type_bare_tokens_len > 2: selected_type_bare = "/".join( selected_type_bare.split('/')[:2]) browsetext = None # If the type does not have '/'s then we treat it as a primitive type if selected_type_bare_tokens_len == 1: browsetext = selected_type # if the type has a single '/' then we treat it as a msg type elif selected_type_bare_tokens_len == 2: msg_class = get_message_class(selected_type_bare) browsetext = get_message_text_from_class(msg_class) # If the type has two '/'s then we treat it as a srv or action type elif selected_type_bare_tokens_len == 3: if self._mode == message_helpers.SRV_MODE: msg_class = get_service_class(selected_type_bare) browsetext = get_service_text_from_class(msg_class) elif self._mode == message_helpers.ACTION_MODE: msg_class = get_action_class(selected_type_bare) browsetext = get_action_text_from_class(msg_class) else: self._logger.warn("Unrecognized value for self._mode: {} " "for selected_type: {}".format( self._mode, selected_type)) else: self._logger.warn( "Invalid selected_type: {}".format(selected_type)) if browsetext is not None: self._browsers.append(TextBrowseDialog(browsetext)) self._browsers[-1].show() if action == remove_action: self._messages_tree.model().removeRow(selected[0].row())