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
old-commit-hash: 01610128bb
testing-closet
Dean Lee 2 years ago committed by GitHub
parent 3a637fe455
commit be9d291dad
  1. 16
      tools/cabana/binaryview.cc
  2. 4
      tools/cabana/chart/chartswidget.cc
  3. 6
      tools/cabana/chart/signalselector.cc
  4. 6
      tools/cabana/dbc/dbcfile.cc
  5. 9
      tools/cabana/dbc/dbcfile.h
  6. 11
      tools/cabana/dbc/dbcmanager.cc
  7. 10
      tools/cabana/dbc/dbcmanager.h
  8. 4
      tools/cabana/detailwidget.cc
  9. 4
      tools/cabana/detailwidget.h
  10. 22
      tools/cabana/historylog.cc
  11. 6
      tools/cabana/historylog.h
  12. 27
      tools/cabana/mainwin.cc
  13. 262
      tools/cabana/messageswidget.cc
  14. 31
      tools/cabana/messageswidget.h
  15. 2
      tools/cabana/settings.cc
  16. 2
      tools/cabana/settings.h
  17. 18
      tools/cabana/signalview.cc
  18. 5
      tools/cabana/signalview.h
  19. 268
      tools/cabana/streams/abstractstream.cc
  20. 80
      tools/cabana/streams/abstractstream.h
  21. 8
      tools/cabana/streams/devicestream.cc
  22. 33
      tools/cabana/streams/livestream.cc
  23. 15
      tools/cabana/streams/livestream.h
  24. 10
      tools/cabana/streams/pandastream.cc
  25. 1
      tools/cabana/streams/pandastream.h
  26. 22
      tools/cabana/streams/replaystream.cc
  27. 4
      tools/cabana/streams/replaystream.h
  28. 9
      tools/cabana/streams/socketcanstream.cc
  29. 4
      tools/cabana/streams/socketcanstream.h
  30. 1
      tools/cabana/streamselector.cc
  31. 12
      tools/cabana/tools/findsignal.cc
  32. 45
      tools/cabana/util.cc
  33. 12
      tools/cabana/util.h
  34. 7
      tools/cabana/videowidget.cc
  35. 4
      tools/cabana/videowidget.h

@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <QDebug>
#include <QFontDatabase> #include <QFontDatabase>
#include <QHeaderView> #include <QHeaderView>
#include <QMouseEvent> #include <QMouseEvent>
@ -273,7 +274,7 @@ void BinaryViewModel::refresh() {
row_count = can->lastMessage(msg_id).dat.size(); row_count = can->lastMessage(msg_id).dat.size();
items.resize(row_count * column_count); 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) { for (int i = 0; i < valid_rows * column_count; ++i) {
items[i].valid = true; items[i].valid = true;
} }
@ -311,7 +312,7 @@ void BinaryViewModel::updateState() {
int val = ((binary[i] >> (7 - j)) & 1) != 0 ? 1 : 0; int val = ((binary[i] >> (7 - j)) & 1) != 0 ? 1 : 0;
// Bit update frequency based highlighting // Bit update frequency based highlighting
double offset = !item.sigs.empty() ? 50 : 0; 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 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); 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; 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 { QVariant BinaryViewModel::data(const QModelIndex &index, int role) const {
if (role == Qt::ToolTipRole) { auto item = (const BinaryViewModel::Item *)index.internalPointer();
auto item = (const BinaryViewModel::Item *)index.internalPointer(); return role == Qt::ToolTipRole && item && !item->sigs.empty() ? signalToolTip(item->sigs.back()) : QVariant();
if (item && !item->sigs.empty()) {
return signalToolTip(item->sigs.back());
}
}
return {};
} }
// BinaryItemDelegate // BinaryItemDelegate
@ -388,7 +384,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
drawSignalCell(painter, option, index, s); 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); painter->fillRect(option.rect, item->bg_color);
} }
auto color_role = item->sigs.contains(bin_view->hovered_sig) ? QPalette::BrightText : QPalette::Text; auto color_role = item->sigs.contains(bin_view->hovered_sig) ? QPalette::BrightText : QPalette::Text;

@ -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(&auto_scroll_timer, &QTimer::timeout, this, &ChartsWidget::doAutoScroll);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged); 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(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange);
QObject::connect(new_plot_btn, &QToolButton::clicked, this, &ChartsWidget::newChart); QObject::connect(new_plot_btn, &QToolButton::clicked, this, &ChartsWidget::newChart);
QObject::connect(remove_all_btn, &QToolButton::clicked, this, &ChartsWidget::removeAll); 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); charts_layout->addWidget(current_charts[i], i / n, i % n);
if (current_charts[i]->sigs.empty()) { if (current_charts[i]->sigs.empty()) {
// the chart will be resized after add signal. delay setVisible to reduce flicker. // 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 { } else {
current_charts[i]->setVisible(true); current_charts[i]->setVisible(true);
} }

@ -44,9 +44,9 @@ SignalSelector::SignalSelector(QString title, QWidget *parent) : QDialog(parent)
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox, 3, 2); main_layout->addWidget(buttonBox, 3, 2);
for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { for (const auto &[id, _] : can->lastMessages()) {
if (auto m = dbc()->msg(it.key())) { if (auto m = dbc()->msg(id)) {
msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key().toString()), QVariant::fromValue(it.key())); msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(id.toString()), QVariant::fromValue(id));
} }
} }
msgs_combo->model()->sort(0); msgs_combo->model()->sort(0);

@ -4,10 +4,8 @@
#include <QFileInfo> #include <QFileInfo>
#include <QRegularExpression> #include <QRegularExpression>
#include <QTextStream> #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); QFile file(dbc_file_name);
if (file.open(QIODevice::ReadOnly)) { if (file.open(QIODevice::ReadOnly)) {
name_ = QFileInfo(dbc_file_name).baseName(); 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 // Open from clipboard
parse(content); parse(content);
} }

