cabana: support logging live stream (#27884)

support logging live stream
old-commit-hash: e3a19ff074
beeps
Dean Lee 2 years ago committed by GitHub
parent e46c9899c1
commit 6a98fd2bfd
  1. 4
      tools/cabana/cabana.cc
  2. 14
      tools/cabana/route.cc
  3. 2
      tools/cabana/route.h
  4. 46
      tools/cabana/settings.cc
  5. 7
      tools/cabana/settings.h
  6. 12
      tools/cabana/streams/livestream.cc
  7. 4
      tools/cabana/streams/replaystream.cc
  8. 5
      tools/cabana/streams/replaystream.h

@ -57,13 +57,13 @@ int main(int argc, char *argv[]) {
route = DEMO_ROUTE;
}
auto replay_stream = new ReplayStream(replay_flags, &app);
auto replay_stream = new ReplayStream(&app);
stream.reset(replay_stream);
if (route.isEmpty()) {
if (OpenRouteDialog dlg(nullptr); !dlg.exec()) {
return 0;
}
} else if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"))) {
} else if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
return 0;
}
}

@ -2,7 +2,7 @@
#include <QButtonGroup>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
@ -11,12 +11,13 @@
OpenRouteDialog::OpenRouteDialog(QWidget *parent) : QDialog(parent) {
// TODO: get route list from api.comma.ai
QHBoxLayout *edit_layout = new QHBoxLayout;
edit_layout->addWidget(new QLabel(tr("Route:")));
edit_layout->addWidget(route_edit = new QLineEdit(this));
QGridLayout *edit_layout = new QGridLayout();
edit_layout->addWidget(new QLabel(tr("Route:"), 0, 0));
edit_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
auto file_btn = new QPushButton(tr("Browse..."), this);
edit_layout->addWidget(file_btn);
edit_layout->addWidget(file_btn, 0, 2);
edit_layout->addWidget(no_vipc = new QCheckBox(tr("No video")), 1, 1);
btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
btn_box->button(QDialogButtonBox::Open)->setEnabled(false);
@ -56,7 +57,8 @@ void OpenRouteDialog::loadRoute() {
if (!is_valid_format) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
} else {
failed_to_load = !dynamic_cast<ReplayStream *>(can)->loadRoute(route, data_dir);
uint32_t flags = no_vipc->isChecked() ? REPLAY_FLAG_NO_VIPC : REPLAY_FLAG_NONE;
failed_to_load = !dynamic_cast<ReplayStream *>(can)->loadRoute(route, data_dir, flags);
if (failed_to_load) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));
} else {

@ -1,5 +1,6 @@
#pragma once
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QDialog>
@ -14,6 +15,7 @@ public:
private:
QLineEdit *route_edit;
QCheckBox *no_vipc;
QDialogButtonBox *btn_box;
bool failed_to_load = false;
};

