#include "tools/cabana/tools/findsignal.h" #include #include #include #include #include #include #include // FindSignalModel QVariant FindSignalModel::headerData(int section, Qt::Orientation orientation, int role) const { static QString titles[] = {"Id", "Start Bit, size", "(time, value)"}; if (role != Qt::DisplayRole) return {}; return orientation == Qt::Horizontal ? titles[section] : QString::number(section + 1); } QVariant FindSignalModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { const auto &s = filtered_signals[index.row()]; switch (index.column()) { case 0: return s.id.toString(); case 1: return QString("%1, %2").arg(s.sig.start_bit).arg(s.sig.size); case 2: return s.values.join(" "); } } return {}; } void FindSignalModel::search(std::function cmp) { beginResetModel(); std::mutex lock; const auto prev_sigs = !histories.isEmpty() ? histories.back() : initial_signals; filtered_signals.clear(); filtered_signals.reserve(prev_sigs.size()); QtConcurrent::blockingMap(prev_sigs, [&](auto &s) { const auto &events = can->events(s.id); auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); auto last = events.cend(); if (last_time < std::numeric_limits::max()) { last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); } auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); if (it != last) { auto values = s.values; values += QString("(%1, %2)").arg((*it)->mono_time / 1e9 - can->routeStartTime(), 0, 'f', 2).arg(get_raw_value((*it)->dat, (*it)->size, s.sig)); std::lock_guard lk(lock); filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values}); } }); histories.push_back(filtered_signals); endResetModel(); } void FindSignalModel::undo() { if (!histories.isEmpty()) { beginResetModel(); histories.pop_back(); filtered_signals.clear(); if (!histories.isEmpty()) filtered_signals = histories.back(); endResetModel(); } } void FindSignalModel::reset() { beginResetModel(); histories.clear(); filtered_signals.clear(); initial_signals.clear(); endResetModel(); } // FindSignalDlg FindSignalDlg::FindSignalDlg(QWidget *parent) : QDialog(parent, Qt::WindowFlags() | Qt::Window) { setWindowTitle(tr("Find Signal")); setAttribute(Qt::WA_DeleteOnClose); QVBoxLayout *main_layout = new QVBoxLayout(this); // Messages group message_group = new QGroupBox(tr("Messages"), this); QFormLayout *message_layout = new QFormLayout(message_group); message_layout->addRow(tr("Bus"), bus_edit = new QLineEdit()); bus_edit->setPlaceholderText(tr("comma-seperated values. Leave blank for all")); message_layout->addRow(tr("Address"), address_edit = new QLineEdit()); address_edit->setPlaceholderText(tr("comma-seperated hex values. Leave blank for all")); QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->addWidget(first_time_edit = new QLineEdit("0")); hlayout->addWidget(new QLabel("-")); hlayout->addWidget(last_time_edit = new QLineEdit("MAX")); hlayout->addWidget(new QLabel("seconds")); hlayout->addStretch(0); message_layout->addRow(tr("Time"), hlayout); // Signal group properties_group = new QGroupBox(tr("Signal")); QFormLayout *property_layout = new QFormLayout(properties_group); property_layout->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint); hlayout = new QHBoxLayout(); hlayout->addWidget(min_size = new QSpinBox); hlayout->addWidget(new QLabel("-")); hlayout->addWidget(max_size = new QSpinBox); hlayout->addWidget(litter_endian = new QCheckBox(tr("Little endian"))); hlayout->addWidget(is_signed = new QCheckBox(tr("Signed"))); hlayout->addStretch(0); min_size->setRange(1, 64); max_size->setRange(1, 64); min_size->setValue(8); max_size->setValue(8); litter_endian->setChecked(true); property_layout->addRow(tr("Size"), hlayout); property_layout->addRow(tr("Factor"), factor_edit = new QLineEdit("1.0")); property_layout->addRow(tr("Offset"), offset_edit = new QLineEdit("0.0")); // find group QGroupBox *find_group = new QGroupBox(tr("Find signal"), this); QVBoxLayout *vlayout = new QVBoxLayout(find_group); hlayout = new QHBoxLayout(); hlayout->addWidget(new QLabel(tr("Value"))); hlayout->addWidget(compare_cb = new QComboBox(this)); hlayout->addWidget(value1 = new QLineEdit); hlayout->addWidget(to_label = new QLabel("-")); hlayout->addWidget(value2 = new QLineEdit); hlayout->addWidget(undo_btn = new QPushButton(tr("Undo prev find"), this)); hlayout->addWidget(search_btn = new QPushButton(tr("Find"))); hlayout->addWidget(reset_btn = new QPushButton(tr("Reset"), this)); vlayout->addLayout(hlayout); compare_cb->addItems({"=", ">", ">=", "!=", "<", "<=", "between"}); value1->setFocus(Qt::OtherFocusReason); value2->setVisible(false); to_label->setVisible(false); undo_btn->setEnabled(false); reset_btn->setEnabled(false); auto double_validator = new DoubleValidator(this); for (auto edit : {value1, value2, factor_edit, offset_edit, first_time_edit, last_time_edit}) { edit->setValidator(double_validator); } vlayout->addWidget(view = new QTableView(this)); view->setContextMenuPolicy(Qt::CustomContextMenu); view->horizontalHeader()->setStretchLastSection(true); view->horizontalHeader()->setSelectionMode(QAbstractItemView::NoSelection); view->setSelectionBehavior(QAbstractItemView::SelectRows); view->setModel(model = new FindSignalModel(this)); hlayout = new QHBoxLayout(); hlayout->addWidget(message_group); hlayout->addWidget(properties_group); main_layout->addLayout(hlayout); main_layout->addWidget(find_group); main_layout->addWidget(stats_label = new QLabel()); setMinimumSize({700, 650}); QObject::connect(search_btn, &QPushButton::clicked, this, &FindSignalDlg::search); QObject::connect(undo_btn, &QPushButton::clicked, model, &FindSignalModel::undo); QObject::connect(model, &QAbstractItemModel::modelReset, this, &FindSignalDlg::modelReset); QObject::connect(reset_btn, &QPushButton::clicked, model, &FindSignalModel::reset); QObject::connect(view, &QTableView::customContextMenuRequested, this, &FindSignalDlg::customMenuRequested); QObject::connect(view, &QTableView::doubleClicked, [this](const QModelIndex &index) { if (index.isValid()) emit openMessage(model->filtered_signals[index.row()].id); }); QObject::connect(compare_cb, qOverload(&QComboBox::currentIndexChanged), [=](int index) { to_label->setVisible(index == compare_cb->count() - 1); value2->setVisible(index == compare_cb->count() - 1); }); } void FindSignalDlg::search() { if (model->histories.isEmpty()) { setInitialSignals(); } auto v1 = value1->text().toDouble(); auto v2 = value2->text().toDouble(); std::function cmp = nullptr; switch (compare_cb->currentIndex()) { case 0: cmp = [v1](double v) { return v == v1;}; break; case 1: cmp = [v1](double v) { return v > v1;}; break; case 2: cmp = [v1](double v) { return v >= v1;}; break; case 3: cmp = [v1](double v) { return v != v1;}; break; case 4: cmp = [v1](double v) { return v < v1;}; break; case 5: cmp = [v1](double v) { return v <= v1;}; break; case 6: cmp = [v1, v2](double v) { return v >= v1 && v <= v2;}; break; } properties_group->setEnabled(false); message_group->setEnabled(false); search_btn->setEnabled(false); stats_label->setVisible(false); search_btn->setText("Finding ...."); QTimer::singleShot(0, this, [=]() { model->search(cmp); }); } void FindSignalDlg::setInitialSignals() { QSet buses; for (auto bus : bus_edit->text().trimmed().split(",")) { bus = bus.trimmed(); if (!bus.isEmpty()) buses.insert(bus.toUShort()); } QSet addresses; for (auto addr : address_edit->text().trimmed().split(",")) { addr = addr.trimmed(); if (!addr.isEmpty()) addresses.insert(addr.toULong(nullptr, 16)); } cabana::Signal sig{}; sig.is_little_endian = litter_endian->isChecked(); sig.is_signed = is_signed->isChecked(); sig.factor = factor_edit->text().toDouble(); sig.offset = offset_edit->text().toDouble(); double first_time_val = first_time_edit->text().toDouble(); double last_time_val = last_time_edit->text().toDouble(); auto [first_sec, last_sec] = std::minmax(first_time_val, last_time_val); uint64_t first_time = (can->routeStartTime() + first_sec) * 1e9; model->last_time = std::numeric_limits::max(); if (last_sec > 0) { model->last_time = (can->routeStartTime() + last_sec) * 1e9; } model->initial_signals.clear(); for (const auto &[id, m] : can->lastMessages()) { if (buses.isEmpty() || buses.contains(id.source) && (addresses.isEmpty() || addresses.contains(id.address))) { const auto &events = can->events(id); auto e = std::lower_bound(events.cbegin(), events.cend(), first_time, CompareCanEvent()); if (e != events.cend()) { const int total_size = m.dat.size() * 8; for (int size = min_size->value(); size <= max_size->value(); ++size) { for (int start = 0; start <= total_size - size; ++start) { FindSignalModel::SearchSignal s{.id = id, .mono_time = first_time, .sig = sig}; s.sig.start_bit = start; s.sig.size = size; updateMsbLsb(s.sig); s.value = get_raw_value((*e)->dat, (*e)->size, s.sig); model->initial_signals.push_back(s); } } } } } } void FindSignalDlg::modelReset() { properties_group->setEnabled(model->histories.isEmpty()); message_group->setEnabled(model->histories.isEmpty()); search_btn->setText(model->histories.isEmpty() ? tr("Find") : tr("Find Next")); reset_btn->setEnabled(!model->histories.isEmpty()); undo_btn->setEnabled(model->histories.size() > 1); search_btn->setEnabled(model->rowCount() > 0 || model->histories.isEmpty()); stats_label->setVisible(true); stats_label->setText(tr("%1 matches. right click on an item to create signal. double click to open message").arg(model->filtered_signals.size())); } void FindSignalDlg::customMenuRequested(const QPoint &pos) { if (auto index = view->indexAt(pos); index.isValid()) { QMenu menu(this); menu.addAction(tr("Create Signal")); if (menu.exec(view->mapToGlobal(pos))) { auto &s = model->filtered_signals[index.row()]; UndoStack::push(new AddSigCommand(s.id, s.sig)); emit openMessage(s.id); } } }