cabana: support suppress highlighted bits (#30336)

* support suppress highlighted bits

d

* faster filtering and sorting

* improve livestream

* specify the context in the connections

* remove inline
This commit is contained in:
Dean Lee 2023-10-31 00:47:23 +08:00 committed by GitHub
parent 61288dfe06
commit 01610128bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 465 additions and 517 deletions

View File

@ -2,6 +2,7 @@
#include <algorithm>
#include <QDebug>
#include <QFontDatabase>
#include <QHeaderView>
#include <QMouseEvent>
@ -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<int>(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;

View File

@ -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);
}

View File

@ -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);

View File

@ -4,10 +4,8 @@
#include <QFileInfo>
#include <QRegularExpression>
#include <QTextStream>
#include <numeric>
#include <sstream>
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);
}

View File

@ -1,18 +1,15 @@
#pragma once
#include <map>
#include <QObject>
#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();

View File

@ -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<DBCFile>(dbc_file_name, this);
auto file = (it != dbc_files.end()) ? it->second : std::make_shared<DBCFile>(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<DBCFile>(name, content, this);
auto file = std::make_shared<DBCFile>(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;

View File

@ -1,5 +1,6 @@
#pragma once
#include <QObject>
#include <memory>
#include <map>
#include <set>
@ -66,15 +67,8 @@ private:
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(", ");
}

View File

@ -147,8 +147,8 @@ void DetailWidget::refresh() {
warning_widget->setVisible(!warnings.isEmpty());
}
void DetailWidget::updateState(const QHash<MessageId, CanData> *msgs) {
if ((msgs && !msgs->contains(msg_id)))
void DetailWidget::updateState(const std::set<MessageId> *msgs) {
if ((msgs && !msgs->count(msg_id)))
return;
if (tab_widget->currentIndex() == 0)

View File

@ -4,6 +4,7 @@
#include <QSplitter>
#include <QTabWidget>
#include <QTextEdit>
#include <set>
#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 @@ private:
void showTabBarContextMenu(const QPoint &pt);
void editMsg();
void removeMsg();
void updateState(const QHash<MessageId, CanData> * msgs = nullptr);
void updateState(const std::set<MessageId> *msgs = nullptr);
MessageId msg_id;
QLabel *warning_icon, *warning_label;

View File

@ -7,7 +7,6 @@
#include <QVBoxLayout>
#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 <class InputIt>
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
std::deque<HistoryLogModel::Message> msgs;
QVector<double> values(sigs.size());
std::vector<double> 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::Message> 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::Message> 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<uint8_t> 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::Message> 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::Message> 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::Message> 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);

View File

@ -46,9 +46,9 @@ public slots:
public:
struct Message {
uint64_t mono_time = 0;
QVector<double> sig_values;
QByteArray data;
QVector<QColor> colors;
std::vector<double> sig_values;
std::vector<uint8_t> data;
std::vector<QColor> colors;
};
template <class InputIt>

View File

@ -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);

View File

@ -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 &current, 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"(
<b>Message View</b><br/>
@ -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<QColor> 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 += "<br /><span style=\"color:gray;\">" + msg->comment + "</span>";
return tooltip;
}
@ -232,31 +210,31 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
}
void MessageListModel::setFilterStrings(const QMap<int, QString> &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<MessageId> &new_msgs) {
auto do_sort = [order = sort_order](std::vector<MessageId> &m, auto proj) {
std::sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) {
void MessageListModel::sortItems(std::vector<MessageListModel::Item> &items) {
auto do_sort = [order = sort_order](std::vector<MessageListModel::Item> &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<int, QString> &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<MessageId> new_msgs;
new_msgs.reserve(can->last_msgs.size() + dbc_address.size());
auto address = dbc_address;
for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) {
if (filter_str.isEmpty() || matchMessage(it.key(), it.value(), filter_str)) {
new_msgs.push_back(it.key());
}
address.remove(it.key().address);
void MessageListModel::filterAndSort(bool force_reset) {
// merge CAN and DBC messages
std::vector<MessageId> 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<Item> 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<MessageId, CanData> *new_msgs, bool has_new_ids) {
if (has_new_ids || filter_str.contains(Column::FREQ) || filter_str.contains(Column::COUNT) || filter_str.contains(Column::DATA)) {
void MessageListModel::msgsReceived(const std::set<MessageId> *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<MessageId, CanData> *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<int>(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));
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <algorithm>
#include <set>
#include <utility>
#include <vector>
@ -9,7 +10,6 @@
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QSet>
#include <QToolBar>
#include <QTreeView>
@ -34,23 +34,28 @@ public:
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<int, QString> &filters);
void msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids);
void filterAndSort();
void suppress();
void clearSuppress();
void msgsReceived(const std::set<MessageId> *new_msgs, bool has_new_ids);
void filterAndSort(bool force_reset = false);
void dbcModified();
std::vector<MessageId> msgs;
QSet<std::pair<MessageId, int>> 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<Item> items_;
private:
void sortMessages(std::vector<MessageId> &new_msgs);
bool matchMessage(const MessageId &id, const CanData &data, const QMap<int, QString> &filters);
void sortItems(std::vector<MessageListModel::Item> &items);
bool match(const MessageListModel::Item &id);
QMap<int, QString> filter_str;
QSet<uint32_t> dbc_address;
QMap<int, QString> filters_;
std::set<MessageId> dbc_messages_;
int sort_column = 0;
Qt::SortOrder sort_order = Qt::AscendingOrder;
};
@ -91,7 +96,7 @@ public:
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();

View File

@ -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);

View File

@ -33,7 +33,7 @@ public:
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;

View File

@ -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<MessageId, CanData> *msgs) {
void SignalView::updateState(const std::set<MessageId> *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));

View File

@ -1,6 +1,7 @@
#pragma once
#include <memory>
#include <set>
#include <QAbstractItemModel>
#include <QLabel>
@ -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 @@ private:
void setSparklineRange(int value);
void handleSignalAdded(MessageId id, const cabana::Signal *sig);
void handleSignalUpdated(const cabana::Signal *sig);
void updateState(const QHash<MessageId, CanData> *msgs = nullptr);
void updateState(const std::set<MessageId> *msgs = nullptr);
struct TreeView : public QTreeView {
TreeView(QWidget *parent) : QTreeView(parent) {}

View File

@ -1,8 +1,10 @@
#include "tools/cabana/streams/abstractstream.h"
#include <algorithm>
#include <utility>
#include <QTimer>
#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<QHash<MessageId, CanData>>();
event_buffer = std::make_unique<MonotonicBuffer>(EVENT_NEXT_BUFFER_SIZE);
event_buffer_ = std::make_unique<MonotonicBuffer>(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::updateMessages(QHash<MessageId, CanData> *messages) {
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::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<MessageId> 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<uint8_t> *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<MessageId, CanData>);
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<const CanEvent *> &AbstractStream::events(const MessageId &id) const {
@ -102,76 +119,62 @@ const std::vector<const CanEvent *> &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<MessageId, CanData>);
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<uint8_t> *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<Event *>::const_iterator first, std::vector<Event *>::const_iterator last) {
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<const CanEvent *> &events) {
static MessageEventsMap msg_events;
static std::vector<const CanEvent *> new_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<Event *>::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<uint8_t> *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<uint8_t> &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));
}
}

