diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index 252a8c680c..897381b3f2 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -3,6 +3,8 @@ #include #include +#include "tools/cabana/dbcmanager.h" + Q_DECLARE_METATYPE(std::vector); Settings settings; @@ -38,6 +40,33 @@ bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool return false; } +QList CANMessages::findSignalValues(const QString &id, const Signal *signal, double value, FindFlags flag, int max_count) { + auto evts = events(); + if (!evts) return {}; + + auto l = id.split(':'); + int bus = l[0].toInt(); + uint32_t address = l[1].toUInt(nullptr, 16); + + QList ret; + ret.reserve(max_count); + for (auto &evt : *evts) { + if (evt->which != cereal::Event::Which::CAN) continue; + + for (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) { ++counters[it.key()]; diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index 58f5ad70b7..44131ad5c9 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -5,7 +5,9 @@ #include #include +#include +#include "opendbc/can/common_dbc.h" #include "tools/replay/replay.h" class Settings : public QObject { @@ -35,12 +37,14 @@ 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, bool use_qcam); void seekTo(double ts); void resetRange(); void setRange(double min, double max); + QList findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count); bool eventFilter(const Event *event); inline std::pair range() const { return {begin_sec, end_sec}; } diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 7cace48402..050802d452 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -3,8 +3,12 @@ #include #include #include +#include +#include #include +#include "selfdrive/ui/qt/util.h" + // SignalForm SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start_bit), QWidget(parent) { @@ -93,6 +97,12 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWid title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index))); title_layout->addWidget(title, 1); + QPushButton *seek_btn = new QPushButton("⌕"); + seek_btn->setStyleSheet("font-weight:bold;font-size:20px"); + seek_btn->setToolTip(tr("Find signal values")); + seek_btn->setFixedSize(20, 20); + title_layout->addWidget(seek_btn); + QPushButton *plot_btn = new QPushButton("📈"); plot_btn->setToolTip(tr("Show Plot")); plot_btn->setFixedSize(20, 20); @@ -124,13 +134,17 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWid main_layout->addWidget(hline); QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); + QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); QObject::connect(save_btn, &QPushButton::clicked, [=]() { QString new_name = form->getSignal().name.c_str(); title->setText(QString("%1. %2").arg(index + 1).arg(new_name)); emit save(); sig_name = new_name; }); - QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); + QObject::connect(seek_btn, &QPushButton::clicked, [this, msg_id, s = &sig]() { + SignalFindDlg dlg(msg_id, s, this); + dlg.exec(); + }); } void SignalEdit::setFormVisible(bool visible) { @@ -159,3 +173,66 @@ AddSignalDialog::AddSignalDialog(const QString &id, int start_bit, int size, QWi connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); } + +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 f31408657f..f7611747ec 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -51,3 +51,10 @@ public: AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent); SignalForm *form; }; + +class SignalFindDlg : public QDialog { + Q_OBJECT + +public: + SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent); +};