From 62dbfd8fd63bf47fd9f11e40a502fc296b5b445c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 13 Dec 2022 14:31:36 +0800 Subject: [PATCH] Cabana: add signal value filters to the logs view (#26779) add filter to logs view old-commit-hash: cecef89124be03eae55b6ee3bc33b3d5cdf7f93c --- tools/cabana/canmessages.cc | 24 --------- tools/cabana/canmessages.h | 3 -- tools/cabana/detailwidget.cc | 2 +- tools/cabana/detailwidget.h | 2 +- tools/cabana/historylog.cc | 100 ++++++++++++++++++++++++++++++----- tools/cabana/historylog.h | 28 ++++++++-- tools/cabana/signaledit.cc | 75 -------------------------- tools/cabana/signaledit.h | 7 --- 8 files changed, 113 insertions(+), 128 deletions(-) diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index ded8be5fa3..767ed51d5e 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -32,30 +32,6 @@ bool CANMessages::loadRoute(const QString &route, const QString &data_dir, uint3 return false; } -QList CANMessages::findSignalValues(const QString &id, const Signal *signal, double value, FindFlags flag, int max_count) { - auto evts = events(); - if (!evts) return {}; - - QList ret; - ret.reserve(max_count); - auto [bus, address] = DBCManager::parseId(id); - for (auto &evt : *evts) { - if (evt->which != cereal::Event::Which::CAN) continue; - - for (const auto &c : evt->event.getCan()) { - if (bus == c.getSrc() && address == c.getAddress()) { - double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), *signal); - if ((flag == EQ && val == value) || (flag == LT && val < value) || (flag == GT && val > value)) { - ret.push_back({(evt->mono_time / (double)1e9) - can->routeStartTime(), val}); - if (ret.size() >= max_count) - return ret; - } - } - } - } - return ret; -} - void CANMessages::process(QHash *messages) { for (auto it = messages->begin(); it != messages->end(); ++it) { can_msgs[it.key()] = it.value(); diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index 47f6008686..4dac4fe9df 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -4,7 +4,6 @@ #include #include -#include #include "opendbc/can/common_dbc.h" #include "tools/cabana/settings.h" @@ -21,12 +20,10 @@ class CANMessages : public QObject { Q_OBJECT public: - enum FindFlags{ EQ, LT, GT }; CANMessages(QObject *parent); ~CANMessages(); bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); void seekTo(double ts); - QList findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count); bool eventFilter(const Event *event); inline QString routeName() const { return replay->route()->name(); } diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 77302b7d38..108aa8776e 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -86,7 +86,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart tab_widget = new QTabWidget(this); tab_widget->setTabPosition(QTabWidget::South); tab_widget->addTab(scroll, "&Msg"); - history_log = new HistoryLog(this); + history_log = new LogsWidget(this); tab_widget->addTab(history_log, "&Logs"); main_layout->addWidget(tab_widget); diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index d90438cab2..18c58ab6bf 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -53,7 +53,7 @@ private: QTabWidget *tab_widget; QToolBar *toolbar; QAction *remove_msg_act; - HistoryLog *history_log; + LogsWidget *history_log; BinaryView *binary_view; QScrollArea *scroll; ChartsWidget *charts; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 37001dd582..7cd974ad08 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -2,6 +2,8 @@ #include #include +#include +#include // HistoryLogModel @@ -17,7 +19,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { if (index.column() == 0) { return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2); } - return !sigs.empty() ? QString::number(m.sig_values[index.column() - 1]) : toHex(m.data); + return !sigs.empty() ? QString::number(m.sig_values[index.column() - 1]) : m.data; } else if (role == Qt::FontRole && index.column() == 1 && sigs.empty()) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); } @@ -26,15 +28,18 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { void HistoryLogModel::setMessage(const QString &message_id) { beginResetModel(); - msg_id = message_id; sigs.clear(); messages.clear(); has_more_data = true; if (auto dbc_msg = dbc()->msg(message_id)) { sigs = dbc_msg->getSignals(); } - endResetModel(); + if (msg_id != message_id || sigs.empty()) { + filter_cmp = nullptr; + } + msg_id = message_id; updateState(); + endResetModel(); } QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const { @@ -53,6 +58,18 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i return {}; } +void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function cmp) { + if (sig_idx < sigs.size()) { + filter_sig_idx = sig_idx; + filter_value = value.toDouble(); + filter_cmp = value.isEmpty() ? nullptr : cmp; + beginResetModel(); + messages.clear(); + updateState(); + endResetModel(); + } +} + void HistoryLogModel::updateState() { if (!msg_id.isEmpty()) { uint64_t last_mono_time = messages.empty() ? 0 : messages.front().mono_time; @@ -87,20 +104,23 @@ std::deque HistoryLogModel::fetchData(uint64_t min_mon std::deque msgs; const auto [src, address] = DBCManager::parseId(msg_id); uint32_t cnt = 0; + QVector values(sigs.size()); for (--it; it != events->begin() && (*it)->mono_time > min_mono_time; --it) { if ((*it)->which == cereal::Event::Which::CAN) { for (const auto &c : (*it)->event.getCan()) { if (src == c.getSrc() && address == c.getAddress()) { const auto dat = c.getDat(); - auto &m = msgs.emplace_back(); - m.mono_time = (*it)->mono_time; - m.data.append((char *)dat.begin(), dat.size()); - m.sig_values.reserve(sigs.size()); - for (const Signal *sig : sigs) { - m.sig_values.push_back(get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig)); + for (int i = 0; i < sigs.size(); ++i) { + values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *(sigs[i])); + } + if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { + auto &m = msgs.emplace_back(); + m.mono_time = (*it)->mono_time; + m.data = toHex(QByteArray((char *)dat.begin(), dat.size())); + m.sig_values = values; + if (++cnt >= batch_size && min_mono_time == 0) + return msgs; } - if (++cnt >= batch_size && min_mono_time == 0) - return msgs; } } } @@ -132,12 +152,64 @@ void HeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalI // HistoryLog HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { - model = new HistoryLogModel(this); - setModel(model); setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap); horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); verticalHeader()->setVisible(false); - setFrameShape(QFrame::NoFrame); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); } + +// LogsWidget + +LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + + filter_container = new QWidget(this); + QHBoxLayout *h = new QHBoxLayout(filter_container); + signals_cb = new QComboBox(this); + h->addWidget(signals_cb); + comp_box = new QComboBox(); + comp_box->addItems({">", "=", "<"}); + h->addWidget(comp_box); + value_edit = new QLineEdit(this); + value_edit->setClearButtonEnabled(true); + value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this)); + h->addWidget(value_edit); + main_layout->addWidget(filter_container); + + model = new HistoryLogModel(this); + logs = new HistoryLog(this); + logs->setModel(model); + main_layout->addWidget(logs); + + QObject::connect(signals_cb, SIGNAL(currentIndexChanged(int)), this, SLOT(setFilter())); + QObject::connect(comp_box, SIGNAL(currentIndexChanged(int)), this, SLOT(setFilter())); + QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter); +} + +void LogsWidget::setMessage(const QString &message_id) { + blockSignals(true); + value_edit->setText(""); + signals_cb->clear(); + comp_box->setCurrentIndex(0); + sigs.clear(); + if (auto dbc_msg = dbc()->msg(message_id)) { + sigs = dbc_msg->getSignals(); + for (auto s : sigs) { + signals_cb->addItem(s->name.c_str()); + } + } + filter_container->setVisible(!sigs.empty()); + model->setMessage(message_id); + blockSignals(false); +} + +void LogsWidget::setFilter() { + std::function cmp; + switch (comp_box->currentIndex()) { + case 0: cmp = std::greater{}; break; + case 1: cmp = std::equal_to{}; break; + case 2: cmp = std::less{}; break; + } + model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp); +} diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 5a9903823f..d5ae47192e 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include #include "tools/cabana/canmessages.h" @@ -19,6 +21,7 @@ public: HistoryLogModel(QObject *parent); void setMessage(const QString &message_id); void updateState(); + void setFilter(int sig_idx, const QString &value, std::function cmp); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void fetchMore(const QModelIndex &parent) override; @@ -29,13 +32,16 @@ public: struct Message { uint64_t mono_time = 0; QVector sig_values; - QByteArray data; + QString data; }; std::deque fetchData(uint64_t min_mono_time, uint64_t max_mono_time); QString msg_id; bool has_more_data = true; const int batch_size = 50; + int filter_sig_idx = -1; + double filter_value = 0; + std::function filter_cmp = nullptr; std::deque messages; std::vector sigs; }; @@ -43,11 +49,27 @@ public: class HistoryLog : public QTableView { public: HistoryLog(QWidget *parent); - void setMessage(const QString &message_id) { model->setMessage(message_id); } + int sizeHintForColumn(int column) const override { return -1; }; +}; + +class LogsWidget : public QWidget { + Q_OBJECT + +public: + LogsWidget(QWidget *parent); + void setMessage(const QString &message_id); void updateState() { model->updateState(); } +private slots: + void setFilter(); + private: - int sizeHintForColumn(int column) const override { return -1; }; void showEvent(QShowEvent *event) override { model->setMessage(model->msg_id); }; + + HistoryLog *logs; HistoryLogModel *model; + QWidget *filter_container; + QComboBox *signals_cb, *comp_box; + QLineEdit *value_edit; + std::vector sigs; }; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 85476c2302..b2491d5b1f 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -4,12 +4,8 @@ #include #include #include -#include -#include #include -#include "selfdrive/ui/qt/util.h" - // SignalForm SignalForm::SignalForm(QWidget *parent) : QWidget(parent) { @@ -94,11 +90,6 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa plot_btn->setCheckable(true); plot_btn->setAutoRaise(true); title_layout->addWidget(plot_btn); - auto seek_btn = new QToolButton(this); - seek_btn->setText("🔍"); - seek_btn->setAutoRaise(true); - seek_btn->setToolTip(tr("Find signal values")); - title_layout->addWidget(seek_btn); auto remove_btn = new QToolButton(this); remove_btn->setAutoRaise(true); remove_btn->setText("x"); @@ -126,7 +117,6 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); }); - QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); }); QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); }); QObject::connect(form, &SignalForm::changed, [this]() { save_timer->start(); }); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); @@ -212,68 +202,3 @@ void SignalEdit::leaveEvent(QEvent *event) { emit highlight(nullptr); QWidget::leaveEvent(event); } - -// SignalFindDlg - -SignalFindDlg::SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Find signal values")); - QVBoxLayout *main_layout = new QVBoxLayout(this); - - QHBoxLayout *h = new QHBoxLayout(); - h->addWidget(new QLabel(signal->name.c_str())); - QComboBox *comp_box = new QComboBox(); - comp_box->addItems({">", "=", "<"}); - h->addWidget(comp_box); - QLineEdit *value_edit = new QLineEdit("0", this); - value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this)); - h->addWidget(value_edit, 1); - QPushButton *search_btn = new QPushButton(tr("Find"), this); - h->addWidget(search_btn); - main_layout->addLayout(h); - - QWidget *container = new QWidget(this); - QVBoxLayout *signals_layout = new QVBoxLayout(container); - QScrollArea *scroll = new QScrollArea(this); - scroll->setWidget(container); - scroll->setWidgetResizable(true); - scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - main_layout->addWidget(scroll); - - QObject::connect(search_btn, &QPushButton::clicked, [=]() { - clearLayout(signals_layout); - - CANMessages::FindFlags comp = CANMessages::EQ; - if (comp_box->currentIndex() == 0) { - comp = CANMessages::GT; - } else if (comp_box->currentIndex() == 2) { - comp = CANMessages::LT; - } - double value = value_edit->text().toDouble(); - - const int limit_results = 50; - auto values = can->findSignalValues(id, signal, value, comp, limit_results); - for (auto &v : values) { - QHBoxLayout *item_layout = new QHBoxLayout(); - item_layout->addWidget(new QLabel(QString::number(v.x(), 'f', 2))); - item_layout->addWidget(new QLabel(QString::number(v.y()))); - item_layout->addStretch(1); - - QPushButton *goto_btn = new QPushButton(tr("Goto"), this); - QObject::connect(goto_btn, &QPushButton::clicked, [sec = v.x()]() { can->seekTo(sec); }); - item_layout->addWidget(goto_btn); - signals_layout->addLayout(item_layout); - } - if (values.size() == limit_results) { - QFrame *hline = new QFrame(); - hline->setFrameShape(QFrame::HLine); - hline->setFrameShadow(QFrame::Sunken); - signals_layout->addWidget(hline); - QLabel *info = new QLabel(tr("Only display the first %1 results").arg(limit_results)); - info->setAlignment(Qt::AlignCenter); - signals_layout->addWidget(info); - } - if (values.size() * 30 > container->height()) { - scroll->setFixedHeight(std::min(values.size() * 30, 300)); - } - }); -} diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index 205be4bb78..eec943226e 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -9,7 +8,6 @@ #include #include "selfdrive/ui/qt/widgets/controls.h" - #include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" @@ -59,8 +57,3 @@ protected: QToolButton *plot_btn; QTimer *save_timer; }; - -class SignalFindDlg : public QDialog { -public: - SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent); -};