diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 5bc88f2d4e7cb4..c72ebf9d359d73 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -273,7 +274,7 @@ void BinaryViewModel::refresh() { row_count = can->lastMessage(msg_id).dat.size(); items.resize(row_count * column_count); } - int valid_rows = std::min(can->lastMessage(msg_id).dat.size(), row_count); + int valid_rows = std::min(can->lastMessage(msg_id).dat.size(), row_count); for (int i = 0; i < valid_rows * column_count; ++i) { items[i].valid = true; } @@ -311,7 +312,7 @@ void BinaryViewModel::updateState() { int val = ((binary[i] >> (7 - j)) & 1) != 0 ? 1 : 0; // Bit update frequency based highlighting double offset = !item.sigs.empty() ? 50 : 0; - auto n = last_msg.bit_change_counts[i][7 - j]; + auto n = last_msg.last_changes[i].bit_change_counts[j]; double min_f = n == 0 ? offset : offset + 25; double alpha = std::clamp(offset + log2(1.0 + factor * (double)n / (double)last_msg.count) * scaler, min_f, max_f); auto color = item.bg_color; @@ -334,13 +335,8 @@ QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, i } QVariant BinaryViewModel::data(const QModelIndex &index, int role) const { - if (role == Qt::ToolTipRole) { - auto item = (const BinaryViewModel::Item *)index.internalPointer(); - if (item && !item->sigs.empty()) { - return signalToolTip(item->sigs.back()); - } - } - return {}; + auto item = (const BinaryViewModel::Item *)index.internalPointer(); + return role == Qt::ToolTipRole && item && !item->sigs.empty() ? signalToolTip(item->sigs.back()) : QVariant(); } // BinaryItemDelegate @@ -388,7 +384,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op drawSignalCell(painter, option, index, s); } } - } else if (item->valid) { + } else if (item->valid && item->bg_color.alpha() > 0) { painter->fillRect(option.rect, item->bg_color); } auto color_role = item->sigs.contains(bin_view->hovered_sig) ? QPalette::BrightText : QPalette::Text; diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 63e103793c4550..0db99063e0be6b 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -100,7 +100,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_tim QObject::connect(&auto_scroll_timer, &QTimer::timeout, this, &ChartsWidget::doAutoScroll); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged); - QObject::connect(can, &AbstractStream::updated, this, &ChartsWidget::updateState); + QObject::connect(can, &AbstractStream::msgsReceived, this, &ChartsWidget::updateState); QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange); QObject::connect(new_plot_btn, &QToolButton::clicked, this, &ChartsWidget::newChart); QObject::connect(remove_all_btn, &QToolButton::clicked, this, &ChartsWidget::removeAll); @@ -324,7 +324,7 @@ void ChartsWidget::updateLayout(bool force) { charts_layout->addWidget(current_charts[i], i / n, i % n); if (current_charts[i]->sigs.empty()) { // the chart will be resized after add signal. delay setVisible to reduce flicker. - QTimer::singleShot(0, [c = current_charts[i]]() { c->setVisible(true); }); + QTimer::singleShot(0, current_charts[i], [c = current_charts[i]]() { c->setVisible(true); }); } else { current_charts[i]->setVisible(true); } diff --git a/tools/cabana/chart/signalselector.cc b/tools/cabana/chart/signalselector.cc index 50fe861a036fd3..0ddb212a8ad42b 100644 --- a/tools/cabana/chart/signalselector.cc +++ b/tools/cabana/chart/signalselector.cc @@ -44,9 +44,9 @@ SignalSelector::SignalSelector(QString title, QWidget *parent) : QDialog(parent) auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox, 3, 2); - for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { - if (auto m = dbc()->msg(it.key())) { - msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key().toString()), QVariant::fromValue(it.key())); + for (const auto &[id, _] : can->lastMessages()) { + if (auto m = dbc()->msg(id)) { + msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(id.toString()), QVariant::fromValue(id)); } } msgs_combo->model()->sort(0); diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index c923e4d5b27e5f..a22d9792128061 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -4,10 +4,8 @@ #include #include #include -#include -#include -DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent) { +DBCFile::DBCFile(const QString &dbc_file_name) { QFile file(dbc_file_name); if (file.open(QIODevice::ReadOnly)) { name_ = QFileInfo(dbc_file_name).baseName(); @@ -22,7 +20,7 @@ DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent } } -DBCFile::DBCFile(const QString &name, const QString &content, QObject *parent) : QObject(parent), name_(name), filename("") { +DBCFile::DBCFile(const QString &name, const QString &content) : name_(name), filename("") { // Open from clipboard parse(content); } diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index a3ab1cebe40111..ade6b249e2b16f 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -1,18 +1,15 @@ #pragma once #include -#include #include "tools/cabana/dbc/dbc.h" const QString AUTO_SAVE_EXTENSION = ".tmp"; -class DBCFile : public QObject { - Q_OBJECT - +class DBCFile { public: - DBCFile(const QString &dbc_file_name, QObject *parent=nullptr); - DBCFile(const QString &name, const QString &content, QObject *parent=nullptr); + DBCFile(const QString &dbc_file_name); + DBCFile(const QString &name, const QString &content); ~DBCFile() {} bool save(); diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 459ca0111d5310..87a7d962c558e6 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -7,7 +7,7 @@ bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QS try { auto it = std::find_if(dbc_files.begin(), dbc_files.end(), [&](auto &f) { return f.second && f.second->filename == dbc_file_name; }); - auto file = (it != dbc_files.end()) ? it->second : std::make_shared(dbc_file_name, this); + auto file = (it != dbc_files.end()) ? it->second : std::make_shared(dbc_file_name); for (auto s : sources) { dbc_files[s] = file; } @@ -22,7 +22,7 @@ bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QS bool DBCManager::open(const SourceSet &sources, const QString &name, const QString &content, QString *error) { try { - auto file = std::make_shared(name, content, this); + auto file = std::make_shared(name, content); for (auto s : sources) { dbc_files[s] = file; } @@ -189,6 +189,13 @@ const SourceSet DBCManager::sources(const DBCFile *dbc_file) const { return sources; } +QString toString(const SourceSet &ss) { + return std::accumulate(ss.cbegin(), ss.cend(), QString(), [](QString str, int source) { + if (!str.isEmpty()) str += ", "; + return str + (source == -1 ? QStringLiteral("all") : QString::number(source)); + }); +} + DBCManager *dbc() { static DBCManager dbc_manager(nullptr); return &dbc_manager; diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 5f782fc930ad6f..53a77a2c13357b 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -66,15 +67,8 @@ class DBCManager : public QObject { DBCManager *dbc(); +QString toString(const SourceSet &ss); inline QString msgName(const MessageId &id) { auto msg = dbc()->msg(id); return msg ? msg->name : UNTITLED; } - -inline QString toString(const SourceSet &ss) { - QStringList ret; - for (auto s : ss) { - ret << (s == -1 ? QString("all") : QString::number(s)); - } - return ret.join(", "); -} diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 472ffce104db0b..7befadb7227169 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -147,8 +147,8 @@ void DetailWidget::refresh() { warning_widget->setVisible(!warnings.isEmpty()); } -void DetailWidget::updateState(const QHash *msgs) { - if ((msgs && !msgs->contains(msg_id))) +void DetailWidget::updateState(const std::set *msgs) { + if ((msgs && !msgs->count(msg_id))) return; if (tab_widget->currentIndex() == 0) diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 5bb4b7f305b67b..15e1ee5f2f954b 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "selfdrive/ui/qt/widgets/controls.h" #include "tools/cabana/binaryview.h" @@ -11,7 +12,6 @@ #include "tools/cabana/historylog.h" #include "tools/cabana/signalview.h" -class MainWindow; class EditMessageDialog : public QDialog { public: EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent); @@ -39,7 +39,7 @@ class DetailWidget : public QWidget { void showTabBarContextMenu(const QPoint &pt); void editMsg(); void removeMsg(); - void updateState(const QHash * msgs = nullptr); + void updateState(const std::set *msgs = nullptr); MessageId msg_id; QLabel *warning_icon, *warning_label; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 53305499635daf..b3440b557bcb7c 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -7,7 +7,6 @@ #include #include "tools/cabana/commands.h" -// HistoryLogModel QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { const bool show_signals = display_signals_mode && sigs.size() > 0; @@ -17,11 +16,11 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2); } int i = index.column() - 1; - return show_signals ? QString::number(m.sig_values[i], 'f', sigs[i]->precision) : toHex(m.data); + return show_signals ? QString::number(m.sig_values[i], 'f', sigs[i]->precision) : QString(); } else if (role == ColorsRole) { - return QVariant::fromValue(m.colors); + return QVariant::fromValue((void *)(&m.colors)); } else if (role == BytesRole) { - return m.data; + return QVariant::fromValue((void *)(&m.data)); } else if (role == Qt::TextAlignmentRole) { return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); } @@ -123,7 +122,7 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) { template std::deque HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) { std::deque msgs; - QVector values(sigs.size()); + std::vector values(sigs.size()); for (; first != last && (*first)->mono_time > min_time; ++first) { const CanEvent *e = *first; for (int i = 0; i < sigs.size(); ++i) { @@ -132,7 +131,7 @@ std::deque HistoryLogModel::fetchData(InputIt first, I if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { auto &m = msgs.emplace_back(); m.mono_time = e->mono_time; - m.data = QByteArray((const char *)e->dat, e->size); + m.data.assign(e->dat, e->dat + e->size); m.sig_values = values; if (msgs.size() >= batch_size && min_time == 0) { return msgs; @@ -146,7 +145,7 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti const auto &events = can->events(msg_id); const auto freq = can->lastMessage(msg_id).freq; const bool update_colors = !display_signals_mode || sigs.empty(); - + const std::vector no_mask; const auto speed = can->getSpeed(); if (dynamic_mode) { auto first = std::upper_bound(events.rbegin(), events.rend(), from_time, [](uint64_t ts, auto e) { @@ -155,7 +154,7 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti auto msgs = fetchData(first, events.rend(), min_time); if (update_colors && (min_time > 0 || messages.empty())) { for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { - hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); + hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, no_mask, freq); it->colors = hex_colors.colors; } } @@ -166,7 +165,7 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti auto msgs = fetchData(first, events.cend(), 0); if (update_colors) { for (auto it = msgs.begin(); it != msgs.end(); ++it) { - hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); + hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, no_mask, freq); it->colors = hex_colors.colors; } } @@ -177,7 +176,7 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti // HeaderView QSize HeaderView::sectionSizeFromContents(int logicalIndex) const { - static QSize time_col_size = fontMetrics().boundingRect({0, 0, 200, 200}, defaultAlignment(), "000000.000").size() + QSize(10, 6); + static const QSize time_col_size = fontMetrics().boundingRect({0, 0, 200, 200}, defaultAlignment(), "000000.000").size() + QSize(10, 6); if (logicalIndex == 0) { return time_col_size; } else { @@ -237,10 +236,11 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { main_layout->addWidget(logs = new QTableView(this)); logs->setModel(model = new HistoryLogModel(this)); delegate = new MessageBytesDelegate(this); - logs->setItemDelegateForColumn(1, new MessageBytesDelegate(this)); logs->setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); logs->horizontalHeader()->setDefaultAlignment(Qt::AlignRight | (Qt::Alignment)Qt::TextWordWrap); logs->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + logs->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + logs->verticalHeader()->setDefaultSectionSize(delegate->sizeForBytes(8).height()); logs->verticalHeader()->setVisible(false); logs->setFrameShape(QFrame::NoFrame); diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index a68fbdbf435c15..154b139fb072c6 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -46,9 +46,9 @@ public slots: public: struct Message { uint64_t mono_time = 0; - QVector sig_values; - QByteArray data; - QVector colors; + std::vector sig_values; + std::vector data; + std::vector colors; }; template diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 4f27dcf5ed30fb..0053b08fd29bfe 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -21,6 +21,7 @@ #include "tools/cabana/commands.h" #include "tools/cabana/streamselector.h" #include "tools/cabana/tools/findsignal.h" +#include "tools/replay/replay.h" MainWindow::MainWindow() : QMainWindow() { createDockWindows(); @@ -84,8 +85,8 @@ void MainWindow::createActions() { close_stream_act->setEnabled(false); file_menu->addSeparator(); - file_menu->addAction(tr("New DBC File"), [this]() { newFile(); })->setShortcuts(QKeySequence::New); - file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); })->setShortcuts(QKeySequence::Open); + file_menu->addAction(tr("New DBC File"), [this]() { newFile(); }, QKeySequence::New); + file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); }, QKeySequence::Open); manage_dbcs_menu = file_menu->addMenu(tr("Manage &DBC Files")); @@ -111,19 +112,15 @@ void MainWindow::createActions() { file_menu->addAction(tr("Load DBC From Clipboard"), [=]() { loadFromClipboard(); }); file_menu->addSeparator(); - save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save); - save_dbc->setShortcuts(QKeySequence::Save); - - save_dbc_as = file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs); - save_dbc_as->setShortcuts(QKeySequence::SaveAs); - + save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save, QKeySequence::Save); + save_dbc_as = file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs, QKeySequence::SaveAs); copy_dbc_to_clipboard = file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveToClipboard); file_menu->addSeparator(); - file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption)->setShortcuts(QKeySequence::Preferences); + file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption, QKeySequence::Preferences); file_menu->addSeparator(); - file_menu->addAction(tr("E&xit"), qApp, &QApplication::closeAllWindows)->setShortcuts(QKeySequence::Quit); + file_menu->addAction(tr("E&xit"), qApp, &QApplication::closeAllWindows, QKeySequence::Quit); // Edit Menu QMenu *edit_menu = menuBar()->addMenu(tr("&Edit")); @@ -157,7 +154,7 @@ void MainWindow::createActions() { // Help Menu QMenu *help_menu = menuBar()->addMenu(tr("&Help")); - help_menu->addAction(tr("Help"), this, &MainWindow::onlineHelp)->setShortcuts(QKeySequence::HelpContents); + help_menu->addAction(tr("Help"), this, &MainWindow::onlineHelp, QKeySequence::HelpContents); help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); } @@ -374,7 +371,7 @@ void MainWindow::eventsMerged() { auto dbc_name = fingerprint_to_dbc[car_fingerprint]; if (dbc_name != QJsonValue::Undefined) { // Prevent dialog that load autosaved file from blocking replay->start(). - QTimer::singleShot(0, [dbc_name, this]() { loadDBCFromOpendbc(dbc_name.toString()); }); + QTimer::singleShot(0, this, [dbc_name, this]() { loadDBCFromOpendbc(dbc_name.toString()); }); } } } @@ -471,11 +468,7 @@ void MainWindow::saveFileToClipboard(DBCFile *dbc_file) { void MainWindow::updateLoadSaveMenus() { int cnt = dbc()->nonEmptyDBCCount(); - if (cnt > 1) { - save_dbc->setText(tr("Save %1 DBCs...").arg(dbc()->dbcCount())); - } else { - save_dbc->setText(tr("Save DBC...")); - } + save_dbc->setText(cnt > 1 ? tr("Save %1 DBCs...").arg(cnt) : tr("Save DBC...")); save_dbc->setEnabled(cnt > 0); save_dbc_as->setEnabled(cnt == 1); diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index aea09c55dee152..29596e32532356 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -11,11 +11,6 @@ #include "tools/cabana/commands.h" -static QString msg_node_from_id(const MessageId &id) { - auto msg = dbc()->msg(id); - return msg ? msg->transmitter : QString(); -} - MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); @@ -23,12 +18,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget // toolbar main_layout->addWidget(createToolBar()); // message table - view = new MessageView(this); - model = new MessageListModel(this); - header = new MessageViewHeader(this); - view->setItemDelegate(delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes)); - view->setHeader(header); - view->setModel(model); + main_layout->addWidget(view = new MessageView(this)); + view->setItemDelegate(delegate = new MessageBytesDelegate(view, settings.multiple_lines_hex)); + view->setModel(model = new MessageListModel(this)); + view->setHeader(header = new MessageViewHeader(this)); view->setSortingEnabled(true); view->sortByColumn(MessageListModel::Column::NAME, Qt::AscendingOrder); view->setAllColumnsShowFocus(true); @@ -36,6 +29,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget view->setItemsExpandable(false); view->setIndentation(0); view->setRootIsDecorated(false); + view->setUniformRowHeights(!settings.multiple_lines_hex); // Must be called before setting any header parameters to avoid overriding restoreHeaderState(settings.message_header_state); @@ -44,15 +38,14 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget header->setStretchLastSection(true); header->setContextMenuPolicy(Qt::CustomContextMenu); - main_layout->addWidget(view); - // suppress QHBoxLayout *suppress_layout = new QHBoxLayout(); - suppress_add = new QPushButton("Suppress Highlighted"); - suppress_clear = new QPushButton(); - suppress_layout->addWidget(suppress_add); - suppress_layout->addWidget(suppress_clear); - QCheckBox *suppress_defined_signals = new QCheckBox(tr("Suppress Defined Signals"), this); + suppress_layout->addWidget(suppress_add = new QPushButton("Suppress Highlighted")); + suppress_layout->addWidget(suppress_clear = new QPushButton()); + suppress_clear->setToolTip(tr("Clear suppressed")); + suppress_layout->addStretch(1); + QCheckBox *suppress_defined_signals = new QCheckBox(tr("Suppress Signals"), this); + suppress_defined_signals->setToolTip(tr("Suppress defined signals")); suppress_defined_signals->setChecked(settings.suppress_defined_signals); suppress_layout->addWidget(suppress_defined_signals); main_layout->addLayout(suppress_layout); @@ -62,10 +55,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget QObject::connect(header, &MessageViewHeader::filtersUpdated, model, &MessageListModel::setFilterStrings); QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent); QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions); - QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, [=](int state) { - settings.suppress_defined_signals = (state == Qt::Checked); - emit settings.changed(); - }); + QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, can, &AbstractStream::suppressDefinedSignals); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MessagesWidget::dbcModified); @@ -76,24 +66,17 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget view->updateBytesSectionSize(); }); QObject::connect(view->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { - if (current.isValid() && current.row() < model->msgs.size()) { - auto &id = model->msgs[current.row()]; + if (current.isValid() && current.row() < model->items_.size()) { + const auto &id = model->items_[current.row()].id; if (!current_msg_id || id != *current_msg_id) { current_msg_id = id; emit msgSelectionChanged(*current_msg_id); } } }); - QObject::connect(suppress_add, &QPushButton::clicked, [=]() { - model->suppress(); - updateSuppressedButtons(); - }); - QObject::connect(suppress_clear, &QPushButton::clicked, [=]() { - model->clearSuppress(); - updateSuppressedButtons(); - }); - - updateSuppressedButtons(); + QObject::connect(suppress_add, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted); + QObject::connect(suppress_clear, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted); + suppressHighlighted(); setWhatsThis(tr(R"( Message View
@@ -126,19 +109,22 @@ void MessagesWidget::dbcModified() { } void MessagesWidget::selectMessage(const MessageId &msg_id) { - auto it = std::find(model->msgs.cbegin(), model->msgs.cend(), msg_id); - if (it != model->msgs.cend()) { - view->setCurrentIndex(model->index(std::distance(model->msgs.cbegin(), it), 0)); + auto it = std::find_if(model->items_.cbegin(), model->items_.cend(), + [&msg_id](auto &item) { return item.id == msg_id; }); + if (it != model->items_.cend()) { + view->setCurrentIndex(model->index(std::distance(model->items_.cbegin(), it), 0)); } } -void MessagesWidget::updateSuppressedButtons() { - if (model->suppressed_bytes.empty()) { - suppress_clear->setEnabled(false); - suppress_clear->setText("Clear Suppressed"); - } else { +void MessagesWidget::suppressHighlighted() { + if (sender() == suppress_add) { + size_t n = can->suppressHighlighted(); + suppress_clear->setText(tr("Clear (%1)").arg(n)); suppress_clear->setEnabled(true); - suppress_clear->setText(QString("Clear Suppressed (%1)").arg(model->suppressed_bytes.size())); + } else { + can->clearSuppressed(); + suppress_clear->setText(tr("Clear")); + suppress_clear->setEnabled(false); } } @@ -160,12 +146,13 @@ void MessagesWidget::menuAboutToShow() { menu->addSeparator(); auto action = menu->addAction(tr("Mutlti-Line bytes"), this, &MessagesWidget::setMultiLineBytes); action->setCheckable(true); - action->setChecked(settings.multiple_lines_bytes); + action->setChecked(settings.multiple_lines_hex); } void MessagesWidget::setMultiLineBytes(bool multi) { - settings.multiple_lines_bytes = multi; + settings.multiple_lines_hex = multi; delegate->setMultipleLines(multi); + view->setUniformRowHeights(!multi); view->updateBytesSectionSize(); view->doItemsLayout(); } @@ -188,7 +175,7 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, } QVariant MessageListModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= msgs.size()) return {}; + if (!index.isValid() || index.row() >= items_.size()) return {}; auto getFreq = [](const CanData &d) { if (d.freq > 0 && (can->currentSec() - d.ts - 1.0 / settings.fps) < (5.0 / d.freq)) { @@ -198,33 +185,24 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { } }; - const auto &id = msgs[index.row()]; - auto &can_data = can->lastMessage(id); + const auto &item = items_[index.row()]; if (role == Qt::DisplayRole) { switch (index.column()) { - case Column::NAME: return msgName(id); - 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::NODE: return msg_node_from_id(id); - 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"; + case Column::NAME: return item.name; + case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : "N/A"; + case Column::ADDRESS: return QString::number(item.id.address, 16); + case Column::NODE: return item.node; + case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(*item.data) : "N/A"; + case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(item.data->count) : "N/A"; + case Column::DATA: return item.id.source != INVALID_SOURCE ? "" : "N/A"; } } else if (role == ColorsRole) { - QVector colors = can_data.colors; - if (!suppressed_bytes.empty()) { - for (int i = 0; i < colors.size(); i++) { - if (suppressed_bytes.contains({id, i})) { - colors[i] = QColor(255, 255, 255, 0); - } - } - } - return QVariant::fromValue(colors); - } else if (role == BytesRole && index.column() == Column::DATA && id.source != INVALID_SOURCE) { - return can_data.dat; + return QVariant::fromValue((void*)(&item.data->colors)); + } else if (role == BytesRole && index.column() == Column::DATA && item.id.source != INVALID_SOURCE) { + return QVariant::fromValue((void*)(&item.data->dat)); } else if (role == Qt::ToolTipRole && index.column() == Column::NAME) { - auto msg = dbc()->msg(id); - auto tooltip = msg ? msg->name : UNTITLED; + auto msg = dbc()->msg(item.id); + auto tooltip = item.name; if (msg && !msg->comment.isEmpty()) tooltip += "
" + msg->comment + ""; return tooltip; } @@ -232,31 +210,31 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { } void MessageListModel::setFilterStrings(const QMap &filters) { - filter_str = filters; + filters_ = filters; filterAndSort(); } void MessageListModel::dbcModified() { - dbc_address.clear(); + dbc_messages_.clear(); for (const auto &[_, m] : dbc()->getMessages(-1)) { - dbc_address.insert(m.address); + dbc_messages_.insert(MessageId{.source = INVALID_SOURCE, .address = m.address}); } - filterAndSort(); + filterAndSort(true); } -void MessageListModel::sortMessages(std::vector &new_msgs) { - auto do_sort = [order = sort_order](std::vector &m, auto proj) { - std::sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) { +void MessageListModel::sortItems(std::vector &items) { + auto do_sort = [order = sort_order](std::vector &m, auto proj) { + std::stable_sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) { return order == Qt::AscendingOrder ? proj(l) < proj(r) : proj(l) > proj(r); }); }; switch (sort_column) { - case Column::NAME: do_sort(new_msgs, [](auto &id) { return std::make_pair(msgName(id), id); }); break; - case Column::SOURCE: do_sort(new_msgs, [](auto &id) { return std::tie(id.source, id); }); break; - case Column::ADDRESS: do_sort(new_msgs, [](auto &id) { return std::tie(id.address, id);}); break; - case Column::NODE: do_sort(new_msgs, [](auto &id) { return std::make_pair(msg_node_from_id(id), id);}); break; - case Column::FREQ: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).freq, id); }); break; - case Column::COUNT: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).count, id); }); break; + case Column::NAME: do_sort(items, [](auto &item) { return std::tie(item.name, item.id); }); break; + case Column::SOURCE: do_sort(items, [](auto &item) { return std::tie(item.id.source, item.id); }); break; + case Column::ADDRESS: do_sort(items, [](auto &item) { return std::tie(item.id.address, item.id);}); break; + case Column::NODE: do_sort(items, [](auto &item) { return std::tie(item.node, item.id);}); break; + case Column::FREQ: do_sort(items, [](auto &item) { return std::tie(item.data->freq, item.id); }); break; + case Column::COUNT: do_sort(items, [](auto &item) { return std::tie(item.data->count, item.id); }); break; } } @@ -275,83 +253,85 @@ static bool parseRange(const QString &filter, uint32_t value, int base = 10) { return ok && value >= min && value <= max; } -bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, const QMap &filters) { +bool MessageListModel::match(const MessageListModel::Item &item) { + if (filters_.isEmpty()) + return true; + bool match = true; - for (auto it = filters.cbegin(); it != filters.cend() && match; ++it) { + for (auto it = filters_.cbegin(); it != filters_.cend() && match; ++it) { const QString &txt = it.value(); - QRegularExpression re(txt, QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption); switch (it.key()) { case Column::NAME: { - const auto msg = dbc()->msg(id); - match = re.match(msg ? msg->name : UNTITLED).hasMatch(); - match = match || (msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), - [&re](const auto &s) { return re.match(s->name).hasMatch(); })); + match = item.name.contains(txt, Qt::CaseInsensitive); + if (!match) { + const auto m = dbc()->msg(item.id); + match = m && std::any_of(m->sigs.cbegin(), m->sigs.cend(), + [&txt](const auto &s) { return s->name.contains(txt, Qt::CaseInsensitive); }); + } break; } case Column::SOURCE: - match = parseRange(txt, id.source); + match = parseRange(txt, item.id.source); break; - case Column::ADDRESS: { - match = re.match(QString::number(id.address, 16)).hasMatch(); - match = match || parseRange(txt, id.address, 16); + case Column::ADDRESS: + match = QString::number(item.id.address, 16).contains(txt, Qt::CaseInsensitive); + match = match || parseRange(txt, item.id.address, 16); break; - } case Column::NODE: - match = re.match(msg_node_from_id(id)).hasMatch(); + match = item.node.contains(txt, Qt::CaseInsensitive); break; case Column::FREQ: // TODO: Hide stale messages? - match = parseRange(txt, data.freq); + match = parseRange(txt, item.data->freq); break; case Column::COUNT: - match = parseRange(txt, data.count); + match = parseRange(txt, item.data->count); break; - case Column::DATA: { - match = QString(data.dat.toHex()).contains(txt, Qt::CaseInsensitive); - match = match || re.match(QString(data.dat.toHex())).hasMatch(); - match = match || re.match(QString(data.dat.toHex(' '))).hasMatch(); + case Column::DATA: + match = utils::toHex(item.data->dat).contains(txt, Qt::CaseInsensitive); break; - } } } return match; } -void MessageListModel::filterAndSort() { - 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); +void MessageListModel::filterAndSort(bool force_reset) { + // merge CAN and DBC messages + std::vector all_messages; + all_messages.reserve(can->lastMessages().size() + dbc_messages_.size()); + auto dbc_msgs = dbc_messages_; + for (const auto &[id, m] : can->lastMessages()) { + all_messages.push_back(id); + dbc_msgs.erase(MessageId{.source = INVALID_SOURCE, .address = id.address}); } + std::copy(dbc_msgs.begin(), dbc_msgs.end(), std::back_inserter(all_messages)); - // 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); - } + // filter and sort + std::vector items; + for (const auto &id : all_messages) { + auto msg = dbc()->msg(id); + Item item = {.id = id, + .name = msg ? msg->name : UNTITLED, + .node = msg ? msg->transmitter : QString(), + .data = &can->lastMessage(id)}; + if (match(item)) + items.emplace_back(item); } + sortItems(items); - sortMessages(new_msgs); - - if (msgs != new_msgs) { + if (force_reset || items_ != items) { beginResetModel(); - msgs = std::move(new_msgs); + items_ = std::move(items); endResetModel(); } } -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)) { +void MessageListModel::msgsReceived(const std::set *new_msgs, bool has_new_ids) { + if (has_new_ids || filters_.contains(Column::FREQ) || filters_.contains(Column::COUNT) || filters_.contains(Column::DATA)) { filterAndSort(); } - for (int i = 0; i < msgs.size(); ++i) { - if (new_msgs->contains(msgs[i])) { + for (int i = 0; i < items_.size(); ++i) { + if (!new_msgs || new_msgs->count(items_[i].id)) { for (int col = Column::FREQ; col < columnCount(); ++col) emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole}); } @@ -359,31 +339,13 @@ void MessageListModel::msgsReceived(const QHash *new_msgs, b } void MessageListModel::sort(int column, Qt::SortOrder order) { - if (column != columnCount() - 1) { + if (column != Column::DATA) { sort_column = column; sort_order = order; filterAndSort(); } } -void MessageListModel::suppress() { - const double cur_ts = can->currentSec(); - - for (auto &id : msgs) { - auto &can_data = can->lastMessage(id); - for (int i = 0; i < can_data.dat.size(); i++) { - const double dt = cur_ts - can_data.last_change_t[i]; - if (dt < 2.0) { - suppressed_bytes.insert({id, i}); - } - } - } -} - -void MessageListModel::clearSuppress() { - suppressed_bytes.clear(); -} - // MessageView void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -414,14 +376,11 @@ void MessageView::updateBytesSectionSize() { auto delegate = ((MessageBytesDelegate *)itemDelegate()); int max_bytes = 8; if (!delegate->multipleLines()) { - for (auto it = can->last_msgs.constBegin(); it != can->last_msgs.constEnd(); ++it) { - max_bytes = std::max(max_bytes, it.value().dat.size()); + for (const auto &[_, m] : can->lastMessages()) { + max_bytes = std::max(max_bytes, m.dat.size()); } } - int width = delegate->widthForBytes(max_bytes); - if (header()->sectionSize(MessageListModel::Column::DATA) != width) { - header()->resizeSection(MessageListModel::Column::DATA, width); - } + header()->resizeSection(MessageListModel::Column::DATA, delegate->sizeForBytes(max_bytes).width()); } // MessageViewHeader @@ -446,8 +405,7 @@ void MessageViewHeader::updateHeaderPositions() { for (int i = 0; i < count(); i++) { if (editors[i]) { int h = editors[i]->sizeHint().height(); - editors[i]->move(sectionViewportPosition(i), sz.height()); - editors[i]->resize(sectionSize(i), h); + editors[i]->setGeometry(sectionViewportPosition(i), sz.height(), sectionSize(i), h); editors[i]->setHidden(isSectionHidden(i)); } } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 11d95dd3f2b67e..063154a2e54fc1 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,7 +10,6 @@ #include #include #include -#include #include #include @@ -34,23 +34,28 @@ Q_OBJECT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override { return Column::DATA + 1; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); } + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return items_.size(); } void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void setFilterStrings(const QMap &filters); - void msgsReceived(const QHash *new_msgs, bool has_new_ids); - void filterAndSort(); - void suppress(); - void clearSuppress(); + void msgsReceived(const std::set *new_msgs, bool has_new_ids); + void filterAndSort(bool force_reset = false); void dbcModified(); - std::vector msgs; - QSet> suppressed_bytes; + + struct Item { + MessageId id; + QString name; + QString node; + const CanData *data; + bool operator==(const Item &other) const { return id == other.id; } + }; + std::vector items_; private: - void sortMessages(std::vector &new_msgs); - bool matchMessage(const MessageId &id, const CanData &data, const QMap &filters); + void sortItems(std::vector &items); + bool match(const MessageListModel::Item &id); - QMap filter_str; - QSet dbc_address; + QMap filters_; + std::set dbc_messages_; int sort_column = 0; Qt::SortOrder sort_order = Qt::AscendingOrder; }; @@ -91,7 +96,7 @@ class MessagesWidget : public QWidget { void selectMessage(const MessageId &message_id); QByteArray saveHeaderState() const { return view->header()->saveState(); } bool restoreHeaderState(const QByteArray &state) const { return view->header()->restoreState(state); } - void updateSuppressedButtons(); + void suppressHighlighted(); public slots: void dbcModified(); diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index ac8d45007d65a2..17de0a1c0ad185 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -33,7 +33,7 @@ void settings_op(SettingOperation op) { op(s, "chart_series_type", settings.chart_series_type); op(s, "theme", settings.theme); op(s, "sparkline_range", settings.sparkline_range); - op(s, "multiple_lines_bytes", settings.multiple_lines_bytes); + op(s, "multiple_lines_hex", settings.multiple_lines_hex); op(s, "log_livestream", settings.log_livestream); op(s, "log_path", settings.log_path); op(s, "drag_direction", (int &)settings.drag_direction); diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index 9f24a6fbd370f7..e75c519ac73e90 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -33,7 +33,7 @@ class Settings : public QObject { int chart_series_type = 0; int theme = 0; int sparkline_range = 15; // 15 seconds - bool multiple_lines_bytes = true; + bool multiple_lines_hex = false; bool log_livestream = true; bool suppress_defined_signals = false; QString log_path; diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index b9795efedfa71b..3abcf4d1112835 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -36,8 +36,8 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) { Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig}; parent_item->children.insert(pos, item); - QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", - "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"}; + QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", + "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Table"}; for (int i = 0; i < std::size(titles); ++i) { item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)}); } @@ -68,10 +68,7 @@ void SignalModel::refresh() { } SignalModel::Item *SignalModel::getItem(const QModelIndex &index) const { - SignalModel::Item *item = nullptr; - if (index.isValid()) { - item = (SignalModel::Item *)index.internalPointer(); - } + auto item = index.isValid() ? (SignalModel::Item *)index.internalPointer() : nullptr; return item ? item : root.get(); } @@ -369,8 +366,7 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->setFont(label_font); QString freq = QString("%1 hz").arg(item->sparkline.freq(), 0, 'g', 2); painter->drawText(rect.adjusted(5, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, freq); - QFontMetrics fm(label_font); - value_adjust = fm.width(freq) + 10; + value_adjust = QFontMetrics(label_font).width(freq) + 10; } // signal value painter->setFont(option.font); @@ -622,13 +618,13 @@ void SignalView::handleSignalUpdated(const cabana::Signal *sig) { } } -void SignalView::updateState(const QHash *msgs) { +void SignalView::updateState(const std::set *msgs) { 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; + if (model->rowCount() == 0 || (msgs && !msgs->count(model->msg_id)) || last_msg.dat.size() == 0) return; for (auto item : model->root->children) { double value = 0; - if (item->sig->getValue((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), &value)) { + if (item->sig->getValue(last_msg.dat.data(), last_msg.dat.size(), &value)) { item->sig_val = item->sig->formatValue(value); } max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val)); diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index c08579bc557056..30978f928cfc8e 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -82,7 +83,7 @@ class SignalItemDelegate : public QStyledItemDelegate { public: SignalItemDelegate(QObject *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; @@ -117,7 +118,7 @@ class SignalView : public QFrame { void setSparklineRange(int value); void handleSignalAdded(MessageId id, const cabana::Signal *sig); void handleSignalUpdated(const cabana::Signal *sig); - void updateState(const QHash *msgs = nullptr); + void updateState(const std::set *msgs = nullptr); struct TreeView : public QTreeView { TreeView(QWidget *parent) : QTreeView(parent) {} diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index eb2859e5a3d477..c68259ed8b5dd4 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -1,8 +1,10 @@ #include "tools/cabana/streams/abstractstream.h" #include +#include -#include +#include "common/timing.h" +#include "tools/cabana/settings.h" static const int EVENT_NEXT_BUFFER_SIZE = 6 * 1024 * 1024; // 6MB @@ -15,82 +17,97 @@ StreamNotifier *StreamNotifier::instance() { AbstractStream::AbstractStream(QObject *parent) : QObject(parent) { assert(parent != nullptr); - new_msgs = std::make_unique>(); - event_buffer = std::make_unique(EVENT_NEXT_BUFFER_SIZE); + event_buffer_ = std::make_unique(EVENT_NEXT_BUFFER_SIZE); + QObject::connect(this, &AbstractStream::privateUpdateLastMsgsSignal, this, &AbstractStream::updateLastMessages, Qt::QueuedConnection); QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo); - QObject::connect(&settings, &Settings::changed, this, &AbstractStream::updateMasks); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &AbstractStream::updateMasks); QObject::connect(dbc(), &DBCManager::maskUpdated, this, &AbstractStream::updateMasks); QObject::connect(this, &AbstractStream::streamStarted, [this]() { emit StreamNotifier::instance()->changingStream(); delete can; can = this; - // TODO: add method stop() to class AbstractStream - QObject::connect(qApp, &QApplication::aboutToQuit, can, []() { - qDebug() << "stopping stream thread"; - can->pause(true); - }); emit StreamNotifier::instance()->streamStarted(); }); } void AbstractStream::updateMasks() { - std::lock_guard lk(mutex); - masks.clear(); - if (settings.suppress_defined_signals) { - for (auto s : sources) { - if (auto f = dbc()->findDBCFile(s)) { - for (const auto &[address, m] : f->getMessages()) { - masks[{.source = (uint8_t)s, .address = address}] = m.mask; - } + std::lock_guard lk(mutex_); + masks_.clear(); + if (!settings.suppress_defined_signals) + return; + + for (const auto s : sources) { + for (const auto &[address, m] : dbc()->getMessages(s)) { + masks_[{.source = (uint8_t)s, .address = address}] = m.mask; + } + } + // clear bit change counts + for (auto &[id, m] : messages_) { + auto &mask = masks_[id]; + const int size = std::min(mask.size(), m.last_changes.size()); + for (int i = 0; i < size; ++i) { + for (int j = 0; j < 8; ++j) { + if (((mask[i] >> (7 - j)) & 1) != 0) m.last_changes[i].bit_change_counts[j] = 0; + } + } + } +} + +void AbstractStream::suppressDefinedSignals(bool suppress) { + settings.suppress_defined_signals = suppress; + updateMasks(); +} + +size_t AbstractStream::suppressHighlighted() { + std::lock_guard lk(mutex_); + size_t cnt = 0; + const double cur_ts = currentSec(); + for (auto &[_, m] : messages_) { + for (auto &last_change : m.last_changes) { + const double dt = cur_ts - last_change.ts; + if (dt < 2.0) { + last_change.suppressed = true; } + // clear bit change counts + last_change.bit_change_counts.fill(0); + cnt += last_change.suppressed; } } + return cnt; +} + +void AbstractStream::clearSuppressed() { + std::lock_guard lk(mutex_); + for (auto &[_, m] : messages_) { + std::for_each(m.last_changes.begin(), m.last_changes.end(), [](auto &c) { c.suppressed = false; }); + } } -void AbstractStream::updateMessages(QHash *messages) { +void AbstractStream::updateLastMessages() { 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(); - sources.insert(id.source); + std::set msgs; + { + std::lock_guard lk(mutex_); + for (const auto &id : new_msgs_) { + last_msgs[id] = messages_[id]; + sources.insert(id.source); + } + msgs = std::move(new_msgs_); } + if (sources.size() != prev_src_size) { updateMasks(); emit sourcesUpdated(sources); } - emit updated(); - emit msgsReceived(messages, prev_msg_size != last_msgs.size()); - delete messages; - processing = false; + emit msgsReceived(&msgs, prev_msg_size != last_msgs.size()); } void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size) { - std::lock_guard lk(mutex); - auto mask_it = masks.find(id); - std::vector *mask = mask_it == masks.end() ? nullptr : &mask_it->second; - all_msgs[id].compute(id, (const char *)data, size, sec, getSpeed(), mask); - if (!new_msgs->contains(id)) { - new_msgs->insert(id, {}); - } -} - -bool AbstractStream::postEvents() { - // delay posting CAN message if UI thread is busy - if (processing == false) { - processing = true; - for (auto it = new_msgs->begin(); it != new_msgs->end(); ++it) { - it.value() = all_msgs[it.key()]; - } - // use pointer to avoid data copy in queued connection. - QMetaObject::invokeMethod(this, std::bind(&AbstractStream::updateMessages, this, new_msgs.release()), Qt::QueuedConnection); - new_msgs.reset(new QHash); - new_msgs->reserve(100); - return true; - } - return false; + std::lock_guard lk(mutex_); + messages_[id].compute(id, data, size, sec, getSpeed(), masks_[id]); + new_msgs_.insert(id); } const std::vector &AbstractStream::events(const MessageId &id) const { @@ -102,76 +119,62 @@ const std::vector &AbstractStream::events(const MessageId &id) const CanData &AbstractStream::lastMessage(const MessageId &id) { static CanData empty_data = {}; auto it = last_msgs.find(id); - return it != last_msgs.end() ? it.value() : empty_data; + return it != last_msgs.end() ? it->second : empty_data; } // it is thread safe to update data in updateLastMsgsTo. // updateLastMsgsTo is always called in UI thread. void AbstractStream::updateLastMsgsTo(double sec) { - new_msgs.reset(new QHash); - all_msgs.clear(); - last_msgs.clear(); + new_msgs_.clear(); + messages_.clear(); uint64_t last_ts = (sec + routeStartTime()) * 1e9; - for (auto &[id, ev] : events_) { - auto it = std::lower_bound(ev.crbegin(), ev.crend(), last_ts, [](auto e, uint64_t ts) { - return e->mono_time > ts; - }); - auto mask_it = masks.find(id); - std::vector *mask = mask_it == masks.end() ? nullptr : &mask_it->second; - if (it != ev.crend()) { - double ts = (*it)->mono_time / 1e9 - routeStartTime(); - auto &m = all_msgs[id]; - m.compute(id, (const char *)(*it)->dat, (*it)->size, ts, getSpeed(), mask); - m.count = std::distance(it, ev.crend()); + for (const auto &[id, ev] : events_) { + auto it = std::upper_bound(ev.begin(), ev.end(), last_ts, CompareCanEvent()); + if (it != ev.begin()) { + auto prev = std::prev(it); + double ts = (*prev)->mono_time / 1e9 - routeStartTime(); + auto &m = messages_[id]; + m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {}); + m.count = std::distance(ev.begin(), prev) + 1; } } - // deep copy all_msgs to last_msgs to avoid multi-threading issue. - last_msgs = all_msgs; - last_msgs.detach(); - // use a timer to prevent recursive calls - QTimer::singleShot(0, [this]() { - emit updated(); - emit msgsReceived(&last_msgs, true); - }); + bool id_changed = messages_.size() != last_msgs.size() || + std::any_of(messages_.cbegin(), messages_.cend(), + [this](const auto &m) { return !last_msgs.count(m.first); }); + last_msgs = messages_; + emit msgsReceived(nullptr, id_changed); } -void AbstractStream::mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last) { - static MessageEventsMap msg_events; - static std::vector new_events; +const CanEvent *AbstractStream::newEvent(uint64_t mono_time, const cereal::CanData::Reader &c) { + auto dat = c.getDat(); + CanEvent *e = (CanEvent *)event_buffer_->allocate(sizeof(CanEvent) + sizeof(uint8_t) * dat.size()); + e->src = c.getSrc(); + e->address = c.getAddress(); + e->mono_time = mono_time; + e->size = dat.size(); + memcpy(e->dat, (uint8_t *)dat.begin(), e->size); + return e; +} +void AbstractStream::mergeEvents(const std::vector &events) { + static MessageEventsMap msg_events; std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); }); - new_events.clear(); - - for (auto it = first; it != last; ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - uint64_t ts = (*it)->mono_time; - for (const auto &c : (*it)->event.getCan()) { - auto dat = c.getDat(); - CanEvent *e = (CanEvent *)event_buffer->allocate(sizeof(CanEvent) + sizeof(uint8_t) * dat.size()); - e->src = c.getSrc(); - e->address = c.getAddress(); - e->mono_time = ts; - e->size = dat.size(); - memcpy(e->dat, (uint8_t *)dat.begin(), e->size); - - msg_events[{.source = e->src, .address = e->address}].push_back(e); - new_events.push_back(e); - } - } + for (auto e : events) { + msg_events[{.source = e->src, .address = e->address}].push_back(e); } - if (!new_events.empty()) { - for (auto &[id, new_e] : msg_events) { + if (!events.empty()) { + for (const auto &[id, new_e] : msg_events) { if (!new_e.empty()) { auto &e = events_[id]; auto pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent()); e.insert(pos, new_e.cbegin(), new_e.cend()); } } - auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent()); - all_events_.insert(pos, new_events.cbegin(), new_events.cend()); + auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), events.front()->mono_time, CompareCanEvent()); + all_events_.insert(pos, events.cbegin(), events.cend()); emit eventsMerged(msg_events); } lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time; @@ -181,15 +184,16 @@ void AbstractStream::mergeEvents(std::vector::const_iterator first, std namespace { -constexpr int periodic_threshold = 10; -constexpr int start_alpha = 128; -constexpr float fade_time = 2.0; -const QColor CYAN = QColor(0, 187, 255, start_alpha); -const QColor RED = QColor(255, 0, 0, start_alpha); -const QColor GREYISH_BLUE = QColor(102, 86, 169, start_alpha / 2); -const QColor CYAN_LIGHTER = QColor(0, 187, 255, start_alpha).lighter(135); -const QColor RED_LIGHTER = QColor(255, 0, 0, start_alpha).lighter(135); -const QColor GREYISH_BLUE_LIGHTER = QColor(102, 86, 169, start_alpha / 2).lighter(135); +enum Color { GREYISH_BLUE, CYAN, RED}; +QColor getColor(int c) { + constexpr int start_alpha = 128; + static const QColor colors[] = { + [GREYISH_BLUE] = QColor(102, 86, 169, start_alpha / 2), + [CYAN] = QColor(0, 187, 255, start_alpha), + [RED] = QColor(255, 0, 0, start_alpha), + }; + return settings.theme == LIGHT_THEME ? colors[c] : colors[c].lighter(135); +} inline QColor blend(const QColor &a, const QColor &b) { return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); @@ -212,8 +216,8 @@ double calc_freq(const MessageId &msg_id, double current_sec) { } // namespace -void CanData::compute(const MessageId &msg_id, const char *can_data, const int size, double current_sec, - double playback_speed, const std::vector *mask, double in_freq) { +void CanData::compute(const MessageId &msg_id, const uint8_t *can_data, const int size, double current_sec, + double playback_speed, const std::vector &mask, double in_freq) { ts = current_sec; ++count; @@ -224,55 +228,53 @@ void CanData::compute(const MessageId &msg_id, const char *can_data, const int s if (dat.size() != size) { dat.resize(size); - bit_change_counts.resize(size); - colors = QVector(size, QColor(0, 0, 0, 0)); - last_change_t.assign(size, ts); - last_delta.resize(size); - same_delta_counter.resize(size); + colors.assign(size, QColor(0, 0, 0, 0)); + last_changes.resize(size); + std::for_each(last_changes.begin(), last_changes.end(), [current_sec](auto &c) { c.ts = current_sec; }); } else { - bool lighter = settings.theme == DARK_THEME; - const QColor &cyan = !lighter ? CYAN : CYAN_LIGHTER; - const QColor &red = !lighter ? RED : RED_LIGHTER; - const QColor &greyish_blue = !lighter ? GREYISH_BLUE : GREYISH_BLUE_LIGHTER; + constexpr int periodic_threshold = 10; + constexpr float fade_time = 2.0; + const float alpha_delta = 1.0 / (freq + 1) / (fade_time * playback_speed); for (int i = 0; i < size; ++i) { - const uint8_t mask_byte = (mask && i < mask->size()) ? (~((*mask)[i])) : 0xff; + auto &last_change = last_changes[i]; + + uint8_t mask_byte = last_change.suppressed ? 0x00 : 0xFF; + if (i < mask.size()) mask_byte &= ~(mask[i]); + const uint8_t last = dat[i] & mask_byte; const uint8_t cur = can_data[i] & mask_byte; - const int delta = cur - last; - if (last != cur) { - double delta_t = ts - last_change_t[i]; - + const int delta = cur - last; // Keep track if signal is changing randomly, or mostly moving in the same direction - if (std::signbit(delta) == std::signbit(last_delta[i])) { - same_delta_counter[i] = std::min(16, same_delta_counter[i] + 1); + if (std::signbit(delta) == std::signbit(last_change.delta)) { + last_change.same_delta_counter = std::min(16, last_change.same_delta_counter + 1); } else { - same_delta_counter[i] = std::max(0, same_delta_counter[i] - 4); + last_change.same_delta_counter = std::max(0, last_change.same_delta_counter - 4); } + const double delta_t = ts - last_change.ts; // Mostly moves in the same direction, color based on delta up/down - if (delta_t * freq > periodic_threshold || same_delta_counter[i] > 8) { + if (delta_t * freq > periodic_threshold || last_change.same_delta_counter > 8) { // Last change was while ago, choose color based on delta up or down - colors[i] = (cur > last) ? cyan : red; + colors[i] = getColor(cur > last ? CYAN : RED); } else { // Periodic changes - colors[i] = blend(colors[i], greyish_blue); + colors[i] = blend(colors[i], getColor(GREYISH_BLUE)); } // Track bit level changes const uint8_t tmp = (cur ^ last); for (int bit = 0; bit < 8; bit++) { - if (tmp & (1 << bit)) { - bit_change_counts[i][bit] += 1; + if (tmp & (1 << (7 - bit))) { + last_change.bit_change_counts[bit] += 1; } } - last_change_t[i] = ts; - last_delta[i] = delta; + last_change.ts = ts; + last_change.delta = delta; } else { // Fade out - float alpha_delta = 1.0 / (freq + 1) / (fade_time * playback_speed); colors[i].setAlphaF(std::max(0.0, colors[i].alphaF() - alpha_delta)); } } diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 02ebc4b5d180e7..16d4040d624465 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -1,34 +1,37 @@ #pragma once #include -#include #include +#include +#include #include #include #include #include -#include -#include "common/timing.h" +#include "cereal/messaging/messaging.h" #include "tools/cabana/dbc/dbcmanager.h" -#include "tools/cabana/settings.h" #include "tools/cabana/util.h" -#include "tools/replay/replay.h" struct CanData { - void compute(const MessageId &msg_id, const char *dat, const int size, double current_sec, - double playback_speed, const std::vector *mask = nullptr, double in_freq = 0); + void compute(const MessageId &msg_id, const uint8_t *dat, const int size, double current_sec, + double playback_speed, const std::vector &mask, double in_freq = 0); double ts = 0.; uint32_t count = 0; double freq = 0; - QByteArray dat; - QVector colors; - std::vector last_change_t; - std::vector> bit_change_counts; - std::vector last_delta; - std::vector same_delta_counter; + std::vector dat; + std::vector colors; + + struct ByteLastChange { + double ts; + int delta; + int same_delta_counter; + bool suppressed; + std::array bit_change_counts; + }; + std::vector last_changes; double last_freq_update_ts = 0; }; @@ -60,7 +63,7 @@ class AbstractStream : public QObject { AbstractStream(QObject *parent); virtual ~AbstractStream() {} virtual void start() = 0; - inline bool liveStreaming() const { return route() == nullptr; } + virtual bool liveStreaming() const { return true; } virtual void seekTo(double ts) {} virtual QString routeName() const = 0; virtual QString carFingerprint() const { return ""; } @@ -68,48 +71,57 @@ class AbstractStream : public QObject { virtual double routeStartTime() const { return 0; } virtual double currentSec() const = 0; virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); } - const CanData &lastMessage(const MessageId &id); - virtual const Route *route() const { return nullptr; } virtual void setSpeed(float speed) {} virtual double getSpeed() { return 1; } virtual bool isPaused() const { return false; } virtual void pause(bool pause) {} - const MessageEventsMap &eventsMap() const { return events_; } - const std::vector &allEvents() const { return all_events_; } + + inline const std::unordered_map &lastMessages() const { return last_msgs; } + inline const MessageEventsMap &eventsMap() const { return events_; } + inline const std::vector &allEvents() const { return all_events_; } + const CanData &lastMessage(const MessageId &id); const std::vector &events(const MessageId &id) const; + size_t suppressHighlighted(); + void clearSuppressed(); + void suppressDefinedSignals(bool suppress); + signals: void paused(); void resume(); void seekedTo(double sec); void streamStarted(); void eventsMerged(const MessageEventsMap &events_map); - void updated(); - void msgsReceived(const QHash *new_msgs, bool has_new_ids); + void msgsReceived(const std::set *new_msgs, bool has_new_ids); void sourcesUpdated(const SourceSet &s); + void privateUpdateLastMsgsSignal(); public: - QHash last_msgs; SourceSet sources; protected: - void mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last); - bool postEvents(); - uint64_t lastEventMonoTime() const { return lastest_event_ts; } + void mergeEvents(const std::vector &events); + const CanEvent *newEvent(uint64_t mono_time, const cereal::CanData::Reader &c); void updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size); - void updateMessages(QHash *); - void updateMasks(); - void updateLastMsgsTo(double sec); + uint64_t lastEventMonoTime() const { return lastest_event_ts; } + std::vector all_events_; uint64_t lastest_event_ts = 0; - std::atomic processing = false; - std::unique_ptr> new_msgs; - QHash all_msgs; + +private: + void updateLastMessages(); + void updateLastMsgsTo(double sec); + void updateMasks(); + MessageEventsMap events_; - std::vector all_events_; - std::unique_ptr event_buffer; - std::mutex mutex; - std::unordered_map> masks; + std::unordered_map last_msgs; + std::unique_ptr event_buffer_; + + // Members accessed in multiple threads. (mutex protected) + std::mutex mutex_; + std::set new_msgs_; + std::unordered_map messages_; + std::unordered_map> masks_; }; class AbstractOpenStreamWidget : public QWidget { diff --git a/tools/cabana/streams/devicestream.cc b/tools/cabana/streams/devicestream.cc index 349a2d7a1cf73d..80507391a7d085 100644 --- a/tools/cabana/streams/devicestream.cc +++ b/tools/cabana/streams/devicestream.cc @@ -8,6 +8,7 @@ #include #include #include +#include // DeviceStream @@ -21,17 +22,14 @@ void DeviceStream::streamThread() { std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); std::unique_ptr sock(SubSocket::create(context.get(), "can", address)); assert(sock != NULL); - sock->setTimeout(50); // run as fast as messages come in while (!QThread::currentThread()->isInterruptionRequested()) { - Message *msg = sock->receive(true); + std::unique_ptr msg(sock->receive(true)); if (!msg) { QThread::msleep(50); continue; } - - handleEvent(msg->getData(), msg->getSize()); - delete msg; + handleEvent(kj::ArrayPtr((capnp::word*)msg->getData(), msg->getSize() / sizeof(capnp::word))); } } diff --git a/tools/cabana/streams/livestream.cc b/tools/cabana/streams/livestream.cc index fd140eb8bf6f70..4e1f6d77d9f6ef 100644 --- a/tools/cabana/streams/livestream.cc +++ b/tools/cabana/streams/livestream.cc @@ -1,12 +1,17 @@ #include "tools/cabana/streams/livestream.h" +#include #include +#include #include +#include "common/timing.h" +#include "common/util.h" + struct LiveStream::Logger { Logger() : start_ts(seconds_since_epoch()), segment_num(-1) {} - void write(const char *data, const size_t size) { + void write(kj::ArrayPtr data) { int n = (seconds_since_epoch() - start_ts) / 60.0; if (std::exchange(segment_num, n) != segment_num) { QString dir = QString("%1/%2--%3") @@ -17,7 +22,8 @@ struct LiveStream::Logger { fs.reset(new std::ofstream((dir + "/rlog").toStdString(), std::ios::binary | std::ios::out)); } - fs->write(data, size); + auto bytes = data.asBytes(); + fs->write((const char*)bytes.begin(), bytes.size()); } std::unique_ptr fs; @@ -57,14 +63,20 @@ LiveStream::~LiveStream() { } // called in streamThread -void LiveStream::handleEvent(const char *data, const size_t size) { +void LiveStream::handleEvent(kj::ArrayPtr data) { if (logger) { - logger->write(data, size); + logger->write(data); } - std::lock_guard lk(lock); - auto &msg = receivedMessages.emplace_back(data, size); - receivedEvents.push_back(msg.event); + capnp::FlatArrayMessageReader reader(data); + auto event = reader.getRoot(); + if (event.which() == cereal::Event::Which::CAN) { + const uint64_t mono_time = event.getLogMonoTime(); + std::lock_guard lk(lock); + for (const auto &c : event.getCan()) { + received_events_.push_back(newEvent(mono_time, c)); + } + } } void LiveStream::timerEvent(QTimerEvent *event) { @@ -72,9 +84,8 @@ void LiveStream::timerEvent(QTimerEvent *event) { { // merge events received from live stream thread. std::lock_guard lk(lock); - mergeEvents(receivedEvents.cbegin(), receivedEvents.cend()); - receivedEvents.clear(); - receivedMessages.clear(); + mergeEvents(received_events_); + received_events_.clear(); } if (!all_events_.empty()) { begin_event_ts = all_events_.front()->mono_time; @@ -112,7 +123,7 @@ void LiveStream::updateEvents() { updateEvent(id, (e->mono_time - begin_event_ts) / 1e9, e->dat, e->size); current_event_ts = e->mono_time; } - postEvents(); + emit privateUpdateLastMsgsSignal(); } void LiveStream::seekTo(double sec) { diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h index d72112c6049dff..719ea15c2476d0 100644 --- a/tools/cabana/streams/livestream.h +++ b/tools/cabana/streams/livestream.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -26,26 +25,16 @@ class LiveStream : public AbstractStream { protected: virtual void streamThread() = 0; - void handleEvent(const char *data, const size_t size); + void handleEvent(kj::ArrayPtr event); private: void startUpdateTimer(); void timerEvent(QTimerEvent *event) override; void updateEvents(); - struct Msg { - Msg(const char *data, const size_t size) { - event = ::new Event(aligned_buf.align(data, size)); - } - ~Msg() { ::delete event; } - Event *event; - AlignedBuffer aligned_buf; - }; - std::mutex lock; QThread *stream_thread; - std::vector receivedEvents; - std::deque receivedMessages; + std::vector received_events_; int timer_id; QBasicTimer update_timer; diff --git a/tools/cabana/streams/pandastream.cc b/tools/cabana/streams/pandastream.cc index 13d202d9cab320..bea1fd7480bfc9 100644 --- a/tools/cabana/streams/pandastream.cc +++ b/tools/cabana/streams/pandastream.cc @@ -1,15 +1,13 @@ #include "tools/cabana/streams/pandastream.h" -#include - +#include #include #include #include #include +#include #include -#include "selfdrive/ui/qt/util.h" - // TODO: remove clearLayout static void clearLayout(QLayout* layout) { while (layout->count() > 0) { @@ -90,7 +88,6 @@ void PandaStream::streamThread() { MessageBuilder msg; auto evt = msg.initEvent(); auto canData = evt.initCan(raw_can_data.size()); - for (uint i = 0; isend_heartbeat(false); } diff --git a/tools/cabana/streams/pandastream.h b/tools/cabana/streams/pandastream.h index 43803950f9eec3..919156f400b632 100644 --- a/tools/cabana/streams/pandastream.h +++ b/tools/cabana/streams/pandastream.h @@ -5,7 +5,6 @@ #include #include -#include #include "tools/cabana/streams/livestream.h" #include "selfdrive/boardd/panda.h" diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index ccf3e7ca030935..e94aefec2b2e3d 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -15,7 +15,7 @@ ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) { op_prefix = std::make_unique(); #endif - QObject::connect(&settings, &Settings::changed, [this]() { + QObject::connect(&settings, &Settings::changed, this, [this]() { if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes); }); } @@ -28,8 +28,18 @@ void ReplayStream::mergeSegments() { for (auto &[n, seg] : replay->segments()) { if (seg && seg->isLoaded() && !processed_segments.count(n)) { processed_segments.insert(n); - const auto &events = seg->log->events; - mergeEvents(events.cbegin(), events.cend()); + + std::vector new_events; + new_events.reserve(seg->log->events.size()); + for (auto it = seg->log->events.cbegin(); it != seg->log->events.cend(); ++it) { + if ((*it)->which == cereal::Event::Which::CAN) { + const uint64_t ts = (*it)->mono_time; + for (const auto &c : (*it)->event.getCan()) { + new_events.push_back(newEvent(ts, c)); + } + } + } + mergeEvents(new_events); } } } @@ -52,7 +62,6 @@ void ReplayStream::start() { bool ReplayStream::eventFilter(const Event *event) { static double prev_update_ts = 0; - // delay posting CAN message if UI thread is busy if (event->which == cereal::Event::Which::CAN) { double current_sec = event->mono_time / 1e9 - routeStartTime(); for (const auto &c : event->event.getCan()) { @@ -64,9 +73,8 @@ bool ReplayStream::eventFilter(const Event *event) { double ts = millis_since_boot(); if ((ts - prev_update_ts) > (1000.0 / settings.fps)) { - if (postEvents()) { - prev_update_ts = ts; - } + emit privateUpdateLastMsgsSignal(); + prev_update_ts = ts; } return true; } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index 7f8f8a4d3a5e03..95fb632628467c 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -8,6 +8,7 @@ #include "common/prefix.h" #include "tools/cabana/streams/abstractstream.h" +#include "tools/replay/replay.h" class ReplayStream : public AbstractStream { Q_OBJECT @@ -18,13 +19,14 @@ class ReplayStream : public AbstractStream { bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); bool eventFilter(const Event *event); void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); } + bool liveStreaming() const override { return false; } inline QString routeName() const override { return replay->route()->name(); } inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } double totalSeconds() const override { return replay->totalSeconds(); } inline QDateTime beginDateTime() const { return replay->route()->datetime(); } inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; } inline double currentSec() const override { return replay->currentSeconds(); } - inline const Route *route() const override { return replay->route(); } + inline const Route *route() const { return replay->route(); } inline void setSpeed(float speed) override { replay->setSpeed(speed); } inline float getSpeed() const { return replay->getSpeed(); } inline Replay *getReplay() const { return replay.get(); } diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc index 3df8e31f3bf9d5..0f13b9901b3ad9 100644 --- a/tools/cabana/streams/socketcanstream.cc +++ b/tools/cabana/streams/socketcanstream.cc @@ -1,8 +1,11 @@ #include "tools/cabana/streams/socketcanstream.h" -#include +#include +#include +#include #include #include +#include SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) { if (!available()) { @@ -49,7 +52,6 @@ void SocketCanStream::streamThread() { auto evt = msg.initEvent(); auto canData = evt.initCan(frames.size()); - for (uint i = 0; i < frames.size(); i++) { if (!frames[i].isValid()) continue; @@ -60,8 +62,7 @@ void SocketCanStream::streamThread() { canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size())); } - auto bytes = msg.toBytes(); - handleEvent((const char*)bytes.begin(), bytes.size()); + handleEvent(capnp::messageToFlatArray(msg)); } } diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h index 6f2d7aa3539d29..e0fb826acb4c89 100644 --- a/tools/cabana/streams/socketcanstream.h +++ b/tools/cabana/streams/socketcanstream.h @@ -5,10 +5,7 @@ #include #include #include - #include -#include -#include #include "tools/cabana/streams/livestream.h" @@ -21,7 +18,6 @@ class SocketCanStream : public LiveStream { public: SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); static AbstractOpenStreamWidget *widget(AbstractStream **stream); - static bool available(); inline QString routeName() const override { diff --git a/tools/cabana/streamselector.cc b/tools/cabana/streamselector.cc index 719ba72920bea5..07755c0fe00c92 100644 --- a/tools/cabana/streamselector.cc +++ b/tools/cabana/streamselector.cc @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index 51d86f596491cc..5155ad91d275fe 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -192,7 +192,7 @@ void FindSignalDlg::search() { search_btn->setEnabled(false); stats_label->setVisible(false); search_btn->setText("Finding ...."); - QTimer::singleShot(0, [=]() { model->search(cmp); }); + QTimer::singleShot(0, this, [=]() { model->search(cmp); }); } void FindSignalDlg::setInitialSignals() { @@ -222,15 +222,15 @@ void FindSignalDlg::setInitialSignals() { } model->initial_signals.clear(); - for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { - if (buses.isEmpty() || buses.contains(it.key().source) && (addresses.isEmpty() || addresses.contains(it.key().address))) { - const auto &events = can->events(it.key()); + 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 = it.value().dat.size() * 8; + 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 = it.key(), .mono_time = first_time, .sig = sig}; + FindSignalModel::SearchSignal s{.id = id, .mono_time = first_time, .sig = sig}; s.sig.start_bit = start; s.sig.size = size; updateMsbLsb(s.sig); diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index 38190da3014559..f984230c47074f 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include #include @@ -59,23 +59,18 @@ MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) hex_text_table[i].setText(QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper()); hex_text_table[i].prepare({}, fixed_font); } + h_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + v_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 1; } -int MessageBytesDelegate::widthForBytes(int n) const { - int h_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; - return n * byte_size.width() + h_margin * 2; +QSize MessageBytesDelegate::sizeForBytes(int n) const { + int rows = multiple_lines ? std::max(1, n / 8) : 1; + return {(n / rows) * byte_size.width() + h_margin * 2, rows * byte_size.height() + v_margin * 2}; } QSize MessageBytesDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - int v_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 1; auto data = index.data(BytesRole); - if (!data.isValid()) { - return {1, byte_size.height() + 2 * v_margin}; - } - int n = data.toByteArray().size(); - assert(n >= 0 && n <= 64); - return !multiple_lines ? QSize{widthForBytes(n), byte_size.height() + 2 * v_margin} - : QSize{widthForBytes(8), byte_size.height() * std::max(1, n / 8) + 2 * v_margin}; + return sizeForBytes(data.isValid() ? static_cast *>(data.value())->size() : 0); } void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -84,20 +79,17 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem & return QStyledItemDelegate::paint(painter, option, index); } - auto byte_list = data.toByteArray(); - auto colors = index.data(ColorsRole).value>(); - - int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); - int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); + QFont old_font = painter->font(); + QPen old_pen = painter->pen(); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.brush(QPalette::Normal, QPalette::Highlight)); } - const QPoint pt{option.rect.left() + h_margin, option.rect.top() + v_margin}; - QFont old_font = painter->font(); - QPen old_pen = painter->pen(); painter->setFont(fixed_font); - for (int i = 0; i < byte_list.size(); ++i) { + + const auto &bytes = *static_cast*>(data.value()); + const auto &colors = *static_cast*>(index.data(ColorsRole).value()); + for (int i = 0; i < bytes.size(); ++i) { int row = !multiple_lines ? 0 : i / 8; int column = !multiple_lines ? i : i % 8; QRect r = QRect({pt.x() + column * byte_size.width(), pt.y() + row * byte_size.height()}, byte_size); @@ -110,7 +102,7 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem & } else if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(QPalette::HighlightedText)); } - utils::drawStaticText(painter, r, hex_text_table[(uint8_t)(byte_list[i])]); + utils::drawStaticText(painter, r, hex_text_table[bytes[i]]); } painter->setFont(old_font); painter->setPen(old_pen); @@ -251,15 +243,6 @@ QString formatSeconds(double sec, bool include_milliseconds, bool absolute_time) } // namespace utils -QString toHex(uint8_t byte) { - static std::array hex = []() { - std::array ret; - for (int i = 0; i < 256; ++i) ret[i] = QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper(); - return ret; - }(); - return hex[byte]; -} - int num_decimals(double num) { const QString string = QString::number(num); auto dot_pos = string.indexOf('.'); diff --git a/tools/cabana/util.h b/tools/cabana/util.h index da476ab31a0ad7..158321f7842964 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -18,7 +17,6 @@ #include #include #include -#include #include "tools/cabana/dbc/dbc.h" #include "tools/cabana/settings.h" @@ -75,18 +73,16 @@ class MessageBytesDelegate : public QStyledItemDelegate { QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool multipleLines() const { return multiple_lines; } void setMultipleLines(bool v) { multiple_lines = v; } - int widthForBytes(int n) const; + QSize sizeForBytes(int n) const; private: std::array hex_text_table; QFont fixed_font; QSize byte_size = {}; bool multiple_lines = false; + int h_margin, v_margin; }; -inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); } -QString toHex(uint8_t byte); - class NameValidator : public QRegExpValidator { Q_OBJECT public: @@ -108,6 +104,10 @@ inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text) auto size = (r.size() - text.size()) / 2; p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text); } +inline QString toHex(const std::vector &dat, char separator = '\0') { + return QByteArray::fromRawData((const char *)dat.data(), dat.size()).toHex(separator).toUpper(); +} + } class ToolButton : public QToolButton { diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 7afcc7b9385ee8..bbb1ef28daf796 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -13,6 +13,9 @@ #include #include +#include "tools/cabana/streams/replaystream.h" +#include "tools/cabana/util.h" + const int MIN_VIDEO_HEIGHT = 100; const int THUMBNAIL_MARGIN = 3; @@ -35,7 +38,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState); - QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); + QObject::connect(can, &AbstractStream::msgsReceived, this, &VideoWidget::updateState); updatePlayBtnState(); setWhatsThis(tr(R"( @@ -179,6 +182,8 @@ void VideoWidget::vipcAvailableStreamsUpdated(std::set streams void VideoWidget::loopPlaybackClicked() { auto replay = qobject_cast(can)->getReplay(); + if (!replay) return; + if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) { replay->removeFlag(REPLAY_FLAG_NO_LOOP); loop_btn->setIcon("repeat"); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index c2fdb41df81705..69f1edd2bc6072 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -5,12 +5,14 @@ #include #include +#include #include #include #include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "tools/cabana/streams/replaystream.h" +#include "tools/cabana/util.h" +#include "tools/replay/logreader.h" struct AlertInfo { cereal::ControlsState::AlertStatus status;