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