Пример #1
0
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
Пример #2
0
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
Пример #3
0
    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__()))
Пример #4
0
 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)
Пример #5
0
    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()))
Пример #6
0
    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))
Пример #7
0
    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)
Пример #8
0
    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
Пример #9
0
    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)
Пример #10
0
    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)
Пример #11
0
    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)
Пример #12
0
    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)
Пример #13
0
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
Пример #14
0
    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())