View File

@ -1,34 +1,37 @@
#pragma once
#include <array>
#include <atomic>
#include <memory>
#include <mutex>
#include <set>
#include <unordered_map>
#include <vector>
#include <QColor>
#include <QDateTime>
#include <QHash>
#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<uint8_t> *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<uint8_t> &mask, double in_freq = 0);
double ts = 0.;
uint32_t count = 0;
double freq = 0;
QByteArray dat;
QVector<QColor> colors;
std::vector<double> last_change_t;
std::vector<std::array<uint32_t, 8>> bit_change_counts;
std::vector<int> last_delta;
std::vector<int> same_delta_counter;
std::vector<uint8_t> dat;
std::vector<QColor> colors;
struct ByteLastChange {
double ts;
int delta;
int same_delta_counter;
bool suppressed;
std::array<uint32_t, 8> bit_change_counts;
};
std::vector<ByteLastChange> last_changes;
double last_freq_update_ts = 0;
};
@ -60,7 +63,7 @@ public:
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 @@ public:
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<const CanEvent *> &allEvents() const { return all_events_; }
inline const std::unordered_map<MessageId, CanData> &lastMessages() const { return last_msgs; }
inline const MessageEventsMap &eventsMap() const { return events_; }
inline const std::vector<const CanEvent *> &allEvents() const { return all_events_; }
const CanData &lastMessage(const MessageId &id);
const std::vector<const CanEvent *> &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<MessageId, CanData> *new_msgs, bool has_new_ids);
void msgsReceived(const std::set<MessageId> *new_msgs, bool has_new_ids);
void sourcesUpdated(const SourceSet &s);
void privateUpdateLastMsgsSignal();
public:
QHash<MessageId, CanData> last_msgs;
SourceSet sources;
protected:
void mergeEvents(std::vector<Event *>::const_iterator first, std::vector<Event *>::const_iterator last);
bool postEvents();
uint64_t lastEventMonoTime() const { return lastest_event_ts; }
void mergeEvents(const std::vector<const CanEvent *> &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<MessageId, CanData> *);
void updateMasks();
void updateLastMsgsTo(double sec);
uint64_t lastEventMonoTime() const { return lastest_event_ts; }
uint64_t lastest_event_ts = 0;
std::atomic<bool> processing = false;
std::unique_ptr<QHash<MessageId, CanData>> new_msgs;
QHash<MessageId, CanData> all_msgs;
MessageEventsMap events_;
std::vector<const CanEvent *> all_events_;
std::unique_ptr<MonotonicBuffer> event_buffer;
std::mutex mutex;
std::unordered_map<MessageId, std::vector<uint8_t>> masks;
uint64_t lastest_event_ts = 0;
private:
void updateLastMessages();
void updateLastMsgsTo(double sec);
void updateMasks();
MessageEventsMap events_;
std::unordered_map<MessageId, CanData> last_msgs;
std::unique_ptr<MonotonicBuffer> event_buffer_;
// Members accessed in multiple threads. (mutex protected)
std::mutex mutex_;
std::set<MessageId> new_msgs_;
std::unordered_map<MessageId, CanData> messages_;
std::unordered_map<MessageId, std::vector<uint8_t>> masks_;
};
class AbstractOpenStreamWidget : public QWidget {

View File

@ -8,6 +8,7 @@
#include <QRadioButton>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QThread>
// DeviceStream
@ -21,17 +22,14 @@ void DeviceStream::streamThread() {
std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString();
std::unique_ptr<SubSocket> 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<Message> msg(sock->receive(true));
if (!msg) {
QThread::msleep(50);
continue;
}
handleEvent(msg->getData(), msg->getSize());
delete msg;
handleEvent(kj::ArrayPtr<capnp::word>((capnp::word*)msg->getData(), msg->getSize() / sizeof(capnp::word)));
}
}

