diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index be0057cf56..045eb3558d 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -61,6 +61,7 @@ namespace cabana { }; struct Msg { + uint32_t address; QString name; uint32_t size; QList sigs; diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index 9173080796..f1a01c4079 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -39,6 +39,7 @@ void DBCFile::open(const QString &content) { msgs.clear(); for (auto &msg : dbc->msgs) { auto &m = msgs[msg.address]; + m.address = msg.address; m.name = msg.name.c_str(); m.size = msg.size; for (auto &s : msg.sigs) { @@ -145,6 +146,7 @@ void DBCFile::removeSignal(const MessageId &id, const QString &sig_name) { void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size) { auto &m = msgs[id.address]; + m.address = id.address; m.name = name; m.size = size; } diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 2451ff9b00..9667dd8f2a 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -119,6 +119,7 @@ void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { cabana::Signal *s = dbc_file->addSignal(id, sig); if (s != nullptr) { + dbc_sources.insert(id.source); for (uint8_t source : dbc_sources) { emit signalAdded({.source = source, .address = id.address}, s); } diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 3b148bb61d..699c280b4c 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -15,6 +15,7 @@ typedef QSet SourceSet; const SourceSet SOURCE_ALL = {}; +const int INVALID_SOURCE = 0xff; class DBCManager : public QObject { Q_OBJECT diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index a9bbf15980..0feb4642f4 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -129,7 +129,9 @@ void DetailWidget::refresh() { QStringList warnings; auto msg = dbc()->msg(msg_id); if (msg) { - if (msg->size != can->lastMessage(msg_id).dat.size()) { + if (msg_id.source == INVALID_SOURCE) { + warnings.push_back(tr("No messages received.")); + } else if (msg->size != can->lastMessage(msg_id).dat.size()) { warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } for (auto s : binary_view->getOverlappingSignals()) { diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 1d86a53ad1..d42468f188 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -15,10 +15,11 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { title_layout->addWidget(num_msg_label); title_layout->addStretch(); - title_layout->addWidget(multiple_lines_bytes = new QCheckBox(tr("Multiple Lines Bytes"), this)); + title_layout->addWidget(multiple_lines_bytes = new QCheckBox(tr("Multiple Lines &Bytes"), this)); multiple_lines_bytes->setToolTip(tr("Display bytes in multiple lines")); multiple_lines_bytes->setChecked(settings.multiple_lines_bytes); - QPushButton *clear_filters = new QPushButton(tr("Clear Filters")); + QPushButton *clear_filters = new QPushButton(tr("&Clear Filters")); + clear_filters->setEnabled(false); title_layout->addWidget(clear_filters); main_layout->addLayout(title_layout); @@ -42,6 +43,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { // Must be called before setting any header parameters to avoid overriding restoreHeaderState(settings.message_header_state); view->header()->setSectionsMovable(true); + view->header()->setSectionResizeMode(MessageListModel::Column::DATA, QHeaderView::Fixed); // Header context menu view->header()->setContextMenuPolicy(Qt::CustomContextMenu); @@ -62,6 +64,9 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { // signals/slots QObject::connect(header, &MessageViewHeader::filtersUpdated, model, &MessageListModel::setFilterStrings); + QObject::connect(header, &MessageViewHeader::filtersUpdated, [=](const QMap &filters) { + clear_filters->setEnabled(!filters.isEmpty()); + }); QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions); QObject::connect(clear_filters, &QPushButton::clicked, header, &MessageViewHeader::clearFilters); QObject::connect(multiple_lines_bytes, &QCheckBox::stateChanged, [=](int state) { @@ -108,7 +113,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { }); updateSuppressedButtons(); - dbcModified(); setWhatsThis(tr(R"( Message View
@@ -122,12 +126,13 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { void MessagesWidget::dbcModified() { num_msg_label->setText(tr("%1 Messages, %2 Signals").arg(dbc()->msgCount()).arg(dbc()->signalCount())); - model->fetchData(); + model->dbcModified(); } void MessagesWidget::selectMessage(const MessageId &msg_id) { - if (int row = model->msgs.indexOf(msg_id); row != -1) { - view->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); + auto it = std::find(model->msgs.cbegin(), model->msgs.cend(), msg_id); + if (it != model->msgs.cend()) { + view->selectionModel()->setCurrentIndex(model->index(std::distance(model->msgs.cbegin(), it), 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } } @@ -180,11 +185,11 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { switch (index.column()) { case Column::NAME: return msgName(id); - case Column::SOURCE: return id.source; + case Column::SOURCE: return id.source != INVALID_SOURCE ? QString::number(id.source) : "N/A" ; case Column::ADDRESS: return QString::number(id.address, 16); - case Column::FREQ: return getFreq(can_data); - case Column::COUNT: return can_data.count; - case Column::DATA: return toHex(can_data.dat); + case Column::FREQ: return id.source != INVALID_SOURCE ? getFreq(can_data) : "N/A"; + case Column::COUNT: return id.source != INVALID_SOURCE ? QString::number(can_data.count) : "N/A"; + case Column::DATA: return id.source != INVALID_SOURCE ? toHex(can_data.dat) : "N/A"; } } else if (role == ColorsRole) { QVector colors = can_data.colors; @@ -196,7 +201,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { } } return QVariant::fromValue(colors); - } else if (role == BytesRole && index.column() == Column::DATA) { + } else if (role == BytesRole && index.column() == Column::DATA && id.source != INVALID_SOURCE) { return can_data.dat; } return {}; @@ -207,7 +212,15 @@ void MessageListModel::setFilterStrings(const QMap &filters) { fetchData(); } -void MessageListModel::sortMessages(Qt::SortOrder sort_order, int sort_column, QList &new_msgs) { +void MessageListModel::dbcModified() { + dbc_address.clear(); + for (const auto &[_, m] : dbc()->getMessages(0)) { + dbc_address.insert(m.address); + } + fetchData(); +} + +void MessageListModel::sortMessages(std::vector &new_msgs) { if (sort_column == Column::NAME) { std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) { auto ll = std::pair{msgName(l), l}; @@ -241,7 +254,7 @@ void MessageListModel::sortMessages(Qt::SortOrder sort_order, int sort_column, Q } } -static std::pair parseRange(QString &filter, bool *ok = nullptr, int base = 10) { +static std::pair parseRange(const QString &filter, bool *ok = nullptr, int base = 10) { // Parse out filter string into a range (e.g. "1" -> {1, 1}, "1-3" -> {1, 3}, "1-" -> {1, inf}) bool ok1 = true, ok2 = true; unsigned int parsed1 = std::numeric_limits::min(); @@ -263,14 +276,14 @@ static std::pair parseRange(QString &filter, bool *o } } -bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, QMap &filters) { +bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, const QMap &filters) { auto cs = Qt::CaseInsensitive; bool match = true; bool convert_ok; for (int column = Column::NAME; column <= Column::DATA; column++) { if (!filters.contains(column)) continue; - QString txt = filters[column]; + const QString &txt = filters[column]; QRegularExpression re(txt, QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption); @@ -341,25 +354,38 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, QM void MessageListModel::fetchData() { - QList new_msgs; - for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) { - if (matchMessage(it.key(), it.value(), filter_str)) { + std::vector new_msgs; + new_msgs.reserve(can->last_msgs.size() + dbc_address.size()); + + auto address = dbc_address; + for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { + if (filter_str.isEmpty() || matchMessage(it.key(), it.value(), filter_str)) { new_msgs.push_back(it.key()); } + address.remove(it.key().address); + } + + // merge all DBC messages + for (auto &addr : address) { + MessageId id{.source = INVALID_SOURCE, .address = addr}; + if (filter_str.isEmpty() || matchMessage(id, {}, filter_str)) { + new_msgs.push_back(id); + } } - sortMessages(sort_order, sort_column, new_msgs); + + sortMessages(new_msgs); if (msgs != new_msgs) { beginResetModel(); - msgs = new_msgs; + msgs = std::move(new_msgs); endResetModel(); } } -void MessageListModel::msgsReceived(const QHash *new_msgs) { - QList prev_msgs = msgs; - fetchData(); - +void MessageListModel::msgsReceived(const QHash *new_msgs, bool has_new_ids) { + if (has_new_ids || filter_str.contains(Column::FREQ) || filter_str.contains(Column::COUNT) || filter_str.contains(Column::DATA)) { + fetchData(); + } for (int i = 0; i < msgs.size(); ++i) { if (new_msgs->contains(msgs[i])) { for (int col = Column::FREQ; col < columnCount(); ++col) diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index c559467919..036758e636 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -34,20 +34,22 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); } void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void setFilterStrings(const QMap &filters); - void msgsReceived(const QHash *new_msgs = nullptr); + void msgsReceived(const QHash *new_msgs, bool has_new_ids); void fetchData(); void suppress(); void clearSuppress(); void reset(); void forceResetModel(); - QList msgs; + void dbcModified(); + std::vector msgs; QSet> suppressed_bytes; private: - static void sortMessages(Qt::SortOrder sort_order, int sort_column, QList &new_msgs); - static bool matchMessage(const MessageId &id, const CanData &data, QMap &filters); + void sortMessages(std::vector &new_msgs); + bool matchMessage(const MessageId &id, const CanData &data, const QMap &filters); QMap filter_str; + QSet dbc_address; int sort_column = 0; Qt::SortOrder sort_order = Qt::AscendingOrder; }; diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 84043f7a1f..124f9f6b9a 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -590,9 +590,9 @@ void SignalView::handleSignalUpdated(const cabana::Signal *sig) { } void SignalView::updateState(const QHash *msgs) { - if (model->rowCount() == 0 || (msgs && !msgs->contains(model->msg_id))) return; - const auto &last_msg = can->lastMessage(model->msg_id); + if (model->rowCount() == 0 || (msgs && !msgs->contains(model->msg_id)) || last_msg.dat.size() == 0) return; + for (auto item : model->root->children) { double value = get_raw_value((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), *item->sig); item->sig_val = item->sig->formatValue(value); diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 3451c15291..355e65b6c5 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -12,6 +12,7 @@ AbstractStream::AbstractStream(QObject *parent) : QObject(parent) { void AbstractStream::updateMessages(QHash *messages) { auto prev_src_size = sources.size(); + auto prev_msg_size = last_msgs.size(); for (auto it = messages->begin(); it != messages->end(); ++it) { const auto &id = it.key(); last_msgs[id] = it.value(); @@ -21,7 +22,7 @@ void AbstractStream::updateMessages(QHash *messages) { emit sourcesUpdated(sources); } emit updated(); - emit msgsReceived(messages); + emit msgsReceived(messages, prev_msg_size != last_msgs.size()); delete messages; processing = false; } @@ -50,6 +51,12 @@ bool AbstractStream::postEvents() { return false; } +const std::vector &AbstractStream::events(const MessageId &id) const { + static std::vector empty_events; + auto it = events_.find(id); + return it != events_.end() ? it->second : empty_events; +} + const CanData &AbstractStream::lastMessage(const MessageId &id) { static CanData empty_data = {}; auto it = last_msgs.find(id); @@ -81,7 +88,7 @@ void AbstractStream::updateLastMsgsTo(double sec) { // use a timer to prevent recursive calls QTimer::singleShot(0, [this]() { emit updated(); - emit msgsReceived(&last_msgs); + emit msgsReceived(&last_msgs, true); }); } diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 80c11d2f19..9b317b3ed6 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -55,7 +55,7 @@ public: virtual bool isPaused() const { return false; } virtual void pause(bool pause) {} const std::vector &allEvents() const { return all_events_; } - const std::vector &events(const MessageId &id) const { return events_.at(id); } + const std::vector &events(const MessageId &id) const; virtual const std::vector> getTimeline() { return {}; } signals: @@ -65,7 +65,7 @@ signals: void streamStarted(); void eventsMerged(); void updated(); - void msgsReceived(const QHash *); + void msgsReceived(const QHash *new_msgs, bool has_new_ids); void sourcesUpdated(const SourceSet &s); public: