cabana: support logging live stream (#27884)
support logging live stream old-commit-hash: e3a19ff074857876dca3c730f6ddc3c6e191aaf5
This commit is contained in:
@@ -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);
|
||||
|
||||
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);
|
||||
|
||||
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
|
||||
form_layout->addRow(buttonBox);
|
||||
main_layout->addWidget(buttonBox);
|
||||
main_layout->addStretch(1);
|
||||
|
||||
setFixedWidth(360);
|
||||
connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton *button) {
|
||||
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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user