View File

@ -1,12 +1,17 @@
#include "tools/cabana/streams/livestream.h"
#include <QThread>
#include <algorithm>
#include <fstream>
#include <memory>
#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<capnp::word> 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<std::ofstream> 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<capnp::word> 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<cereal::Event>();
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) {

View File

@ -1,6 +1,5 @@
#pragma once
#include <deque>
#include <memory>
#include <vector>
@ -26,26 +25,16 @@ public:
protected:
virtual void streamThread() = 0;
void handleEvent(const char *data, const size_t size);
void handleEvent(kj::ArrayPtr<capnp::word> 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<Event *> receivedEvents;
std::deque<Msg> receivedMessages;
std::vector<const CanEvent *> received_events_;
int timer_id;
QBasicTimer update_timer;

View File

@ -1,15 +1,13 @@
#include "tools/cabana/streams/pandastream.h"
#include <vector>
#include <QDebug>
#include <QCheckBox>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QThread>
#include <QVBoxLayout>
#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; i<raw_can_data.size(); i++) {
canData[i].setAddress(raw_can_data[i].address);
canData[i].setBusTime(raw_can_data[i].busTime);
@ -98,8 +95,7 @@ void PandaStream::streamThread() {
canData[i].setSrc(raw_can_data[i].src);
}
auto bytes = msg.toBytes();
handleEvent((const char*)bytes.begin(), bytes.size());
handleEvent(capnp::messageToFlatArray(msg));
panda->send_heartbeat(false);
}

View File

@ -5,7 +5,6 @@
#include <QComboBox>
#include <QFormLayout>
#include <QVBoxLayout>
#include "tools/cabana/streams/livestream.h"
#include "selfdrive/boardd/panda.h"

View File

@ -15,7 +15,7 @@ ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
op_prefix = std::make_unique<OpenpilotPrefix>();
#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<const CanEvent *> 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;
}

