cabana: show and edit all messages present in DBC files (#28108)
* show all messages present in DBC files * set last section stretch * user can't resize data section * re-fetch if filtering freq|count|data * reserve set space * use contains * emit signalAdded for all related sources old-commit-hash: f7e024f2f22139153aa8ee5ce9a9713bd51d99f0
This commit is contained in:
@@ -61,6 +61,7 @@ namespace cabana {
|
||||
};
|
||||
|
||||
struct Msg {
|
||||
uint32_t address;
|
||||
QString name;
|
||||
uint32_t size;
|
||||
QList<cabana::Signal> sigs;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
typedef QSet<uint8_t> SourceSet;
|
||||
const SourceSet SOURCE_ALL = {};
|
||||
const int INVALID_SOURCE = 0xff;
|
||||
|
||||
class DBCManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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<int, QString> &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"(
|
||||
<b>Message View</b><br/>
|
||||
@@ -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<QColor> 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<int, QString> &filters) {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
void MessageListModel::sortMessages(Qt::SortOrder sort_order, int sort_column, QList<MessageId> &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<MessageId> &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<unsigned int, unsigned int> parseRange(QString &filter, bool *ok = nullptr, int base = 10) {
|
||||
static std::pair<unsigned int, unsigned int> 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<unsigned int>::min();
|
||||
@@ -263,14 +276,14 @@ static std::pair<unsigned int, unsigned int> parseRange(QString &filter, bool *o
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, QMap<int, QString> &filters) {
|
||||
bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, const QMap<int, QString> &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<MessageId> 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<MessageId> 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);
|
||||
}
|
||||
sortMessages(sort_order, sort_column, new_msgs);
|
||||
|
||||
// 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(new_msgs);
|
||||
|
||||
if (msgs != new_msgs) {
|
||||
beginResetModel();
|
||||
msgs = new_msgs;
|
||||
msgs = std::move(new_msgs);
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageListModel::msgsReceived(const QHash<MessageId, CanData> *new_msgs) {
|
||||
QList<MessageId> prev_msgs = msgs;
|
||||
fetchData();
|
||||
|
||||
void MessageListModel::msgsReceived(const QHash<MessageId, CanData> *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)
|
||||
|
||||
@@ -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<int, QString> &filters);
|
||||
void msgsReceived(const QHash<MessageId, CanData> *new_msgs = nullptr);
|
||||
void msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids);
|
||||
void fetchData();
|
||||
void suppress();
|
||||
void clearSuppress();
|
||||
void reset();
|
||||
void forceResetModel();
|
||||
QList<MessageId> msgs;
|
||||
void dbcModified();
|
||||
std::vector<MessageId> msgs;
|
||||
QSet<std::pair<MessageId, int>> suppressed_bytes;
|
||||
|
||||
private:
|
||||
static void sortMessages(Qt::SortOrder sort_order, int sort_column, QList<MessageId> &new_msgs);
|
||||
static bool matchMessage(const MessageId &id, const CanData &data, QMap<int, QString> &filters);
|
||||
void sortMessages(std::vector<MessageId> &new_msgs);
|
||||
bool matchMessage(const MessageId &id, const CanData &data, const QMap<int, QString> &filters);
|
||||
|
||||
QMap<int, QString> filter_str;
|
||||
QSet<uint32_t> dbc_address;
|
||||
int sort_column = 0;
|
||||
Qt::SortOrder sort_order = Qt::AscendingOrder;
|
||||
};
|
||||
|
||||
@@ -590,9 +590,9 @@ void SignalView::handleSignalUpdated(const cabana::Signal *sig) {
|
||||
}
|
||||
|
||||
void SignalView::updateState(const QHash<MessageId, CanData> *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);
|
||||
|
||||
@@ -12,6 +12,7 @@ AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
|
||||
|
||||
void AbstractStream::updateMessages(QHash<MessageId, CanData> *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<MessageId, CanData> *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<const CanEvent *> &AbstractStream::events(const MessageId &id) const {
|
||||
static std::vector<const CanEvent *> 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
virtual bool isPaused() const { return false; }
|
||||
virtual void pause(bool pause) {}
|
||||
const std::vector<const CanEvent *> &allEvents() const { return all_events_; }
|
||||
const std::vector<const CanEvent *> &events(const MessageId &id) const { return events_.at(id); }
|
||||
const std::vector<const CanEvent *> &events(const MessageId &id) const;
|
||||
virtual const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return {}; }
|
||||
|
||||
signals:
|
||||
@@ -65,7 +65,7 @@ signals:
|
||||
void streamStarted();
|
||||
void eventsMerged();
|
||||
void updated();
|
||||
void msgsReceived(const QHash<MessageId, CanData> *);
|
||||
void msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids);
|
||||
void sourcesUpdated(const SourceSet &s);
|
||||
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user