mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-09-10 10:13:40 +08:00
cabana: move stream open widgets into streamselector (#38715)
This commit is contained in:
@@ -101,16 +101,6 @@ private:
|
||||
std::unordered_map<MessageId, std::vector<uint8_t>> masks_;
|
||||
};
|
||||
|
||||
class AbstractOpenStreamWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AbstractOpenStreamWidget(QWidget *parent = nullptr) : QWidget(parent) {}
|
||||
virtual AbstractStream *open() = 0;
|
||||
|
||||
signals:
|
||||
void enableOpenButton(bool);
|
||||
};
|
||||
|
||||
class DummyStream : public AbstractStream {
|
||||
public:
|
||||
std::string routeName() const override { return "No Stream"; }
|
||||
|
||||
@@ -14,12 +14,8 @@
|
||||
|
||||
#include "openpilot/cereal/services.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QFormLayout>
|
||||
#include <QCoreApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QRadioButton>
|
||||
|
||||
#include "tools/cabana/utils/util.h"
|
||||
|
||||
// DeviceStream
|
||||
|
||||
@@ -118,31 +114,3 @@ void DeviceStream::streamThread() {
|
||||
handleEvent(kj::ArrayPtr<capnp::word>((capnp::word*)msg->getData(), msg->getSize() / sizeof(capnp::word)));
|
||||
}
|
||||
}
|
||||
|
||||
// OpenDeviceWidget
|
||||
|
||||
OpenDeviceWidget::OpenDeviceWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QRadioButton *msgq = new QRadioButton(tr("MSGQ"));
|
||||
QRadioButton *zmq = new QRadioButton(tr("ZMQ"));
|
||||
ip_address = new QLineEdit(this);
|
||||
ip_address->setPlaceholderText(tr("Enter device Ip Address"));
|
||||
ip_address->setValidator(new IpAddressValidator(this));
|
||||
|
||||
group = new QButtonGroup(this);
|
||||
group->addButton(msgq, 0);
|
||||
group->addButton(zmq, 1);
|
||||
|
||||
QFormLayout *form_layout = new QFormLayout(this);
|
||||
form_layout->addRow(msgq);
|
||||
form_layout->addRow(zmq, ip_address);
|
||||
QObject::connect(group, qOverload<QAbstractButton *, bool>(&QButtonGroup::buttonToggled), [=](QAbstractButton *button, bool checked) {
|
||||
ip_address->setEnabled(button == zmq && checked);
|
||||
});
|
||||
zmq->setChecked(true);
|
||||
}
|
||||
|
||||
AbstractStream *OpenDeviceWidget::open() {
|
||||
QString ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text();
|
||||
bool msgq = group->checkedId() == 0;
|
||||
return new DeviceStream(msgq ? "" : ip);
|
||||
}
|
||||
|
||||
@@ -19,15 +19,3 @@ protected:
|
||||
pid_t bridge_pid = -1;
|
||||
const QString zmq_address;
|
||||
};
|
||||
|
||||
class OpenDeviceWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenDeviceWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
QLineEdit *ip_address;
|
||||
QButtonGroup *group;
|
||||
};
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
PandaStream::PandaStream(PandaStreamConfig config_) : config(config_) {
|
||||
if (!connect()) {
|
||||
throw std::runtime_error("Failed to connect to panda");
|
||||
@@ -77,113 +71,3 @@ void PandaStream::streamThread() {
|
||||
panda->send_heartbeat(false);
|
||||
}
|
||||
}
|
||||
|
||||
// OpenPandaWidget
|
||||
|
||||
OpenPandaWidget::OpenPandaWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
form_layout = new QFormLayout(this);
|
||||
if (can && dynamic_cast<PandaStream *>(can) != nullptr) {
|
||||
form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(QString::fromStdString(can->routeName()))));
|
||||
form_layout->addWidget(new QLabel("Close the current connection via [File menu -> Close Stream] before connecting to another Panda."));
|
||||
QTimer::singleShot(0, [this]() { emit enableOpenButton(false); });
|
||||
return;
|
||||
}
|
||||
|
||||
QHBoxLayout *serial_layout = new QHBoxLayout();
|
||||
serial_layout->addWidget(serial_edit = new QComboBox());
|
||||
|
||||
QPushButton *refresh = new QPushButton(tr("Refresh"));
|
||||
refresh->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
serial_layout->addWidget(refresh);
|
||||
form_layout->addRow(tr("Serial"), serial_layout);
|
||||
|
||||
QObject::connect(refresh, &QPushButton::clicked, this, &OpenPandaWidget::refreshSerials);
|
||||
QObject::connect(serial_edit, &QComboBox::currentTextChanged, this, &OpenPandaWidget::buildConfigForm);
|
||||
|
||||
// Populate serials
|
||||
refreshSerials();
|
||||
buildConfigForm();
|
||||
}
|
||||
|
||||
void OpenPandaWidget::refreshSerials() {
|
||||
serial_edit->clear();
|
||||
for (auto serial : Panda::list()) {
|
||||
serial_edit->addItem(QString::fromStdString(serial));
|
||||
}
|
||||
}
|
||||
|
||||
void OpenPandaWidget::buildConfigForm() {
|
||||
for (int i = form_layout->rowCount() - 1; i > 0; --i) {
|
||||
form_layout->removeRow(i);
|
||||
}
|
||||
|
||||
QString serial = serial_edit->currentText();
|
||||
bool has_fd = false;
|
||||
bool has_panda = !serial.isEmpty();
|
||||
if (has_panda) {
|
||||
try {
|
||||
Panda panda(serial.toStdString());
|
||||
has_fd = (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA) || (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA_V2);
|
||||
} catch (const std::exception& e) {
|
||||
fprintf(stderr, "failed to open panda %s\n", serial.toUtf8().constData());
|
||||
has_panda = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_panda) {
|
||||
config.serial = serial.toStdString();
|
||||
config.bus_config.resize(3);
|
||||
for (int i = 0; i < config.bus_config.size(); i++) {
|
||||
QHBoxLayout *bus_layout = new QHBoxLayout;
|
||||
|
||||
// CAN Speed
|
||||
bus_layout->addWidget(new QLabel(tr("CAN Speed (kbps):")));
|
||||
QComboBox *can_speed = new QComboBox;
|
||||
for (int j = 0; j < std::size(speeds); j++) {
|
||||
can_speed->addItem(QString::number(speeds[j]));
|
||||
|
||||
if (data_speeds[j] == config.bus_config[i].can_speed_kbps) {
|
||||
can_speed->setCurrentIndex(j);
|
||||
}
|
||||
}
|
||||
QObject::connect(can_speed, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index) {config.bus_config[i].can_speed_kbps = speeds[index];});
|
||||
bus_layout->addWidget(can_speed);
|
||||
|
||||
// CAN-FD Speed
|
||||
if (has_fd) {
|
||||
QCheckBox *enable_fd = new QCheckBox("CAN-FD");
|
||||
bus_layout->addWidget(enable_fd);
|
||||
bus_layout->addWidget(new QLabel(tr("Data Speed (kbps):")));
|
||||
QComboBox *data_speed = new QComboBox;
|
||||
for (int j = 0; j < std::size(data_speeds); j++) {
|
||||
data_speed->addItem(QString::number(data_speeds[j]));
|
||||
|
||||
if (data_speeds[j] == config.bus_config[i].data_speed_kbps) {
|
||||
data_speed->setCurrentIndex(j);
|
||||
}
|
||||
}
|
||||
|
||||
data_speed->setEnabled(false);
|
||||
bus_layout->addWidget(data_speed);
|
||||
|
||||
QObject::connect(data_speed, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index) {config.bus_config[i].data_speed_kbps = data_speeds[index];});
|
||||
QObject::connect(enable_fd, &QCheckBox::stateChanged, data_speed, &QComboBox::setEnabled);
|
||||
QObject::connect(enable_fd, &QCheckBox::stateChanged, [=](int state) {config.bus_config[i].can_fd = (bool)state;});
|
||||
}
|
||||
|
||||
form_layout->addRow(tr("Bus %1:").arg(i), bus_layout);
|
||||
}
|
||||
} else {
|
||||
config.serial = "";
|
||||
form_layout->addWidget(new QLabel(tr("No panda found")));
|
||||
}
|
||||
}
|
||||
|
||||
AbstractStream *OpenPandaWidget::open() {
|
||||
try {
|
||||
return new PandaStream(config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,9 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFormLayout>
|
||||
|
||||
#include "tools/cabana/streams/livestream.h"
|
||||
#include "tools/cabana/panda.h"
|
||||
|
||||
const uint32_t speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U};
|
||||
const uint32_t data_speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U, 2000U, 5000U};
|
||||
|
||||
struct BusConfig {
|
||||
int can_speed_kbps = 500;
|
||||
int data_speed_kbps = 2000;
|
||||
@@ -38,19 +32,3 @@ protected:
|
||||
std::unique_ptr<Panda> panda;
|
||||
PandaStreamConfig config = {};
|
||||
};
|
||||
|
||||
class OpenPandaWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenPandaWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
void refreshSerials();
|
||||
void buildConfigForm();
|
||||
|
||||
QComboBox *serial_edit;
|
||||
QFormLayout *form_layout;
|
||||
PandaStreamConfig config = {};
|
||||
};
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <QLabel>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "common/timing.h"
|
||||
#include "common/util.h"
|
||||
#include "tools/cabana/routesdialog.h"
|
||||
|
||||
ReplayStream::ReplayStream() {
|
||||
unsetenv("ZMQ");
|
||||
@@ -116,64 +109,3 @@ void ReplayStream::pause(bool pause) {
|
||||
replay->pause(pause);
|
||||
pause ? paused() : resume();
|
||||
}
|
||||
|
||||
|
||||
// OpenReplayWidget
|
||||
|
||||
OpenReplayWidget::OpenReplayWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QGridLayout *grid_layout = new QGridLayout(this);
|
||||
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
|
||||
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
|
||||
route_edit->setPlaceholderText(tr("Enter route name or browse for local/remote route"));
|
||||
auto browse_remote_btn = new QPushButton(tr("Remote route..."), this);
|
||||
grid_layout->addWidget(browse_remote_btn, 0, 2);
|
||||
auto browse_local_btn = new QPushButton(tr("Local route..."), this);
|
||||
grid_layout->addWidget(browse_local_btn, 0, 3);
|
||||
|
||||
QHBoxLayout *camera_layout = new QHBoxLayout();
|
||||
for (auto c : {tr("Road camera"), tr("Driver camera"), tr("Wide road camera")})
|
||||
camera_layout->addWidget(cameras.emplace_back(new QCheckBox(c, this)));
|
||||
cameras[0]->setChecked(true);
|
||||
camera_layout->addStretch(1);
|
||||
grid_layout->addItem(camera_layout, 1, 1);
|
||||
|
||||
setMinimumWidth(550);
|
||||
QObject::connect(browse_local_btn, &QPushButton::clicked, [=]() {
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), QString::fromStdString(settings.last_route_dir));
|
||||
if (!dir.isEmpty()) {
|
||||
route_edit->setText(dir);
|
||||
settings.last_route_dir = std::filesystem::absolute(dir.toStdString()).parent_path().string();
|
||||
}
|
||||
});
|
||||
QObject::connect(browse_remote_btn, &QPushButton::clicked, [this]() {
|
||||
RoutesDialog route_dlg(this);
|
||||
if (route_dlg.exec()) {
|
||||
route_edit->setText(QString::fromStdString(route_dlg.route()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AbstractStream *OpenReplayWidget::open() {
|
||||
QString route = route_edit->text();
|
||||
QString data_dir;
|
||||
if (int idx = route.lastIndexOf('/'); idx != -1 && util::file_exists(route.toStdString())) {
|
||||
data_dir = route.mid(0, idx + 1);
|
||||
route = route.mid(idx + 1);
|
||||
}
|
||||
|
||||
bool is_valid_format = Route::parseRoute(route.toStdString()).str.size() > 0;
|
||||
if (!is_valid_format) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
|
||||
} else {
|
||||
auto replay_stream = std::make_unique<ReplayStream>();
|
||||
uint32_t flags = REPLAY_FLAG_NONE;
|
||||
if (cameras[1]->isChecked()) flags |= REPLAY_FLAG_CABIN_CAMERA;
|
||||
if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_WIDE_ROAD;
|
||||
if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC;
|
||||
|
||||
if (replay_stream->loadRoute(route.toStdString(), data_dir.toStdString(), flags)) {
|
||||
return replay_stream.release();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "common/prefix.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
@@ -43,15 +41,3 @@ private:
|
||||
std::set<int> processed_segments;
|
||||
std::unique_ptr<OpenpilotPrefix> op_prefix;
|
||||
};
|
||||
|
||||
class OpenReplayWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenReplayWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
QLineEdit *route_edit;
|
||||
std::vector<QCheckBox *> cameras;
|
||||
};
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
SocketCanStream::SocketCanStream(SocketCanStreamConfig config_) : config(config_) {
|
||||
if (!available()) {
|
||||
@@ -98,51 +91,3 @@ void SocketCanStream::streamThread() {
|
||||
handleEvent(capnp::messageToFlatArray(msg));
|
||||
}
|
||||
}
|
||||
|
||||
OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addStretch(1);
|
||||
|
||||
QFormLayout *form_layout = new QFormLayout();
|
||||
|
||||
QHBoxLayout *device_layout = new QHBoxLayout();
|
||||
device_edit = new QComboBox();
|
||||
device_edit->setFixedWidth(300);
|
||||
device_layout->addWidget(device_edit);
|
||||
|
||||
QPushButton *refresh = new QPushButton(tr("Refresh"));
|
||||
refresh->setFixedWidth(100);
|
||||
device_layout->addWidget(refresh);
|
||||
form_layout->addRow(tr("Device"), device_layout);
|
||||
main_layout->addLayout(form_layout);
|
||||
|
||||
main_layout->addStretch(1);
|
||||
|
||||
QObject::connect(refresh, &QPushButton::clicked, this, &OpenSocketCanWidget::refreshDevices);
|
||||
QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText().toStdString(); });
|
||||
|
||||
// Populate devices
|
||||
refreshDevices();
|
||||
}
|
||||
|
||||
void OpenSocketCanWidget::refreshDevices() {
|
||||
device_edit->clear();
|
||||
// Scan /sys/class/net/ for CAN interfaces (type 280 = ARPHRD_CAN)
|
||||
std::error_code ec;
|
||||
for (const auto &entry : std::filesystem::directory_iterator("/sys/class/net", ec)) {
|
||||
std::ifstream type_file(entry.path() / "type");
|
||||
int type = 0;
|
||||
if (type_file >> type && type == 280) {
|
||||
device_edit->addItem(QString::fromStdString(entry.path().filename().string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbstractStream *OpenSocketCanWidget::open() {
|
||||
try {
|
||||
return new SocketCanStream(config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to SocketCAN device: '%1'").arg(e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include "tools/cabana/streams/livestream.h"
|
||||
|
||||
struct SocketCanStreamConfig {
|
||||
@@ -25,17 +23,3 @@ protected:
|
||||
SocketCanStreamConfig config = {};
|
||||
int sock_fd = -1;
|
||||
};
|
||||
|
||||
class OpenSocketCanWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenSocketCanWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
void refreshDevices();
|
||||
|
||||
QComboBox *device_edit;
|
||||
SocketCanStreamConfig config = {};
|
||||
};
|
||||
|
||||
@@ -1,18 +1,275 @@
|
||||
#include "tools/cabana/streamselector.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QTimer>
|
||||
|
||||
#include "tools/cabana/streams/devicestream.h"
|
||||
#include "tools/cabana/streams/pandastream.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
#include "tools/cabana/routesdialog.h"
|
||||
|
||||
// OpenReplayWidget
|
||||
|
||||
OpenReplayWidget::OpenReplayWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QGridLayout *grid_layout = new QGridLayout(this);
|
||||
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
|
||||
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
|
||||
route_edit->setPlaceholderText(tr("Enter route name or browse for local/remote route"));
|
||||
auto browse_remote_btn = new QPushButton(tr("Remote route..."), this);
|
||||
grid_layout->addWidget(browse_remote_btn, 0, 2);
|
||||
auto browse_local_btn = new QPushButton(tr("Local route..."), this);
|
||||
grid_layout->addWidget(browse_local_btn, 0, 3);
|
||||
|
||||
QHBoxLayout *camera_layout = new QHBoxLayout();
|
||||
for (auto c : {tr("Road camera"), tr("Driver camera"), tr("Wide road camera")})
|
||||
camera_layout->addWidget(cameras.emplace_back(new QCheckBox(c, this)));
|
||||
cameras[0]->setChecked(true);
|
||||
camera_layout->addStretch(1);
|
||||
grid_layout->addItem(camera_layout, 1, 1);
|
||||
|
||||
setMinimumWidth(550);
|
||||
QObject::connect(browse_local_btn, &QPushButton::clicked, [=]() {
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), QString::fromStdString(settings.last_route_dir));
|
||||
if (!dir.isEmpty()) {
|
||||
route_edit->setText(dir);
|
||||
settings.last_route_dir = std::filesystem::absolute(dir.toStdString()).parent_path().string();
|
||||
}
|
||||
});
|
||||
QObject::connect(browse_remote_btn, &QPushButton::clicked, [this]() {
|
||||
RoutesDialog route_dlg(this);
|
||||
if (route_dlg.exec()) {
|
||||
route_edit->setText(QString::fromStdString(route_dlg.route()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AbstractStream *OpenReplayWidget::open() {
|
||||
QString route = route_edit->text();
|
||||
QString data_dir;
|
||||
if (int idx = route.lastIndexOf('/'); idx != -1 && util::file_exists(route.toStdString())) {
|
||||
data_dir = route.mid(0, idx + 1);
|
||||
route = route.mid(idx + 1);
|
||||
}
|
||||
|
||||
bool is_valid_format = Route::parseRoute(route.toStdString()).str.size() > 0;
|
||||
if (!is_valid_format) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
|
||||
} else {
|
||||
auto replay_stream = std::make_unique<ReplayStream>();
|
||||
uint32_t flags = REPLAY_FLAG_NONE;
|
||||
if (cameras[1]->isChecked()) flags |= REPLAY_FLAG_CABIN_CAMERA;
|
||||
if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_WIDE_ROAD;
|
||||
if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC;
|
||||
|
||||
if (replay_stream->loadRoute(route.toStdString(), data_dir.toStdString(), flags)) {
|
||||
return replay_stream.release();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// OpenPandaWidget
|
||||
|
||||
static const uint32_t speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U};
|
||||
static const uint32_t data_speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U, 2000U, 5000U};
|
||||
|
||||
OpenPandaWidget::OpenPandaWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
form_layout = new QFormLayout(this);
|
||||
if (can && dynamic_cast<PandaStream *>(can) != nullptr) {
|
||||
form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(QString::fromStdString(can->routeName()))));
|
||||
form_layout->addWidget(new QLabel("Close the current connection via [File menu -> Close Stream] before connecting to another Panda."));
|
||||
QTimer::singleShot(0, [this]() { emit enableOpenButton(false); });
|
||||
return;
|
||||
}
|
||||
|
||||
QHBoxLayout *serial_layout = new QHBoxLayout();
|
||||
serial_layout->addWidget(serial_edit = new QComboBox());
|
||||
|
||||
QPushButton *refresh = new QPushButton(tr("Refresh"));
|
||||
refresh->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
serial_layout->addWidget(refresh);
|
||||
form_layout->addRow(tr("Serial"), serial_layout);
|
||||
|
||||
QObject::connect(refresh, &QPushButton::clicked, this, &OpenPandaWidget::refreshSerials);
|
||||
QObject::connect(serial_edit, &QComboBox::currentTextChanged, this, &OpenPandaWidget::buildConfigForm);
|
||||
|
||||
// Populate serials
|
||||
refreshSerials();
|
||||
buildConfigForm();
|
||||
}
|
||||
|
||||
void OpenPandaWidget::refreshSerials() {
|
||||
serial_edit->clear();
|
||||
for (auto serial : Panda::list()) {
|
||||
serial_edit->addItem(QString::fromStdString(serial));
|
||||
}
|
||||
}
|
||||
|
||||
void OpenPandaWidget::buildConfigForm() {
|
||||
for (int i = form_layout->rowCount() - 1; i > 0; --i) {
|
||||
form_layout->removeRow(i);
|
||||
}
|
||||
|
||||
QString serial = serial_edit->currentText();
|
||||
bool has_fd = false;
|
||||
bool has_panda = !serial.isEmpty();
|
||||
if (has_panda) {
|
||||
try {
|
||||
Panda panda(serial.toStdString());
|
||||
has_fd = (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA) || (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA_V2);
|
||||
} catch (const std::exception& e) {
|
||||
fprintf(stderr, "failed to open panda %s\n", serial.toUtf8().constData());
|
||||
has_panda = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_panda) {
|
||||
config.serial = serial.toStdString();
|
||||
config.bus_config.resize(3);
|
||||
for (int i = 0; i < config.bus_config.size(); i++) {
|
||||
QHBoxLayout *bus_layout = new QHBoxLayout;
|
||||
|
||||
// CAN Speed
|
||||
bus_layout->addWidget(new QLabel(tr("CAN Speed (kbps):")));
|
||||
QComboBox *can_speed = new QComboBox;
|
||||
for (int j = 0; j < std::size(speeds); j++) {
|
||||
can_speed->addItem(QString::number(speeds[j]));
|
||||
|
||||
if (data_speeds[j] == config.bus_config[i].can_speed_kbps) {
|
||||
can_speed->setCurrentIndex(j);
|
||||
}
|
||||
}
|
||||
QObject::connect(can_speed, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index) {config.bus_config[i].can_speed_kbps = speeds[index];});
|
||||
bus_layout->addWidget(can_speed);
|
||||
|
||||
// CAN-FD Speed
|
||||
if (has_fd) {
|
||||
QCheckBox *enable_fd = new QCheckBox("CAN-FD");
|
||||
bus_layout->addWidget(enable_fd);
|
||||
bus_layout->addWidget(new QLabel(tr("Data Speed (kbps):")));
|
||||
QComboBox *data_speed = new QComboBox;
|
||||
for (int j = 0; j < std::size(data_speeds); j++) {
|
||||
data_speed->addItem(QString::number(data_speeds[j]));
|
||||
|
||||
if (data_speeds[j] == config.bus_config[i].data_speed_kbps) {
|
||||
data_speed->setCurrentIndex(j);
|
||||
}
|
||||
}
|
||||
|
||||
data_speed->setEnabled(false);
|
||||
bus_layout->addWidget(data_speed);
|
||||
|
||||
QObject::connect(data_speed, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index) {config.bus_config[i].data_speed_kbps = data_speeds[index];});
|
||||
QObject::connect(enable_fd, &QCheckBox::stateChanged, data_speed, &QComboBox::setEnabled);
|
||||
QObject::connect(enable_fd, &QCheckBox::stateChanged, [=](int state) {config.bus_config[i].can_fd = (bool)state;});
|
||||
}
|
||||
|
||||
form_layout->addRow(tr("Bus %1:").arg(i), bus_layout);
|
||||
}
|
||||
} else {
|
||||
config.serial = "";
|
||||
form_layout->addWidget(new QLabel(tr("No panda found")));
|
||||
}
|
||||
}
|
||||
|
||||
AbstractStream *OpenPandaWidget::open() {
|
||||
try {
|
||||
return new PandaStream(config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// OpenDeviceWidget
|
||||
|
||||
OpenDeviceWidget::OpenDeviceWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QRadioButton *msgq = new QRadioButton(tr("MSGQ"));
|
||||
QRadioButton *zmq = new QRadioButton(tr("ZMQ"));
|
||||
ip_address = new QLineEdit(this);
|
||||
ip_address->setPlaceholderText(tr("Enter device Ip Address"));
|
||||
ip_address->setValidator(new IpAddressValidator(this));
|
||||
|
||||
group = new QButtonGroup(this);
|
||||
group->addButton(msgq, 0);
|
||||
group->addButton(zmq, 1);
|
||||
|
||||
QFormLayout *form_layout = new QFormLayout(this);
|
||||
form_layout->addRow(msgq);
|
||||
form_layout->addRow(zmq, ip_address);
|
||||
QObject::connect(group, qOverload<QAbstractButton *, bool>(&QButtonGroup::buttonToggled), [=](QAbstractButton *button, bool checked) {
|
||||
ip_address->setEnabled(button == zmq && checked);
|
||||
});
|
||||
zmq->setChecked(true);
|
||||
}
|
||||
|
||||
AbstractStream *OpenDeviceWidget::open() {
|
||||
QString ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text();
|
||||
bool msgq = group->checkedId() == 0;
|
||||
return new DeviceStream(msgq ? "" : ip);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#include "tools/cabana/streams/socketcanstream.h"
|
||||
// OpenSocketCanWidget
|
||||
|
||||
OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addStretch(1);
|
||||
|
||||
QFormLayout *form_layout = new QFormLayout();
|
||||
|
||||
QHBoxLayout *device_layout = new QHBoxLayout();
|
||||
device_edit = new QComboBox();
|
||||
device_edit->setFixedWidth(300);
|
||||
device_layout->addWidget(device_edit);
|
||||
|
||||
QPushButton *refresh = new QPushButton(tr("Refresh"));
|
||||
refresh->setFixedWidth(100);
|
||||
device_layout->addWidget(refresh);
|
||||
form_layout->addRow(tr("Device"), device_layout);
|
||||
main_layout->addLayout(form_layout);
|
||||
|
||||
main_layout->addStretch(1);
|
||||
|
||||
QObject::connect(refresh, &QPushButton::clicked, this, &OpenSocketCanWidget::refreshDevices);
|
||||
QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText().toStdString(); });
|
||||
|
||||
// Populate devices
|
||||
refreshDevices();
|
||||
}
|
||||
|
||||
void OpenSocketCanWidget::refreshDevices() {
|
||||
device_edit->clear();
|
||||
// Scan /sys/class/net/ for CAN interfaces (type 280 = ARPHRD_CAN)
|
||||
std::error_code ec;
|
||||
for (const auto &entry : std::filesystem::directory_iterator("/sys/class/net", ec)) {
|
||||
std::ifstream type_file(entry.path() / "type");
|
||||
int type = 0;
|
||||
if (type_file >> type && type == 280) {
|
||||
device_edit->addItem(QString::fromStdString(entry.path().filename().string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbstractStream *OpenSocketCanWidget::open() {
|
||||
try {
|
||||
return new SocketCanStream(config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to SocketCAN device: '%1'").arg(e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// StreamSelector
|
||||
|
||||
StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Open stream"));
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
@@ -1,11 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QTabWidget>
|
||||
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
#include "tools/cabana/streams/pandastream.h"
|
||||
#ifdef __linux__
|
||||
#include "tools/cabana/streams/socketcanstream.h"
|
||||
#endif
|
||||
|
||||
class AbstractOpenStreamWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AbstractOpenStreamWidget(QWidget *parent = nullptr) : QWidget(parent) {}
|
||||
virtual AbstractStream *open() = 0;
|
||||
|
||||
signals:
|
||||
void enableOpenButton(bool);
|
||||
};
|
||||
|
||||
class OpenReplayWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenReplayWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
QLineEdit *route_edit;
|
||||
std::vector<QCheckBox *> cameras;
|
||||
};
|
||||
|
||||
class OpenPandaWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenPandaWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
void refreshSerials();
|
||||
void buildConfigForm();
|
||||
|
||||
QComboBox *serial_edit;
|
||||
QFormLayout *form_layout;
|
||||
PandaStreamConfig config = {};
|
||||
};
|
||||
|
||||
class OpenDeviceWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenDeviceWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
QLineEdit *ip_address;
|
||||
QButtonGroup *group;
|
||||
};
|
||||
|
||||
#ifdef __linux__
|
||||
// no Q_OBJECT: moc does not define __linux__ and would otherwise skip this class
|
||||
class OpenSocketCanWidget : public AbstractOpenStreamWidget {
|
||||
public:
|
||||
OpenSocketCanWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
void refreshDevices();
|
||||
|
||||
QComboBox *device_edit;
|
||||
SocketCanStreamConfig config = {};
|
||||
};
|
||||
#endif
|
||||
|
||||
class StreamSelector : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
Reference in New Issue
Block a user