View File

@ -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 @@ public:
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(); }

View File

@ -1,8 +1,11 @@
#include "tools/cabana/streams/socketcanstream.h"
#include <QLabel>
#include <QDebug>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QPushButton>
#include <QThread>
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));
}
}

View File

@ -5,10 +5,7 @@
#include <QtSerialBus/QCanBus>
#include <QtSerialBus/QCanBusDevice>
#include <QtSerialBus/QCanBusDeviceInfo>
#include <QComboBox>
#include <QFormLayout>
#include <QVBoxLayout>
#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 {

View File

@ -2,7 +2,6 @@
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFormLayout>
#include <QLabel>
#include <QPushButton>

View File

@ -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);

View File

@ -9,7 +9,7 @@
#include <unistd.h>
#include <QColor>
#include <QDebug>
#include <QDateTime>
#include <QFontDatabase>
#include <QLocale>
#include <QPainter>
@ -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<std::vector<uint8_t> *>(data.value<void *>())->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<QVector<QColor>>();
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<std::vector<uint8_t>*>(data.value<void*>());
const auto &colors = *static_cast<std::vector<QColor>*>(index.data(ColorsRole).value<void*>());
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<QString, 256> hex = []() {
std::array<QString, 256> 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('.');

View File

@ -8,7 +8,6 @@
#include <QApplication>
#include <QByteArray>
#include <QDateTime>
#include <QDoubleValidator>
#include <QFont>
#include <QPainter>
@ -18,7 +17,6 @@
#include <QStringBuilder>
#include <QStyledItemDelegate>
#include <QToolButton>
#include <QVector>
#include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/settings.h"
@ -75,18 +73,16 @@ public:
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<QStaticText, 256> 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<uint8_t> &dat, char separator = '\0') {
return QByteArray::fromRawData((const char *)dat.data(), dat.size()).toHex(separator).toUpper();
}
}
class ToolButton : public QToolButton {

View File

@ -13,6 +13,9 @@
#include <QVBoxLayout>
#include <QtConcurrent>
#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<VisionStreamType> streams
void VideoWidget::loopPlaybackClicked() {
auto replay = qobject_cast<ReplayStream *>(can)->getReplay();
if (!replay) return;
if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) {
replay->removeFlag(REPLAY_FLAG_NO_LOOP);
loop_btn->setIcon("repeat");

View File

@ -5,12 +5,14 @@
#include <set>
#include <QHBoxLayout>
#include <QFrame>
#include <QSlider>
#include <QTabBar>
#include <QToolButton>
#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;