diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d557b3af80..6af4ca08f5 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -27,8 +27,9 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', + 'utils/export.cc', 'utils/util.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', - 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index a0e523d666..b1256098eb 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -2,7 +2,7 @@ #include -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" uint qHash(const MessageId &item) { return qHash(item.source) ^ qHash(item.address); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index b3440b557b..90bd9a8f76 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -2,11 +2,12 @@ #include +#include #include -#include #include #include "tools/cabana/commands.h" +#include "tools/cabana/utils/export.h" QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { const bool show_signals = display_signals_mode && sigs.size() > 0; @@ -220,6 +221,8 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { h->addWidget(filters_widget); h->addStretch(0); h->addWidget(dynamic_mode = new QCheckBox(tr("Dynamic")), 0, Qt::AlignRight); + ToolButton *export_btn = new ToolButton("filetype-csv", tr("Export to CSV file...")); + h->addWidget(export_btn, 0, Qt::AlignRight); display_type_cb->addItems({"Signal", "Hex"}); display_type_cb->setToolTip(tr("Display signal value or raw hex value")); @@ -252,6 +255,7 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(setFilter())); QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter())); QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter); + QObject::connect(export_btn, &QToolButton::clicked, this, &LogsWidget::exportToCSV); QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &LogsWidget::refresh); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &LogsWidget::refresh); @@ -304,3 +308,13 @@ void LogsWidget::showEvent(QShowEvent *event) { model->refresh(); } } + +void LogsWidget::exportToCSV() { + QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(can->routeName()).arg(msgName(model->msg_id)); + QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(msgName(model->msg_id)), + dir, tr("csv (*.csv)")); + if (!fn.isEmpty()) { + const bool export_signals = model->display_signals_mode && model->sigs.size() > 0; + export_signals ? utils::exportSignalsToCSV(fn, model->msg_id) : utils::exportToCSV(fn, model->msg_id); + } +} diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 154b139fb0..21df6a622e 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -11,7 +11,7 @@ #include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" class HeaderView : public QHeaderView { public: @@ -80,6 +80,7 @@ public: private slots: void setFilter(); + void exportToCSV(); private: void refresh(); diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 410f3a65f4..bcb65e2e3e 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -22,6 +22,7 @@ #include "tools/cabana/commands.h" #include "tools/cabana/streamselector.h" #include "tools/cabana/tools/findsignal.h" +#include "tools/cabana/utils/export.h" #include "tools/replay/replay.h" MainWindow::MainWindow() : QMainWindow() { @@ -86,7 +87,9 @@ void MainWindow::createActions() { QMenu *file_menu = menuBar()->addMenu(tr("&File")); file_menu->addAction(tr("Open Stream..."), this, &MainWindow::openStream); close_stream_act = file_menu->addAction(tr("Close stream"), this, &MainWindow::closeStream); + export_to_csv_act = file_menu->addAction(tr("Export to CSV..."), this, &MainWindow::exportToCSV); close_stream_act->setEnabled(false); + export_to_csv_act->setEnabled(false); file_menu->addSeparator(); file_menu->addAction(tr("New DBC File"), [this]() { newFile(); }, QKeySequence::New); @@ -270,6 +273,14 @@ void MainWindow::closeStream() { statusBar()->showMessage(tr("stream closed")); } +void MainWindow::exportToCSV() { + QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(can->routeName()); + QString fn = QFileDialog::getSaveFileName(this, "Export stream to CSV file", dir, tr("csv (*.csv)")); + if (!fn.isEmpty()) { + utils::exportToCSV(fn); + } +} + void MainWindow::newFile(SourceSet s) { closeFile(s); dbc()->open(s, "", ""); @@ -344,6 +355,7 @@ void MainWindow::changingStream() { void MainWindow::streamStarted() { bool has_stream = dynamic_cast(can) == nullptr; close_stream_act->setEnabled(has_stream); + export_to_csv_act->setEnabled(has_stream); tools_menu->setEnabled(has_stream); createDockWidgets(); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index ea0bad5ea4..0b869f153d 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -29,6 +29,7 @@ public: public slots: void openStream(); void closeStream(); + void exportToCSV(); void changingStream(); void streamStarted(); @@ -94,6 +95,7 @@ protected: QMenu *manage_dbcs_menu = nullptr; QMenu *tools_menu = nullptr; QAction *close_stream_act = nullptr; + QAction *export_to_csv_act = nullptr; QAction *save_dbc = nullptr; QAction *save_dbc_as = nullptr; QAction *copy_dbc_to_clipboard = nullptr; diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 17de0a1c0a..b63789a026 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -10,7 +10,7 @@ #include #include -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" Settings settings; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 6c9c025129..10ffcb184a 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -12,7 +12,7 @@ #include "cereal/messaging/messaging.h" #include "tools/cabana/dbc/dbcmanager.h" -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" struct CanData { void compute(const MessageId &msg_id, const uint8_t *dat, const int size, double current_sec, diff --git a/tools/cabana/utils/export.cc b/tools/cabana/utils/export.cc new file mode 100644 index 0000000000..10f69ad548 --- /dev/null +++ b/tools/cabana/utils/export.cc @@ -0,0 +1,47 @@ +#include "tools/cabana/utils/export.h" + +#include +#include + +#include "tools/cabana/streams/abstractstream.h" + +namespace utils { + +void exportToCSV(const QString &file_name, std::optional msg_id) { + QFile file(file_name); + if (file.open(QIODevice::ReadWrite | QIODevice::Truncate)) { + const uint64_t start_time = can->routeStartTime(); + QTextStream stream(&file); + stream << "time,addr,bus,data\n"; + for (auto e : msg_id ? can->events(*msg_id) : can->allEvents()) { + stream << QString::number((e->mono_time / 1e9) - start_time, 'f', 2) << "," + << "0x" << QString::number(e->address, 16) << "," << e->src << "," + << "0x" << QByteArray::fromRawData((const char *)e->dat, e->size).toHex().toUpper() << "\n"; + } + } +} + +void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id) { + QFile file(file_name); + if (auto msg = dbc()->msg(msg_id); msg && msg->sigs.size() && file.open(QIODevice::ReadWrite | QIODevice::Truncate)) { + QTextStream stream(&file); + stream << "time,addr,bus"; + for (auto s : msg->sigs) + stream << "," << s->name; + stream << "\n"; + + const uint64_t start_time = can->routeStartTime(); + for (auto e : can->events(msg_id)) { + stream << QString::number((e->mono_time / 1e9) - start_time, 'f', 2) << "," + << "0x" << QString::number(e->address, 16) << "," << e->src; + for (auto s : msg->sigs) { + double value = 0; + s->getValue(e->dat, e->size, &value); + stream << "," << QString::number(value, 'f', s->precision); + } + stream << "\n"; + } + } +} + +} // namespace utils diff --git a/tools/cabana/utils/export.h b/tools/cabana/utils/export.h new file mode 100644 index 0000000000..270906b163 --- /dev/null +++ b/tools/cabana/utils/export.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "tools/cabana/dbc/dbcmanager.h" + +namespace utils { +void exportToCSV(const QString &file_name, std::optional msg_id = std::nullopt); +void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id); +} // namespace utils diff --git a/tools/cabana/util.cc b/tools/cabana/utils/util.cc similarity index 99% rename from tools/cabana/util.cc rename to tools/cabana/utils/util.cc index a17961243f..c89e39993b 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/utils/util.cc @@ -1,4 +1,4 @@ -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" #include #include diff --git a/tools/cabana/util.h b/tools/cabana/utils/util.h similarity index 100% rename from tools/cabana/util.h rename to tools/cabana/utils/util.h diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 67c2c8a29f..b2039e09a4 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -10,7 +10,7 @@ #include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" #include "tools/replay/logreader.h" struct AlertInfo {