@ -1,18 +1,15 @@
#pragma once #pragma once
#include <map> #include <map>
#include <QObject>
#include "tools/cabana/dbc/dbc.h" #include "tools/cabana/dbc/dbc.h"
const QString AUTO_SAVE_EXTENSION = ".tmp"; const QString AUTO_SAVE_EXTENSION = ".tmp";
class DBCFile : public QObject { class DBCFile {
Q_OBJECT
public: public:
DBCFile(const QString &dbc_file_name, QObject *parent=nullptr); DBCFile(const QString &dbc_file_name);
DBCFile(const QString &name, const QString &content, QObject *parent=nullptr); DBCFile(const QString &name, const QString &content);
~DBCFile() {} ~DBCFile() {}
bool save(); bool save();

@ -7,7 +7,7 @@ bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QS
try { try {
auto it = std::find_if(dbc_files.begin(), dbc_files.end(), auto it = std::find_if(dbc_files.begin(), dbc_files.end(),
[&](auto &f) { return f.second && f.second->filename == dbc_file_name; }); [&](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) { for (auto s : sources) {
dbc_files[s] = file; 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) { bool DBCManager::open(const SourceSet &sources, const QString &name, const QString &content, QString *error) {
try { try {
auto file = std::make_shared<DBCFile>(name, content, this); auto file = std::make_shared<DBCFile>(name, content);
for (auto s : sources) { for (auto s : sources) {
dbc_files[s] = file; dbc_files[s] = file;
} }
@ -189,6 +189,13 @@ const SourceSet DBCManager::sources(const DBCFile *dbc_file) const {
return sources; 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() { DBCManager *dbc() {
static DBCManager dbc_manager(nullptr); static DBCManager dbc_manager(nullptr);
return &dbc_manager; return &dbc_manager;

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <QObject>
#include <memory> #include <memory>
#include <map> #include <map>
#include <set> #include <set>
@ -66,15 +67,8 @@ private:
DBCManager *dbc(); DBCManager *dbc();
QString toString(const SourceSet &ss);
inline QString msgName(const MessageId &id) { inline QString msgName(const MessageId &id) {
auto msg = dbc()->msg(id); auto msg = dbc()->msg(id);
return msg ? msg->name : UNTITLED; 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(", ");
}

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

@ -4,6 +4,7 @@
#include <QSplitter> #include <QSplitter>
#include <QTabWidget> #include <QTabWidget>
#include <QTextEdit> #include <QTextEdit>
#include <set>
#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/controls.h"
#include "tools/cabana/binaryview.h" #include "tools/cabana/binaryview.h"
@ -11,7 +12,6 @@
#include "tools/cabana/historylog.h" #include "tools/cabana/historylog.h"
#include "tools/cabana/signalview.h" #include "tools/cabana/signalview.h"
class MainWindow;
class EditMessageDialog : public QDialog { class EditMessageDialog : public QDialog {
public: public:
EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent); EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent);
@ -39,7 +39,7 @@ private:
void showTabBarContextMenu(const QPoint &pt); void showTabBarContextMenu(const QPoint &pt);
void editMsg(); void editMsg();
void removeMsg(); void removeMsg();
void updateState(const QHash<MessageId, CanData> * msgs = nullptr); void updateState(const std::set<MessageId> *msgs = nullptr);
MessageId msg_id; MessageId msg_id;
QLabel *warning_icon, *warning_label; QLabel *warning_icon, *warning_label;

@ -7,7 +7,6 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include "tools/cabana/commands.h" #include "tools/cabana/commands.h"
// HistoryLogModel
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
const bool show_signals = display_signals_mode && sigs.size() > 0; 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); return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2);
} }
int i = index.column() - 1; 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) { } else if (role == ColorsRole) {
return QVariant::fromValue(m.colors); return QVariant::fromValue((void *)(&m.colors));
} else if (role == BytesRole) { } else if (role == BytesRole) {
return m.data; return QVariant::fromValue((void *)(&m.data));
} else if (role == Qt::TextAlignmentRole) { } else if (role == Qt::TextAlignmentRole) {
return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter);
} }
@ -123,7 +122,7 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) {
template <class InputIt> template <class InputIt>
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) { std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
std::deque<HistoryLogModel::Message> msgs; 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) { for (; first != last && (*first)->mono_time > min_time; ++first) {
const CanEvent *e = *first; const CanEvent *e = *first;
for (int i = 0; i < sigs.size(); ++i) { 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)) { if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) {
auto &m = msgs.emplace_back(); auto &m = msgs.emplace_back();
m.mono_time = e->mono_time; 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; m.sig_values = values;
if (msgs.size() >= batch_size && min_time == 0) { if (msgs.size() >= batch_size && min_time == 0) {
return msgs; 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 &events = can->events(msg_id);
const auto freq = can->lastMessage(msg_id).freq; const auto freq = can->lastMessage(msg_id).freq;
const bool update_colors = !display_signals_mode || sigs.empty(); const bool update_colors = !display_signals_mode || sigs.empty();
const std::vector<uint8_t> no_mask;
const auto speed = can->getSpeed(); const auto speed = can->getSpeed();
if (dynamic_mode) { if (dynamic_mode) {
auto first = std::upper_bound(events.rbegin(), events.rend(), from_time, [](uint64_t ts, auto e) { 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); auto msgs = fetchData(first, events.rend(), min_time);
if (update_colors && (min_time > 0 || messages.empty())) { if (update_colors && (min_time > 0 || messages.empty())) {
for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { 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; 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); auto msgs = fetchData(first, events.cend(), 0);
if (update_colors) { if (update_colors) {
for (auto it = msgs.begin(); it != msgs.end(); ++it) { 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; it->colors = hex_colors.colors;
} }
} }
@ -177,7 +176,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t from_ti
// HeaderView // HeaderView
QSize HeaderView::sectionSizeFromContents(int logicalIndex) const { 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) { if (logicalIndex == 0) {
return time_col_size; return time_col_size;
} else { } else {
@ -237,10 +236,11 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) {
main_layout->addWidget(logs = new QTableView(this)); main_layout->addWidget(logs = new QTableView(this));
logs->setModel(model = new HistoryLogModel(this)); logs->setModel(model = new HistoryLogModel(this));
delegate = new MessageBytesDelegate(this); delegate = new MessageBytesDelegate(this);
logs->setItemDelegateForColumn(1, new MessageBytesDelegate(this));
logs->setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); logs->setHorizontalHeader(new HeaderView(Qt::Horizontal, this));
logs->horizontalHeader()->setDefaultAlignment(Qt::AlignRight | (Qt::Alignment)Qt::TextWordWrap); logs->horizontalHeader()->setDefaultAlignment(Qt::AlignRight | (Qt::Alignment)Qt::TextWordWrap);
logs->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); logs->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
logs->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
logs->verticalHeader()->setDefaultSectionSize(delegate->sizeForBytes(8).height());
logs->verticalHeader()->setVisible(false); logs->verticalHeader()->setVisible(false);
logs->setFrameShape(QFrame::NoFrame); logs->setFrameShape(QFrame::NoFrame);

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

@ -21,6 +21,7 @@
#include "tools/cabana/commands.h" #include "tools/cabana/commands.h"
#include "tools/cabana/streamselector.h" #include "tools/cabana/streamselector.h"
#include "tools/cabana/tools/findsignal.h" #include "tools/cabana/tools/findsignal.h"
#include "tools/replay/replay.h"
MainWindow::MainWindow() : QMainWindow() { MainWindow::MainWindow() : QMainWindow() {
createDockWindows(); createDockWindows();
@ -84,8 +85,8 @@ void MainWindow::createActions() {
close_stream_act->setEnabled(false); close_stream_act->setEnabled(false);
file_menu->addSeparator(); file_menu->addSeparator();
file_menu->addAction(tr("New DBC File"), [this]() { newFile(); })->setShortcuts(QKeySequence::New); file_menu->addAction(tr("New DBC File"), [this]() { newFile(); }, QKeySequence::New);
file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); })->setShortcuts(QKeySequence::Open); file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); }, QKeySequence::Open);
manage_dbcs_menu = file_menu->addMenu(tr("Manage &DBC Files")); 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->addAction(tr("Load DBC From Clipboard"), [=]() { loadFromClipboard(); });
file_menu->addSeparator(); file_menu->addSeparator();
save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save); save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save, QKeySequence::Save);
save_dbc->setShortcuts(QKeySequence::Save); save_dbc_as = file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs, QKeySequence::SaveAs);
save_dbc_as = file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs);
save_dbc_as->setShortcuts(QKeySequence::SaveAs);
copy_dbc_to_clipboard = file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveToClipboard); copy_dbc_to_clipboard = file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveToClipboard);
file_menu->addSeparator(); 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->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 // Edit Menu
QMenu *edit_menu = menuBar()->addMenu(tr("&Edit")); QMenu *edit_menu = menuBar()->addMenu(tr("&Edit"));
@ -157,7 +154,7 @@ void MainWindow::createActions() {
// Help Menu // Help Menu
QMenu *help_menu = menuBar()->addMenu(tr("&Help")); 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); help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
} }
@ -374,7 +371,7 @@ void MainWindow::eventsMerged() {
auto dbc_name = fingerprint_to_dbc[car_fingerprint]; auto dbc_name = fingerprint_to_dbc[car_fingerprint];
if (dbc_name != QJsonValue::Undefined) { if (dbc_name != QJsonValue::Undefined) {
// Prevent dialog that load autosaved file from blocking replay->start(). // 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() { void MainWindow::updateLoadSaveMenus() {
int cnt = dbc()->nonEmptyDBCCount(); int cnt = dbc()->nonEmptyDBCCount();
if (cnt > 1) { save_dbc->setText(cnt > 1 ? tr("Save %1 DBCs...").arg(cnt) : tr("Save DBC..."));
save_dbc->setText(tr("Save %1 DBCs...").arg(dbc()->dbcCount()));
} else {
save_dbc->setText(tr("Save DBC..."));
}
save_dbc->setEnabled(cnt > 0); save_dbc->setEnabled(cnt > 0);
save_dbc_as->setEnabled(cnt == 1); save_dbc_as->setEnabled(cnt == 1);

@ -11,11 +11,6 @@
#include "tools/cabana/commands.h" #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) { MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setContentsMargins(0, 0, 0, 0);
@ -23,12 +18,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget
// toolbar // toolbar
main_layout->addWidget(createToolBar()); main_layout->addWidget(createToolBar());
// message table // message table
view = new MessageView(this); main_layout->addWidget(view = new MessageView(this));
model = new MessageListModel(this); view->setItemDelegate(delegate = new MessageBytesDelegate(view, settings.multiple_lines_hex));
header = new MessageViewHeader(this); view->setModel(model = new MessageListModel(this));
view->setItemDelegate(delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes)); view->setHeader(header = new MessageViewHeader(this));
view->setHeader(header);
view->setModel(model);
view->setSortingEnabled(true); view->setSortingEnabled(true);
view->sortByColumn(MessageListModel::Column::NAME, Qt::AscendingOrder); view->sortByColumn(MessageListModel::Column::NAME, Qt::AscendingOrder);
view->setAllColumnsShowFocus(true); view->setAllColumnsShowFocus(true);
@ -36,6 +29,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget
view->setItemsExpandable(false); view->setItemsExpandable(false);
view->setIndentation(0); view->setIndentation(0);
view->setRootIsDecorated(false); view->setRootIsDecorated(false);
view->setUniformRowHeights(!settings.multiple_lines_hex);
// Must be called before setting any header parameters to avoid overriding // Must be called before setting any header parameters to avoid overriding
restoreHeaderState(settings.message_header_state); restoreHeaderState(settings.message_header_state);
@ -44,15 +38,14 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget
header->setStretchLastSection(true); header->setStretchLastSection(true);
header->setContextMenuPolicy(Qt::CustomContextMenu); header->setContextMenuPolicy(Qt::CustomContextMenu);
main_layout->addWidget(view);
// suppress // suppress
QHBoxLayout *suppress_layout = new QHBoxLayout(); QHBoxLayout *suppress_layout = new QHBoxLayout();
suppress_add = new QPushButton("Suppress Highlighted"); suppress_layout->addWidget(suppress_add = new QPushButton("Suppress Highlighted"));
suppress_clear = new QPushButton(); suppress_layout->addWidget(suppress_clear = new QPushButton());
suppress_layout->addWidget(suppress_add); suppress_clear->setToolTip(tr("Clear suppressed"));
suppress_layout->addWidget(suppress_clear); suppress_layout->addStretch(1);
QCheckBox *suppress_defined_signals = new QCheckBox(tr("Suppress Defined Signals"), this); 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_defined_signals->setChecked(settings.suppress_defined_signals);
suppress_layout->addWidget(suppress_defined_signals); suppress_layout->addWidget(suppress_defined_signals);
main_layout->addLayout(suppress_layout); 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::filtersUpdated, model, &MessageListModel::setFilterStrings);
QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent); QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent);
QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions); QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions);
QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, [=](int state) { QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, can, &AbstractStream::suppressDefinedSignals);
settings.suppress_defined_signals = (state == Qt::Checked);
emit settings.changed();
});
QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified);
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, 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(); view->updateBytesSectionSize();
}); });
QObject::connect(view->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) { QObject::connect(view->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
if (current.isValid() && current.row() < model->msgs.size()) { if (current.isValid() && current.row() < model->items_.size()) {
auto &id = model->msgs[current.row()]; const auto &id = model->items_[current.row()].id;
if (!current_msg_id || id != *current_msg_id) { if (!current_msg_id || id != *current_msg_id) {
current_msg_id = id; current_msg_id = id;
emit msgSelectionChanged(*current_msg_id); emit msgSelectionChanged(*current_msg_id);
} }
} }
}); });
QObject::connect(suppress_add, &QPushButton::clicked, [=]() { QObject::connect(suppress_add, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted);
model->suppress(); QObject::connect(suppress_clear, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted);
updateSuppressedButtons(); suppressHighlighted();
});
QObject::connect(suppress_clear, &QPushButton::clicked, [=]() {
model->clearSuppress();
updateSuppressedButtons();
});
updateSuppressedButtons();
setWhatsThis(tr(R"( setWhatsThis(tr(R"(
<b>Message View</b><br/> <b>Message View</b><br/>
@ -126,19 +109,22 @@ void MessagesWidget::dbcModified() {
} }
void MessagesWidget::selectMessage(const MessageId &msg_id) { void MessagesWidget::selectMessage(const MessageId &msg_id) {
auto it = std::find(model->msgs.cbegin(), model->msgs.cend(), msg_id); auto it = std::find_if(model->items_.cbegin(), model->items_.cend(),
if (it != model->msgs.cend()) { [&msg_id](auto &item) { return item.id == msg_id; });
view->setCurrentIndex(model->index(std::distance(model->msgs.cbegin(), it), 0)); if (it != model->items_.cend()) {
view->setCurrentIndex(model->index(std::distance(model->items_.cbegin(), it), 0));
} }
} }
void MessagesWidget::updateSuppressedButtons() { void MessagesWidget::suppressHighlighted() {
if (model->suppressed_bytes.empty()) { if (sender() == suppress_add) {
suppress_clear->setEnabled(false); size_t n = can->suppressHighlighted();
suppress_clear->setText("Clear Suppressed"); suppress_clear->setText(tr("Clear (%1)").arg(n));
} else {
suppress_clear->setEnabled(true); 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(); menu->addSeparator();
auto action = menu->addAction(tr("Mutlti-Line bytes"), this, &MessagesWidget::setMultiLineBytes); auto action = menu->addAction(tr("Mutlti-Line bytes"), this, &MessagesWidget::setMultiLineBytes);
action->setCheckable(true); action->setCheckable(true);
action->setChecked(settings.multiple_lines_bytes); action->setChecked(settings.multiple_lines_hex);
} }
void MessagesWidget::setMultiLineBytes(bool multi) { void MessagesWidget::setMultiLineBytes(bool multi) {
settings.multiple_lines_bytes = multi; settings.multiple_lines_hex = multi;
delegate->setMultipleLines(multi); delegate->setMultipleLines(multi);
view->setUniformRowHeights(!multi);
view->updateBytesSectionSize(); view->updateBytesSectionSize();
view->doItemsLayout(); view->doItemsLayout();
} }
@ -188,7 +175,7 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
} }
QVariant MessageListModel::data(const QModelIndex &index, int role) const { 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) { auto getFreq = [](const CanData &d) {
if (d.freq > 0 && (can->currentSec() - d.ts - 1.0 / settings.fps) < (5.0 / d.freq)) { 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()]; const auto &item = items_[index.row()];
auto &can_data = can->lastMessage(id);
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
switch (index.column()) { switch (index.column()) {
case Column::NAME: return msgName(id); case Column::NAME: return item.name;
case Column::SOURCE: return id.source != INVALID_SOURCE ? QString::number(id.source) : "N/A"; case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : "N/A";
case Column::ADDRESS: return QString::number(id.address, 16); case Column::ADDRESS: return QString::number(item.id.address, 16);
case Column::NODE: return msg_node_from_id(id); case Column::NODE: return item.node;
case Column::FREQ: return id.source != INVALID_SOURCE ? getFreq(can_data) : "N/A"; case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(*item.data) : "N/A";
case Column::COUNT: return id.source != INVALID_SOURCE ? QString::number(can_data.count) : "N/A"; case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(item.data->count) : "N/A";
case Column::DATA: return id.source != INVALID_SOURCE ? toHex(can_data.dat) : "N/A"; case Column::DATA: return item.id.source != INVALID_SOURCE ? "" : "N/A";
} }
} else if (role == ColorsRole) { } else if (role == ColorsRole) {
QVector<QColor> colors = can_data.colors; return QVariant::fromValue((void*)(&item.data->colors));
if (!suppressed_bytes.empty()) { } else if (role == BytesRole && index.column() == Column::DATA && item.id.source != INVALID_SOURCE) {
for (int i = 0; i < colors.size(); i++) { return QVariant::fromValue((void*)(&item.data->dat));
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;
} else if (role == Qt::ToolTipRole && index.column() == Column::NAME) { } else if (role == Qt::ToolTipRole && index.column() == Column::NAME) {
auto msg = dbc()->msg(id); auto msg = dbc()->msg(item.id);
auto tooltip = msg ? msg->name : UNTITLED; auto tooltip = item.name;
if (msg && !msg->comment.isEmpty()) tooltip += "<br /><span style=\"color:gray;\">" + msg->comment + "</span>"; if (msg && !msg->comment.isEmpty()) tooltip += "<br /><span style=\"color:gray;\">" + msg->comment + "</span>";
return tooltip; return tooltip;
} }
@ -232,31 +210,31 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
} }
void MessageListModel::setFilterStrings(const QMap<int, QString> &filters) { void MessageListModel::setFilterStrings(const QMap<int, QString> &filters) {
filter_str = filters; filters_ = filters;
filterAndSort(); filterAndSort();
} }
void MessageListModel::dbcModified() { void MessageListModel::dbcModified() {
dbc_address.clear(); dbc_messages_.clear();
for (const auto &[_, m] : dbc()->getMessages(-1)) { 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) { void MessageListModel::sortItems(std::vector<MessageListModel::Item> &items) {
auto do_sort = [order = sort_order](std::vector<MessageId> &m, auto proj) { auto do_sort = [order = sort_order](std::vector<MessageListModel::Item> &m, auto proj) {
std::sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) { 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); return order == Qt::AscendingOrder ? proj(l) < proj(r) : proj(l) > proj(r);
}); });
}; };
switch (sort_column) { switch (sort_column) {
case Column::NAME: do_sort(new_msgs, [](auto &id) { return std::make_pair(msgName(id), id); }); break; case Column::NAME: do_sort(items, [](auto &item) { return std::tie(item.name, item.id); }); break;
case Column::SOURCE: do_sort(new_msgs, [](auto &id) { return std::tie(id.source, id); }); break; case Column::SOURCE: do_sort(items, [](auto &item) { return std::tie(item.id.source, item.id); }); break;
case Column::ADDRESS: do_sort(new_msgs, [](auto &id) { return std::tie(id.address, id);}); break; case Column::ADDRESS: do_sort(items, [](auto &item) { return std::tie(item.id.address, item.id);}); break;
case Column::NODE: do_sort(new_msgs, [](auto &id) { return std::make_pair(msg_node_from_id(id), id);}); break; case Column::NODE: do_sort(items, [](auto &item) { return std::tie(item.node, item.id);}); break;
case Column::FREQ: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).freq, id); }); break; case Column::FREQ: do_sort(items, [](auto &item) { return std::tie(item.data->freq, item.id); }); break;
case Column::COUNT: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).count, 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; 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; 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(); const QString &txt = it.value();
QRegularExpression re(txt, QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption);
switch (it.key()) { switch (it.key()) {
case Column::NAME: { case Column::NAME: {
const auto msg = dbc()->msg(id); match = item.name.contains(txt, Qt::CaseInsensitive);
match = re.match(msg ? msg->name : UNTITLED).hasMatch(); if (!match) {
match = match || (msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), const auto m = dbc()->msg(item.id);
[&re](const auto &s) { return re.match(s->name).hasMatch(); })); match = m && std::any_of(m->sigs.cbegin(), m->sigs.cend(),
[&txt](const auto &s) { return s->name.contains(txt, Qt::CaseInsensitive); });
}
break; break;
} }
case Column::SOURCE: case Column::SOURCE:
match = parseRange(txt, id.source); match = parseRange(txt, item.id.source);
break; break;
case Column::ADDRESS: { case Column::ADDRESS:
match = re.match(QString::number(id.address, 16)).hasMatch(); match = QString::number(item.id.address, 16).contains(txt, Qt::CaseInsensitive);
match = match || parseRange(txt, id.address, 16); match = match || parseRange(txt, item.id.address, 16);
break; break;
}
case Column::NODE: case Column::NODE:
match = re.match(msg_node_from_id(id)).hasMatch(); match = item.node.contains(txt, Qt::CaseInsensitive);
break; break;
case Column::FREQ: case Column::FREQ:
// TODO: Hide stale messages? // TODO: Hide stale messages?
match = parseRange(txt, data.freq); match = parseRange(txt, item.data->freq);
break; break;
case Column::COUNT: case Column::COUNT:
match = parseRange(txt, data.count); match = parseRange(txt, item.data->count);
break; break;
case Column::DATA: { case Column::DATA:
match = QString(data.dat.toHex()).contains(txt, Qt::CaseInsensitive); match = utils::toHex(item.data->dat).contains(txt, Qt::CaseInsensitive);
match = match || re.match(QString(data.dat.toHex())).hasMatch();
match = match || re.match(QString(data.dat.toHex(' '))).hasMatch();
break; break;
}
} }
} }
return match; return match;
} }
void MessageListModel::filterAndSort() { void MessageListModel::filterAndSort(bool force_reset) {
std::vector<MessageId> new_msgs; // merge CAN and DBC messages
new_msgs.reserve(can->last_msgs.size() + dbc_address.size()); std::vector<MessageId> all_messages;
all_messages.reserve(can->lastMessages().size() + dbc_messages_.size());
auto address = dbc_address; auto dbc_msgs = dbc_messages_;
for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { for (const auto &[id, m] : can->lastMessages()) {
if (filter_str.isEmpty() || matchMessage(it.key(), it.value(), filter_str)) { all_messages.push_back(id);
new_msgs.push_back(it.key()); dbc_msgs.erase(MessageId{.source = INVALID_SOURCE, .address = id.address});
}
address.remove(it.key().address);
} }
std::copy(dbc_msgs.begin(), dbc_msgs.end(), std::back_inserter(all_messages));
// merge all DBC messages // filter and sort
for (auto &addr : address) { std::vector<Item> items;
MessageId id{.source = INVALID_SOURCE, .address = addr}; for (const auto &id : all_messages) {
if (filter_str.isEmpty() || matchMessage(id, {}, filter_str)) { auto msg = dbc()->msg(id);
new_msgs.push_back(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 (force_reset || items_ != items) {
if (msgs != new_msgs) {
beginResetModel(); beginResetModel();
msgs = std::move(new_msgs); items_ = std::move(items);
endResetModel(); endResetModel();
} }
} }
void MessageListModel::msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids) { void MessageListModel::msgsReceived(const std::set<MessageId> *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)) { if (has_new_ids || filters_.contains(Column::FREQ) || filters_.contains(Column::COUNT) || filters_.contains(Column::DATA)) {
filterAndSort(); filterAndSort();
} }
for (int i = 0; i < msgs.size(); ++i) { for (int i = 0; i < items_.size(); ++i) {
if (new_msgs->contains(msgs[i])) { if (!new_msgs || new_msgs->count(items_[i].id)) {
for (int col = Column::FREQ; col < columnCount(); ++col) for (int col = Column::FREQ; col < columnCount(); ++col)
emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole}); 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) { void MessageListModel::sort(int column, Qt::SortOrder order) {
if (column != columnCount() - 1) { if (column != Column::DATA) {
sort_column = column; sort_column = column;
sort_order = order; sort_order = order;
filterAndSort(); 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 // MessageView
void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
@ -414,14 +376,11 @@ void MessageView::updateBytesSectionSize() {
auto delegate = ((MessageBytesDelegate *)itemDelegate()); auto delegate = ((MessageBytesDelegate *)itemDelegate());
int max_bytes = 8; int max_bytes = 8;
if (!delegate->multipleLines()) { if (!delegate->multipleLines()) {
for (auto it = can->last_msgs.constBegin(); it != can->last_msgs.constEnd(); ++it) { for (const auto &[_, m] : can->lastMessages()) {
max_bytes = std::max(max_bytes, it.value().dat.size()); max_bytes = std::max<int>(max_bytes, m.dat.size());
} }
} }
int width = delegate->widthForBytes(max_bytes); header()->resizeSection(MessageListModel::Column::DATA, delegate->sizeForBytes(max_bytes).width());
if (header()->sectionSize(MessageListModel::Column::DATA) != width) {
header()->resizeSection(MessageListModel::Column::DATA, width);
}
} }
// MessageViewHeader // MessageViewHeader
@ -446,8 +405,7 @@ void MessageViewHeader::updateHeaderPositions() {
for (int i = 0; i < count(); i++) { for (int i = 0; i < count(); i++) {
if (editors[i]) { if (editors[i]) {
int h = editors[i]->sizeHint().height(); int h = editors[i]->sizeHint().height();
editors[i]->move(sectionViewportPosition(i), sz.height()); editors[i]->setGeometry(sectionViewportPosition(i), sz.height(), sectionSize(i), h);
editors[i]->resize(sectionSize(i), h);
editors[i]->setHidden(isSectionHidden(i)); editors[i]->setHidden(isSectionHidden(i));
} }
} }

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <set>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -9,7 +10,6 @@
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QMenu> #include <QMenu>
#include <QSet>
#include <QToolBar> #include <QToolBar>
#include <QTreeView> #include <QTreeView>
@ -34,23 +34,28 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 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; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return Column::DATA + 1; }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; 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 sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
void setFilterStrings(const QMap<int, QString> &filters); void setFilterStrings(const QMap<int, QString> &filters);
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 filterAndSort(); void filterAndSort(bool force_reset = false);
void suppress();
void clearSuppress();
void dbcModified(); 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: private:
void sortMessages(std::vector<MessageId> &new_msgs); void sortItems(std::vector<MessageListModel::Item> &items);
bool matchMessage(const MessageId &id, const CanData &data, const QMap<int, QString> &filters); bool match(const MessageListModel::Item &id);
QMap<int, QString> filter_str; QMap<int, QString> filters_;
QSet<uint32_t> dbc_address; std::set<MessageId> dbc_messages_;
int sort_column = 0; int sort_column = 0;
Qt::SortOrder sort_order = Qt::AscendingOrder; Qt::SortOrder sort_order = Qt::AscendingOrder;
}; };
@ -91,7 +96,7 @@ public:
void selectMessage(const MessageId &message_id); void selectMessage(const MessageId &message_id);
QByteArray saveHeaderState() const { return view->header()->saveState(); } QByteArray saveHeaderState() const { return view->header()->saveState(); }
bool restoreHeaderState(const QByteArray &state) const { return view->header()->restoreState(state); } bool restoreHeaderState(const QByteArray &state) const { return view->header()->restoreState(state); }
void updateSuppressedButtons(); void suppressHighlighted();
public slots: public slots:
void dbcModified(); void dbcModified();

@ -33,7 +33,7 @@ void settings_op(SettingOperation op) {
op(s, "chart_series_type", settings.chart_series_type); op(s, "chart_series_type", settings.chart_series_type);
op(s, "theme", settings.theme); op(s, "theme", settings.theme);
op(s, "sparkline_range", settings.sparkline_range); 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_livestream", settings.log_livestream);
op(s, "log_path", settings.log_path); op(s, "log_path", settings.log_path);
op(s, "drag_direction", (int &)settings.drag_direction); op(s, "drag_direction", (int &)settings.drag_direction);

@ -33,7 +33,7 @@ public:
int chart_series_type = 0; int chart_series_type = 0;
int theme = 0; int theme = 0;
int sparkline_range = 15; // 15 seconds int sparkline_range = 15; // 15 seconds
bool multiple_lines_bytes = true; bool multiple_lines_hex = false;
bool log_livestream = true; bool log_livestream = true;
bool suppress_defined_signals = false; bool suppress_defined_signals = false;
QString log_path; QString log_path;

@ -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) { 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}; Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig};
parent_item->children.insert(pos, item); parent_item->children.insert(pos, item);
QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type",
"Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"}; "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Table"};
for (int i = 0; i < std::size(titles); ++i) { 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)}); 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 *SignalModel::getItem(const QModelIndex &index) const {
SignalModel::Item *item = nullptr; auto item = index.isValid() ? (SignalModel::Item *)index.internalPointer() : nullptr;
if (index.isValid()) {
item = (SignalModel::Item *)index.internalPointer();
}
return item ? item : root.get(); return item ? item : root.get();
} }
@ -369,8 +366,7 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->setFont(label_font); painter->setFont(label_font);
QString freq = QString("%1 hz").arg(item->sparkline.freq(), 0, 'g', 2); 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); painter->drawText(rect.adjusted(5, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, freq);
QFontMetrics fm(label_font); value_adjust = QFontMetrics(label_font).width(freq) + 10;
value_adjust = fm.width(freq) + 10;
} }
// signal value // signal value
painter->setFont(option.font); 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); 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) { for (auto item : model->root->children) {
double value = 0; 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); item->sig_val = item->sig->formatValue(value);
} }
max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val)); max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val));

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <set>
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QLabel> #include <QLabel>
@ -82,7 +83,7 @@ class SignalItemDelegate : public QStyledItemDelegate {
public: public:
SignalItemDelegate(QObject *parent); SignalItemDelegate(QObject *parent);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; 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; 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 updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, 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 setSparklineRange(int value);
void handleSignalAdded(MessageId id, const cabana::Signal *sig); void handleSignalAdded(MessageId id, const cabana::Signal *sig);
void handleSignalUpdated(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 { struct TreeView : public QTreeView {
TreeView(QWidget *parent) : QTreeView(parent) {} TreeView(QWidget *parent) : QTreeView(parent) {}

@ -1,8 +1,10 @@
#include "tools/cabana/streams/abstractstream.h" #include "tools/cabana/streams/abstractstream.h"
#include <algorithm> #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 static const int EVENT_NEXT_BUFFER_SIZE = 6 * 1024 * 1024; // 6MB
@ -15,82 +17,97 @@ StreamNotifier *StreamNotifier::instance() {
AbstractStream::AbstractStream(QObject *parent) : QObject(parent) { AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
assert(parent != nullptr); 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(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::DBCFileChanged, this, &AbstractStream::updateMasks);
QObject::connect(dbc(), &DBCManager::maskUpdated, this, &AbstractStream::updateMasks); QObject::connect(dbc(), &DBCManager::maskUpdated, this, &AbstractStream::updateMasks);
QObject::connect(this, &AbstractStream::streamStarted, [this]() { QObject::connect(this, &AbstractStream::streamStarted, [this]() {
emit StreamNotifier::instance()->changingStream(); emit StreamNotifier::instance()->changingStream();
delete can; delete can;
can = this; 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(); emit StreamNotifier::instance()->streamStarted();
}); });
} }
void AbstractStream::updateMasks() { void AbstractStream::updateMasks() {
std::lock_guard lk(mutex); std::lock_guard lk(mutex_);
masks.clear(); masks_.clear();
if (settings.suppress_defined_signals) { if (!settings.suppress_defined_signals)
for (auto s : sources) { return;
if (auto f = dbc()->findDBCFile(s)) {
for (const auto &[address, m] : f->getMessages()) { for (const auto s : sources) {
masks[{.source = (uint8_t)s, .address = address}] = m.mask; 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<MessageId, CanData> *messages) { void AbstractStream::updateLastMessages() {
auto prev_src_size = sources.size(); auto prev_src_size = sources.size();
auto prev_msg_size = last_msgs.size(); auto prev_msg_size = last_msgs.size();
for (auto it = messages->begin(); it != messages->end(); ++it) { std::set<MessageId> msgs;
const auto &id = it.key(); {
last_msgs[id] = it.value(); std::lock_guard lk(mutex_);
sources.insert(id.source); 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) { if (sources.size() != prev_src_size) {
updateMasks(); updateMasks();
emit sourcesUpdated(sources); emit sourcesUpdated(sources);
} }
emit updated(); emit msgsReceived(&msgs, prev_msg_size != last_msgs.size());
emit msgsReceived(messages, prev_msg_size != last_msgs.size());
delete messages;
processing = false;
} }
void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size) { void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size) {
std::lock_guard lk(mutex); std::lock_guard lk(mutex_);
auto mask_it = masks.find(id); messages_[id].compute(id, data, size, sec, getSpeed(), masks_[id]);
std::vector<uint8_t> *mask = mask_it == masks.end() ? nullptr : &mask_it->second; new_msgs_.insert(id);
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;
} }
const std::vector<const CanEvent *> &AbstractStream::events(const MessageId &id) const { 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) { const CanData &AbstractStream::lastMessage(const MessageId &id) {
static CanData empty_data = {}; static CanData empty_data = {};
auto it = last_msgs.find(id); 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. // it is thread safe to update data in updateLastMsgsTo.
// updateLastMsgsTo is always called in UI thread. // updateLastMsgsTo is always called in UI thread.
void AbstractStream::updateLastMsgsTo(double sec) { void AbstractStream::updateLastMsgsTo(double sec) {
new_msgs.reset(new QHash<MessageId, CanData>); new_msgs_.clear();
all_msgs.clear(); messages_.clear();
last_msgs.clear();
uint64_t last_ts = (sec + routeStartTime()) * 1e9; uint64_t last_ts = (sec + routeStartTime()) * 1e9;
for (auto &[id, ev] : events_) { for (const auto &[id, ev] : events_) {
auto it = std::lower_bound(ev.crbegin(), ev.crend(), last_ts, [](auto e, uint64_t ts) { auto it = std::upper_bound(ev.begin(), ev.end(), last_ts, CompareCanEvent());
return e->mono_time > ts; if (it != ev.begin()) {
}); auto prev = std::prev(it);
auto mask_it = masks.find(id); double ts = (*prev)->mono_time / 1e9 - routeStartTime();
std::vector<uint8_t> *mask = mask_it == masks.end() ? nullptr : &mask_it->second; auto &m = messages_[id];
if (it != ev.crend()) { m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {});
double ts = (*it)->mono_time / 1e9 - routeStartTime(); m.count = std::distance(ev.begin(), prev) + 1;
auto &m = all_msgs[id];
m.compute(id, (const char *)(*it)->dat, (*it)->size, ts, getSpeed(), mask);
m.count = std::distance(it, ev.crend());
} }
} }
// deep copy all_msgs to last_msgs to avoid multi-threading issue. bool id_changed = messages_.size() != last_msgs.size() ||
last_msgs = all_msgs; std::any_of(messages_.cbegin(), messages_.cend(),
last_msgs.detach(); [this](const auto &m) { return !last_msgs.count(m.first); });
// use a timer to prevent recursive calls last_msgs = messages_;
QTimer::singleShot(0, [this]() { emit msgsReceived(nullptr, id_changed);
emit updated();
emit msgsReceived(&last_msgs, true);
});
} }
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) {
static MessageEventsMap msg_events; auto dat = c.getDat();
static std::vector<const CanEvent *> new_events; 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;
std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); }); std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); });
new_events.clear(); for (auto e : events) {
msg_events[{.source = e->src, .address = e->address}].push_back(e);
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);
}
}
} }
if (!new_events.empty()) { if (!events.empty()) {
for (auto &[id, new_e] : msg_events) { for (const auto &[id, new_e] : msg_events) {
if (!new_e.empty()) { if (!new_e.empty()) {
auto &e = events_[id]; auto &e = events_[id];
auto pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent()); auto pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent());
e.insert(pos, new_e.cbegin(), new_e.cend()); 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()); auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), events.front()->mono_time, CompareCanEvent());
all_events_.insert(pos, new_events.cbegin(), new_events.cend()); all_events_.insert(pos, events.cbegin(), events.cend());
emit eventsMerged(msg_events); emit eventsMerged(msg_events);
} }
lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time; 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 { namespace {
constexpr int periodic_threshold = 10; enum Color { GREYISH_BLUE, CYAN, RED};
constexpr int start_alpha = 128; QColor getColor(int c) {
constexpr float fade_time = 2.0; constexpr int start_alpha = 128;
const QColor CYAN = QColor(0, 187, 255, start_alpha); static const QColor colors[] = {
const QColor RED = QColor(255, 0, 0, start_alpha); [GREYISH_BLUE] = QColor(102, 86, 169, start_alpha / 2),
const QColor GREYISH_BLUE = QColor(102, 86, 169, start_alpha / 2); [CYAN] = QColor(0, 187, 255, start_alpha),
const QColor CYAN_LIGHTER = QColor(0, 187, 255, start_alpha).lighter(135); [RED] = QColor(255, 0, 0, start_alpha),
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); return settings.theme == LIGHT_THEME ? colors[c] : colors[c].lighter(135);
}
inline QColor blend(const QColor &a, const QColor &b) { 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); 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 } // namespace
void CanData::compute(const MessageId &msg_id, const char *can_data, const int size, double current_sec, 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) { double playback_speed, const std::vector<uint8_t> &mask, double in_freq) {
ts = current_sec; ts = current_sec;
++count; ++count;
@ -224,55 +228,53 @@ void CanData::compute(const MessageId &msg_id, const char *can_data, const int s
if (dat.size() != size) { if (dat.size() != size) {
dat.resize(size); dat.resize(size);
bit_change_counts.resize(size); colors.assign(size, QColor(0, 0, 0, 0));
colors = QVector(size, QColor(0, 0, 0, 0)); last_changes.resize(size);
last_change_t.assign(size, ts); std::for_each(last_changes.begin(), last_changes.end(), [current_sec](auto &c) { c.ts = current_sec; });
last_delta.resize(size);
same_delta_counter.resize(size);
} else { } else {
bool lighter = settings.theme == DARK_THEME; constexpr int periodic_threshold = 10;
const QColor &cyan = !lighter ? CYAN : CYAN_LIGHTER; constexpr float fade_time = 2.0;
const QColor &red = !lighter ? RED : RED_LIGHTER; const float alpha_delta = 1.0 / (freq + 1) / (fade_time * playback_speed);
const QColor &greyish_blue = !lighter ? GREYISH_BLUE : GREYISH_BLUE_LIGHTER;
for (int i = 0; i < size; ++i) { 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 last = dat[i] & mask_byte;
const uint8_t cur = can_data[i] & mask_byte; const uint8_t cur = can_data[i] & mask_byte;
const int delta = cur - last;
if (last != cur) { 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 // Keep track if signal is changing randomly, or mostly moving in the same direction
if (std::signbit(delta) == std::signbit(last_delta[i])) { if (std::signbit(delta) == std::signbit(last_change.delta)) {
same_delta_counter[i] = std::min(16, same_delta_counter[i] + 1); last_change.same_delta_counter = std::min(16, last_change.same_delta_counter + 1);
} else { } 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 // 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 // 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 { } else {
// Periodic changes // Periodic changes
colors[i] = blend(colors[i], greyish_blue); colors[i] = blend(colors[i], getColor(GREYISH_BLUE));
} }
// Track bit level changes // Track bit level changes
const uint8_t tmp = (cur ^ last); const uint8_t tmp = (cur ^ last);
for (int bit = 0; bit < 8; bit++) { for (int bit = 0; bit < 8; bit++) {
if (tmp & (1 << bit)) { if (tmp & (1 << (7 - bit))) {
bit_change_counts[i][bit] += 1; last_change.bit_change_counts[bit] += 1;
} }
} }
last_change_t[i] = ts; last_change.ts = ts;
last_delta[i] = delta; last_change.delta = delta;
} else { } else {
// Fade out // 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)); colors[i].setAlphaF(std::max(0.0, colors[i].alphaF() - alpha_delta));
} }
} }

@ -1,34 +1,37 @@
#pragma once #pragma once
#include <array> #include <array>
#include <atomic>
#include <memory> #include <memory>
#include <mutex>
#include <set>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <QColor> #include <QColor>
#include <QDateTime> #include <QDateTime>
#include <QHash>
#include "common/timing.h" #include "cereal/messaging/messaging.h"
#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/settings.h"
#include "tools/cabana/util.h" #include "tools/cabana/util.h"
#include "tools/replay/replay.h"
struct CanData { struct CanData {
void compute(const MessageId &msg_id, const char *dat, const int size, double current_sec, 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 = nullptr, double in_freq = 0); double playback_speed, const std::vector<uint8_t> &mask, double in_freq = 0);
double ts = 0.; double ts = 0.;
uint32_t count = 0; uint32_t count = 0;
double freq = 0; double freq = 0;
QByteArray dat; std::vector<uint8_t> dat;
QVector<QColor> colors; std::vector<QColor> colors;
std::vector<double> last_change_t;
std::vector<std::array<uint32_t, 8>> bit_change_counts; struct ByteLastChange {
std::vector<int> last_delta; double ts;
std::vector<int> same_delta_counter; 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; double last_freq_update_ts = 0;
}; };
@ -60,7 +63,7 @@ public:
AbstractStream(QObject *parent); AbstractStream(QObject *parent);
virtual ~AbstractStream() {} virtual ~AbstractStream() {}
virtual void start() = 0; virtual void start() = 0;
inline bool liveStreaming() const { return route() == nullptr; } virtual bool liveStreaming() const { return true; }
virtual void seekTo(double ts) {} virtual void seekTo(double ts) {}
virtual QString routeName() const = 0; virtual QString routeName() const = 0;
virtual QString carFingerprint() const { return ""; } virtual QString carFingerprint() const { return ""; }
@ -68,48 +71,57 @@ public:
virtual double routeStartTime() const { return 0; } virtual double routeStartTime() const { return 0; }
virtual double currentSec() const = 0; virtual double currentSec() const = 0;
virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); } 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 void setSpeed(float speed) {}
virtual double getSpeed() { return 1; } virtual double getSpeed() { return 1; }
virtual bool isPaused() const { return false; } virtual bool isPaused() const { return false; }
virtual void pause(bool pause) {} 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; const std::vector<const CanEvent *> &events(const MessageId &id) const;
size_t suppressHighlighted();
void clearSuppressed();
void suppressDefinedSignals(bool suppress);
signals: signals:
void paused(); void paused();
void resume(); void resume();
void seekedTo(double sec); void seekedTo(double sec);
void streamStarted(); void streamStarted();
void eventsMerged(const MessageEventsMap &events_map); void eventsMerged(const MessageEventsMap &events_map);
void updated(); void msgsReceived(const std::set<MessageId> *new_msgs, bool has_new_ids);
void msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids);
void sourcesUpdated(const SourceSet &s); void sourcesUpdated(const SourceSet &s);
void privateUpdateLastMsgsSignal();
public: public:
QHash<MessageId, CanData> last_msgs;
SourceSet sources; SourceSet sources;
protected: protected:
void mergeEvents(std::vector<Event *>::const_iterator first, std::vector<Event *>::const_iterator last); void mergeEvents(const std::vector<const CanEvent *> &events);
bool postEvents(); const CanEvent *newEvent(uint64_t mono_time, const cereal::CanData::Reader &c);
uint64_t lastEventMonoTime() const { return lastest_event_ts; }
void updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size); void updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size);
void updateMessages(QHash<MessageId, CanData> *); uint64_t lastEventMonoTime() const { return lastest_event_ts; }
void updateMasks();
void updateLastMsgsTo(double sec);
std::vector<const CanEvent *> all_events_;
uint64_t lastest_event_ts = 0; uint64_t lastest_event_ts = 0;
std::atomic<bool> processing = false;
std::unique_ptr<QHash<MessageId, CanData>> new_msgs; private:
QHash<MessageId, CanData> all_msgs; void updateLastMessages();
void updateLastMsgsTo(double sec);
void updateMasks();
MessageEventsMap events_; MessageEventsMap events_;
std::vector<const CanEvent *> all_events_; std::unordered_map<MessageId, CanData> last_msgs;
std::unique_ptr<MonotonicBuffer> event_buffer; std::unique_ptr<MonotonicBuffer> event_buffer_;
std::mutex mutex;
std::unordered_map<MessageId, std::vector<uint8_t>> masks; // 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 { class AbstractOpenStreamWidget : public QWidget {