@ -3,8 +3,11 @@
#include <QAbstractButton>
#include <QDialogButtonBox>
#include <QDir>
#include <QFileDialog>
#include <QFormLayout>
#include <QPushButton>
#include <QSettings>
#include <QStandardPaths>
#include "tools/cabana/util.h"
@ -32,6 +35,8 @@ void Settings::save() {
s.setValue("theme", theme);
s.setValue("sparkline_range", sparkline_range);
s.setValue("multiple_lines_bytes", multiple_lines_bytes);
s.setValue("log_livestream", log_livestream);
s.setValue("log_path", log_path);
}
void Settings::load() {
@ -52,13 +57,20 @@ void Settings::load() {
theme = s.value("theme", 0).toInt();
sparkline_range = s.value("sparkline_range", 15).toInt();
multiple_lines_bytes = s.value("multiple_lines_bytes", true).toBool();
log_livestream = s.value("log_livestream", true).toBool();
log_path = s.value("log_path").toString();
if (log_path.isEmpty()) {
log_path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/cabana_live_stream/";
}
}
// SettingsDlg
SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Settings"));
QFormLayout *form_layout = new QFormLayout(this);
QVBoxLayout *main_layout = new QVBoxLayout(this);
QGroupBox *groupbox = new QGroupBox("General");
QFormLayout *form_layout = new QFormLayout(groupbox);
theme = new QComboBox(this);
theme->setToolTip(tr("You may need to restart cabana after changes theme"));
@ -77,7 +89,10 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
cached_minutes->setSingleStep(1);
cached_minutes->setValue(settings.max_cached_minutes);
form_layout->addRow(tr("Max Cached Minutes"), cached_minutes);
main_layout->addWidget(groupbox);
groupbox = new QGroupBox("Chart");
form_layout = new QFormLayout(groupbox);
chart_series_type = new QComboBox(this);
chart_series_type->addItems({tr("Line"), tr("Step Line"), tr("Scatter")});
chart_series_type->setCurrentIndex(settings.chart_series_type);
@ -88,12 +103,31 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
chart_height->setSingleStep(10);
chart_height->setValue(settings.chart_height);
form_layout->addRow(tr("Chart Height"), chart_height);
main_layout->addWidget(groupbox);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
form_layout->addRow(buttonBox);
log_livestream = new QGroupBox(tr("Enable live stream logging"), this);
log_livestream->setCheckable(true);
QHBoxLayout *path_layout = new QHBoxLayout(log_livestream);
path_layout->addWidget(log_path = new QLineEdit(settings.log_path, this));
log_path->setReadOnly(true);
auto browse_btn = new QPushButton(tr("B&rowse..."));
path_layout->addWidget(browse_btn);
main_layout->addWidget(log_livestream);
setFixedWidth(360);
connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton *button) {
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
main_layout->addWidget(buttonBox);
main_layout->addStretch(1);
QObject::connect(browse_btn, &QPushButton::clicked, [this]() {
QString fn = QFileDialog::getExistingDirectory(
this, tr("Log File Location"),
QStandardPaths::writableLocation(QStandardPaths::HomeLocation),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!fn.isEmpty()) {
log_path->setText(fn);
}
});
QObject::connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton *button) {
auto role = buttonBox->buttonRole(button);
if (role == QDialogButtonBox::AcceptRole) {
save();
@ -115,6 +149,8 @@ void SettingsDlg::save() {
settings.max_cached_minutes = cached_minutes->value();
settings.chart_series_type = chart_series_type->currentIndex();
settings.chart_height = chart_height->value();
settings.log_livestream = log_livestream->isChecked();
settings.log_path = log_path->text();
settings.save();
emit settings.changed();
}

@ -1,8 +1,11 @@
#pragma once
#include <QByteArray>
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QGroupBox>
#include <QLineEdit>
#include <QSpinBox>
#define LIGHT_THEME 1
@ -25,6 +28,8 @@ public:
int theme = 0;
int sparkline_range = 15; // 15 seconds
bool multiple_lines_bytes = true;
bool log_livestream = true;
QString log_path;
QString last_dir;
QString last_route_dir;
QByteArray geometry;
@ -48,6 +53,8 @@ public:
QSpinBox *chart_height;
QComboBox *chart_series_type;
QComboBox *theme;
QGroupBox *log_livestream;
QLineEdit *log_path;
};
extern Settings settings;

@ -19,6 +19,13 @@ void LiveStream::streamThread() {
if (!zmq_address.isEmpty()) {
setenv("ZMQ", "1", 1);
}
std::unique_ptr<std::ofstream> fs;
if (settings.log_livestream) {
std::string path = (settings.log_path + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd--hh-mm-ss") + "--0").toStdString();
util::create_directories(path, 0755);
fs.reset(new std::ofstream(path + "/rlog" , std::ios::binary | std::ios::out));
}
std::unique_ptr<Context> context(Context::create());
std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString();
std::unique_ptr<SubSocket> sock(SubSocket::create(context.get(), "can", address));
@ -31,9 +38,12 @@ void LiveStream::streamThread() {
QThread::msleep(50);
continue;
}
if (fs) {
fs->write(msg->getData(), msg->getSize());
}
std::lock_guard lk(lock);
handleEvent(messages.emplace_back(msg).event);
// TODO: write stream to log file to replay it with cabana --data_dir flag.
}
}

@ -1,6 +1,6 @@
#include "tools/cabana/streams/replaystream.h"
ReplayStream::ReplayStream(uint32_t replay_flags, QObject *parent) : replay_flags(replay_flags), AbstractStream(parent, false) {
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent, false) {
QObject::connect(&settings, &Settings::changed, [this]() {
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);
});
@ -25,7 +25,7 @@ void ReplayStream::mergeSegments() {
}
}
bool ReplayStream::loadRoute(const QString &route, const QString &data_dir) {
bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) {
replay.reset(new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this));
replay->setSegmentCacheLimit(settings.max_cached_minutes);
replay->installEventFilter(event_filter, this);

@ -7,9 +7,9 @@ class ReplayStream : public AbstractStream {
Q_OBJECT
public:
ReplayStream(uint32_t replay_flags, QObject *parent);
ReplayStream(QObject *parent);
~ReplayStream();
bool loadRoute(const QString &route, const QString &data_dir);
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); };
inline QString routeName() const override { return replay->route()->name(); }
@ -29,6 +29,5 @@ public:
private:
void mergeSegments();
std::unique_ptr<Replay> replay = nullptr;
uint32_t replay_flags = REPLAY_FLAG_NONE;
std::set<int> processed_segments;
};

Loading…
Cancel
Save