@ -8,6 +8,7 @@
#include <QRadioButton> #include <QRadioButton>
#include <QRegularExpression> #include <QRegularExpression>
#include <QRegularExpressionValidator> #include <QRegularExpressionValidator>
#include <QThread>
// DeviceStream // DeviceStream
@ -21,17 +22,14 @@ void DeviceStream::streamThread() {
std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString();
std::unique_ptr<SubSocket> sock(SubSocket::create(context.get(), "can", address)); std::unique_ptr<SubSocket> sock(SubSocket::create(context.get(), "can", address));
assert(sock != NULL); assert(sock != NULL);
sock->setTimeout(50);
// run as fast as messages come in // run as fast as messages come in
while (!QThread::currentThread()->isInterruptionRequested()) { while (!QThread::currentThread()->isInterruptionRequested()) {
Message *msg = sock->receive(true); std::unique_ptr<Message> msg(sock->receive(true));
if (!msg) { if (!msg) {
QThread::msleep(50); QThread::msleep(50);
continue; continue;
} }
handleEvent(kj::ArrayPtr<capnp::word>((capnp::word*)msg->getData(), msg->getSize() / sizeof(capnp::word)));
handleEvent(msg->getData(), msg->getSize());
delete msg;
} }
} }

@ -1,12 +1,17 @@
#include "tools/cabana/streams/livestream.h" #include "tools/cabana/streams/livestream.h"
#include <QThread>
#include <algorithm> #include <algorithm>
#include <fstream>
#include <memory> #include <memory>
#include "common/timing.h"
#include "common/util.h"
struct LiveStream::Logger { struct LiveStream::Logger {
Logger() : start_ts(seconds_since_epoch()), segment_num(-1) {} 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; int n = (seconds_since_epoch() - start_ts) / 60.0;
if (std::exchange(segment_num, n) != segment_num) { if (std::exchange(segment_num, n) != segment_num) {
QString dir = QString("%1/%2--%3") 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.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; std::unique_ptr<std::ofstream> fs;
@ -57,14 +63,20 @@ LiveStream::~LiveStream() {
} }
// called in streamThread // called in streamThread
void LiveStream::handleEvent(const char *data, const size_t size) { void LiveStream::handleEvent(kj::ArrayPtr<capnp::word> data) {
if (logger) { if (logger) {
logger->write(data, size); logger->write(data);
} }
std::lock_guard lk(lock); capnp::FlatArrayMessageReader reader(data);
auto &msg = receivedMessages.emplace_back(data, size); auto event = reader.getRoot<cereal::Event>();
receivedEvents.push_back(msg.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) { void LiveStream::timerEvent(QTimerEvent *event) {
@ -72,9 +84,8 @@ void LiveStream::timerEvent(QTimerEvent *event) {
{ {
// merge events received from live stream thread. // merge events received from live stream thread.
std::lock_guard lk(lock); std::lock_guard lk(lock);
mergeEvents(receivedEvents.cbegin(), receivedEvents.cend()); mergeEvents(received_events_);
receivedEvents.clear(); received_events_.clear();
receivedMessages.clear();
} }
if (!all_events_.empty()) { if (!all_events_.empty()) {
begin_event_ts = all_events_.front()->mono_time; 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); updateEvent(id, (e->mono_time - begin_event_ts) / 1e9, e->dat, e->size);
current_event_ts = e->mono_time; current_event_ts = e->mono_time;
} }
postEvents(); emit privateUpdateLastMsgsSignal();
} }
void LiveStream::seekTo(double sec) { void LiveStream::seekTo(double sec) {

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

@ -1,15 +1,13 @@
#include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/pandastream.h"
#include <vector> #include <QDebug>
#include <QCheckBox> #include <QCheckBox>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QThread>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "selfdrive/ui/qt/util.h"
// TODO: remove clearLayout // TODO: remove clearLayout
static void clearLayout(QLayout* layout) { static void clearLayout(QLayout* layout) {
while (layout->count() > 0) { while (layout->count() > 0) {
@ -90,7 +88,6 @@ void PandaStream::streamThread() {
MessageBuilder msg; MessageBuilder msg;
auto evt = msg.initEvent(); auto evt = msg.initEvent();
auto canData = evt.initCan(raw_can_data.size()); auto canData = evt.initCan(raw_can_data.size());
for (uint i = 0; i<raw_can_data.size(); i++) { for (uint i = 0; i<raw_can_data.size(); i++) {
canData[i].setAddress(raw_can_data[i].address); canData[i].setAddress(raw_can_data[i].address);
canData[i].setBusTime(raw_can_data[i].busTime); canData[i].setBusTime(raw_can_data[i].busTime);
@ -98,8 +95,7 @@ void PandaStream::streamThread() {
canData[i].setSrc(raw_can_data[i].src); canData[i].setSrc(raw_can_data[i].src);
} }
auto bytes = msg.toBytes(); handleEvent(capnp::messageToFlatArray(msg));
handleEvent((const char*)bytes.begin(), bytes.size());
panda->send_heartbeat(false); panda->send_heartbeat(false);
} }

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

@ -15,7 +15,7 @@ ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
op_prefix = std::make_unique<OpenpilotPrefix>(); op_prefix = std::make_unique<OpenpilotPrefix>();
#endif #endif
QObject::connect(&settings, &Settings::changed, [this]() { QObject::connect(&settings, &Settings::changed, this, [this]() {
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes); if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);
}); });
} }
@ -28,8 +28,18 @@ void ReplayStream::mergeSegments() {
for (auto &[n, seg] : replay->segments()) { for (auto &[n, seg] : replay->segments()) {
if (seg && seg->isLoaded() && !processed_segments.count(n)) { if (seg && seg->isLoaded() && !processed_segments.count(n)) {
processed_segments.insert(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) { bool ReplayStream::eventFilter(const Event *event) {
static double prev_update_ts = 0; static double prev_update_ts = 0;
// delay posting CAN message if UI thread is busy
if (event->which == cereal::Event::Which::CAN) { if (event->which == cereal::Event::Which::CAN) {
double current_sec = event->mono_time / 1e9 - routeStartTime(); double current_sec = event->mono_time / 1e9 - routeStartTime();
for (const auto &c : event->event.getCan()) { for (const auto &c : event->event.getCan()) {
@ -64,9 +73,8 @@ bool ReplayStream::eventFilter(const Event *event) {
double ts = millis_since_boot(); double ts = millis_since_boot();
if ((ts - prev_update_ts) > (1000.0 / settings.fps)) { if ((ts - prev_update_ts) > (1000.0 / settings.fps)) {
if (postEvents()) { emit privateUpdateLastMsgsSignal();
prev_update_ts = ts; prev_update_ts = ts;
}
} }
return true; return true;
} }

@ -8,6 +8,7 @@
#include "common/prefix.h" #include "common/prefix.h"
#include "tools/cabana/streams/abstractstream.h" #include "tools/cabana/streams/abstractstream.h"
#include "tools/replay/replay.h"
class ReplayStream : public AbstractStream { class ReplayStream : public AbstractStream {
Q_OBJECT Q_OBJECT
@ -18,13 +19,14 @@ public:
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
bool eventFilter(const Event *event); bool eventFilter(const Event *event);
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); } 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 routeName() const override { return replay->route()->name(); }
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
double totalSeconds() const override { return replay->totalSeconds(); } double totalSeconds() const override { return replay->totalSeconds(); }
inline QDateTime beginDateTime() const { return replay->route()->datetime(); } inline QDateTime beginDateTime() const { return replay->route()->datetime(); }
inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; } inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; }
inline double currentSec() const override { return replay->currentSeconds(); } 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 void setSpeed(float speed) override { replay->setSpeed(speed); }
inline float getSpeed() const { return replay->getSpeed(); } inline float getSpeed() const { return replay->getSpeed(); }
inline Replay *getReplay() const { return replay.get(); } inline Replay *getReplay() const { return replay.get(); }

@ -1,8 +1,11 @@
#include "tools/cabana/streams/socketcanstream.h" #include "tools/cabana/streams/socketcanstream.h"
#include <QLabel> #include <QDebug>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QThread>
SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) { SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) {
if (!available()) { if (!available()) {
@ -49,7 +52,6 @@ void SocketCanStream::streamThread() {
auto evt = msg.initEvent(); auto evt = msg.initEvent();
auto canData = evt.initCan(frames.size()); auto canData = evt.initCan(frames.size());
for (uint i = 0; i < frames.size(); i++) { for (uint i = 0; i < frames.size(); i++) {
if (!frames[i].isValid()) continue; if (!frames[i].isValid()) continue;
@ -60,8 +62,7 @@ void SocketCanStream::streamThread() {
canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size())); canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size()));
} }
auto bytes = msg.toBytes(); handleEvent(capnp::messageToFlatArray(msg));
handleEvent((const char*)bytes.begin(), bytes.size());
} }
} }

@ -5,10 +5,7 @@
#include <QtSerialBus/QCanBus> #include <QtSerialBus/QCanBus>
#include <QtSerialBus/QCanBusDevice> #include <QtSerialBus/QCanBusDevice>
#include <QtSerialBus/QCanBusDeviceInfo> #include <QtSerialBus/QCanBusDeviceInfo>
#include <QComboBox> #include <QComboBox>
#include <QFormLayout>
#include <QVBoxLayout>
#include "tools/cabana/streams/livestream.h" #include "tools/cabana/streams/livestream.h"
@ -21,7 +18,6 @@ class SocketCanStream : public LiveStream {
public: public:
SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {});
static AbstractOpenStreamWidget *widget(AbstractStream **stream); static AbstractOpenStreamWidget *widget(AbstractStream **stream);
static bool available(); static bool available();
inline QString routeName() const override { inline QString routeName() const override {

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

@ -192,7 +192,7 @@ void FindSignalDlg::search() {
search_btn->setEnabled(false); search_btn->setEnabled(false);
stats_label->setVisible(false); stats_label->setVisible(false);
search_btn->setText("Finding ...."); search_btn->setText("Finding ....");
QTimer::singleShot(0, [=]() { model->search(cmp); }); QTimer::singleShot(0, this, [=]() { model->search(cmp); });
} }
void FindSignalDlg::setInitialSignals() { void FindSignalDlg::setInitialSignals() {
@ -222,15 +222,15 @@ void FindSignalDlg::setInitialSignals() {
} }
model->initial_signals.clear(); model->initial_signals.clear();
for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { for (const auto &[id, m] : can->lastMessages()) {
if (buses.isEmpty() || buses.contains(it.key().source) && (addresses.isEmpty() || addresses.contains(it.key().address))) { if (buses.isEmpty() || buses.contains(id.source) && (addresses.isEmpty() || addresses.contains(id.address))) {
const auto &events = can->events(it.key()); const auto &events = can->events(id);
auto e = std::lower_bound(events.cbegin(), events.cend(), first_time, CompareCanEvent()); auto e = std::lower_bound(events.cbegin(), events.cend(), first_time, CompareCanEvent());
if (e != events.cend()) { 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 size = min_size->value(); size <= max_size->value(); ++size) {
for (int start = 0; start <= total_size - size; ++start) { 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.start_bit = start;
s.sig.size = size; s.sig.size = size;
updateMsbLsb(s.sig); updateMsbLsb(s.sig);

@ -9,7 +9,7 @@
#include <unistd.h> #include <unistd.h>
#include <QColor> #include <QColor>
#include <QDebug> #include <QDateTime>
#include <QFontDatabase> #include <QFontDatabase>
#include <QLocale> #include <QLocale>
#include <QPainter> #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].setText(QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
hex_text_table[i].prepare({}, fixed_font); 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 { QSize MessageBytesDelegate::sizeForBytes(int n) const {
int h_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; int rows = multiple_lines ? std::max(1, n / 8) : 1;
return n * byte_size.width() + h_margin * 2; 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 { 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); auto data = index.data(BytesRole);
if (!data.isValid()) { return sizeForBytes(data.isValid() ? static_cast<std::vector<uint8_t> *>(data.value<void *>())->size() : 0);
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};
} }
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { 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); return QStyledItemDelegate::paint(painter, option, index);
} }
auto byte_list = data.toByteArray(); QFont old_font = painter->font();
auto colors = index.data(ColorsRole).value<QVector<QColor>>(); QPen old_pen = painter->pen();
int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
if (option.state & QStyle::State_Selected) { if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.brush(QPalette::Normal, QPalette::Highlight)); painter->fillRect(option.rect, option.palette.brush(QPalette::Normal, QPalette::Highlight));
} }
const QPoint pt{option.rect.left() + h_margin, option.rect.top() + v_margin}; 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); 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 row = !multiple_lines ? 0 : i / 8;
int column = !multiple_lines ? i : 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); 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) { } else if (option.state & QStyle::State_Selected) {
painter->setPen(option.palette.color(QPalette::HighlightedText)); 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->setFont(old_font);
painter->setPen(old_pen); painter->setPen(old_pen);
@ -251,15 +243,6 @@ QString formatSeconds(double sec, bool include_milliseconds, bool absolute_time)
} // namespace utils } // 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) { int num_decimals(double num) {
const QString string = QString::number(num); const QString string = QString::number(num);
auto dot_pos = string.indexOf('.'); auto dot_pos = string.indexOf('.');

@ -8,7 +8,6 @@
#include <QApplication> #include <QApplication>
#include <QByteArray> #include <QByteArray>
#include <QDateTime>
#include <QDoubleValidator> #include <QDoubleValidator>
#include <QFont> #include <QFont>
#include <QPainter> #include <QPainter>
@ -18,7 +17,6 @@
#include <QStringBuilder> #include <QStringBuilder>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include <QToolButton> #include <QToolButton>
#include <QVector>
#include "tools/cabana/dbc/dbc.h" #include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/settings.h" #include "tools/cabana/settings.h"
@ -75,18 +73,16 @@ public:
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
bool multipleLines() const { return multiple_lines; } bool multipleLines() const { return multiple_lines; }
void setMultipleLines(bool v) { multiple_lines = v; } void setMultipleLines(bool v) { multiple_lines = v; }
int widthForBytes(int n) const; QSize sizeForBytes(int n) const;
private: private:
std::array<QStaticText, 256> hex_text_table; std::array<QStaticText, 256> hex_text_table;
QFont fixed_font; QFont fixed_font;
QSize byte_size = {}; QSize byte_size = {};
bool multiple_lines = false; 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 { class NameValidator : public QRegExpValidator {
Q_OBJECT Q_OBJECT
public: public:
@ -108,6 +104,10 @@ inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text)
auto size = (r.size() - text.size()) / 2; auto size = (r.size() - text.size()) / 2;
p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text); 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 { class ToolButton : public QToolButton {

@ -13,6 +13,9 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QtConcurrent> #include <QtConcurrent>
#include "tools/cabana/streams/replaystream.h"
#include "tools/cabana/util.h"
const int MIN_VIDEO_HEIGHT = 100; const int MIN_VIDEO_HEIGHT = 100;
const int THUMBNAIL_MARGIN = 3; const int THUMBNAIL_MARGIN = 3;
@ -35,7 +38,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::resume, 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(); updatePlayBtnState();
setWhatsThis(tr(R"( setWhatsThis(tr(R"(
@ -179,6 +182,8 @@ void VideoWidget::vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams
void VideoWidget::loopPlaybackClicked() { void VideoWidget::loopPlaybackClicked() {
auto replay = qobject_cast<ReplayStream *>(can)->getReplay(); auto replay = qobject_cast<ReplayStream *>(can)->getReplay();
if (!replay) return;
if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) { if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) {
replay->removeFlag(REPLAY_FLAG_NO_LOOP); replay->removeFlag(REPLAY_FLAG_NO_LOOP);
loop_btn->setIcon("repeat"); loop_btn->setIcon("repeat");

@ -5,12 +5,14 @@
#include <set> #include <set>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QFrame>
#include <QSlider> #include <QSlider>
#include <QTabBar> #include <QTabBar>
#include <QToolButton> #include <QToolButton>
#include "selfdrive/ui/qt/widgets/cameraview.h" #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 { struct AlertInfo {
cereal::ControlsState::AlertStatus status; cereal::ControlsState::AlertStatus status;

Loading…
Cancel
Save