diff --git a/tools/cabana/README.md b/tools/cabana/README.md index cfda056636..53723ef8a6 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -17,6 +17,7 @@ Options: --stream read can messages from live streaming --panda read can messages from panda --panda-serial read can messages from panda with given serial + --socketcan read can messages from given SocketCAN device --zmq the ip address on which to receive zmq messages --data_dir local directory with routes diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index c6b47630ef..a713b612fc 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -9,9 +9,11 @@ base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', if arch == "Darwin": base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') + base_frameworks.append('QtSerialBus') else: base_libs.append('OpenCL') base_libs.append('Qt5Charts') + base_libs.append('Qt5SerialBus') qt_libs = ['qt_util'] + base_libs @@ -27,7 +29,7 @@ assets_src = "assets/assets.qrc" cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"])) -cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', +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', '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) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 49b8fcf6ca..7d3e6ab99f 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -7,6 +7,7 @@ #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#include "tools/cabana/streams/socketcanstream.h" int main(int argc, char *argv[]) { QCoreApplication::setApplicationName("Cabana"); @@ -28,6 +29,9 @@ int main(int argc, char *argv[]) { cmd_parser.addOption({"stream", "read can messages from live streaming"}); cmd_parser.addOption({"panda", "read can messages from panda"}); cmd_parser.addOption({"panda-serial", "read can messages from panda with given serial", "panda-serial"}); + if (SocketCanStream::available()) { + cmd_parser.addOption({"socketcan", "read can messages from given SocketCAN device", "socketcan"}); + } cmd_parser.addOption({"zmq", "the ip address on which to receive zmq messages", "zmq"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); cmd_parser.addOption({"no-vipc", "do not output video"}); @@ -50,6 +54,10 @@ int main(int argc, char *argv[]) { qWarning() << e.what(); return 0; } + } else if (cmd_parser.isSet("socketcan")) { + SocketCanStreamConfig config = {}; + config.device = cmd_parser.value("socketcan"); + stream = new SocketCanStream(&app, config); } else { uint32_t replay_flags = REPLAY_FLAG_NONE; if (cmd_parser.isSet("ecam")) { diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 21885d742b..5cafa58cb8 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -34,6 +34,12 @@ struct CanEvent { uint8_t dat[]; }; +struct BusConfig { + int can_speed_kbps = 500; + int data_speed_kbps = 2000; + bool can_fd = false; +}; + class AbstractStream : public QObject { Q_OBJECT diff --git a/tools/cabana/streams/pandastream.h b/tools/cabana/streams/pandastream.h index f726c5cfb6..49e1836c2d 100644 --- a/tools/cabana/streams/pandastream.h +++ b/tools/cabana/streams/pandastream.h @@ -10,12 +10,6 @@ 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; - bool can_fd = false; -}; - struct PandaStreamConfig { QString serial = ""; std::vector bus_config; diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc new file mode 100644 index 0000000000..d5f9d115f2 --- /dev/null +++ b/tools/cabana/streams/socketcanstream.cc @@ -0,0 +1,115 @@ +#include "tools/cabana/streams/socketcanstream.h" +#include "socketcanstream.h" + +#include +#include +#include + +SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) { + if (!available()) { + throw std::runtime_error("SocketCAN plugin not available"); + } + + qDebug() << "Connecting to SocketCAN device" << config.device; + if (!connect()) { + throw std::runtime_error("Failed to connect to SocketCAN device"); + } +} + +bool SocketCanStream::available() { + return QCanBus::instance()->plugins().contains("socketcan"); +} + +bool SocketCanStream::connect() { + // Connecting might generate some warnings about missing socketcan/libsocketcan libraries + // These are expected and can be ignored, we don't need the advanced features of libsocketcan + QString errorString; + device.reset(QCanBus::instance()->createDevice("socketcan", config.device, &errorString)); + + if (!device) { + qDebug() << "Failed to create SocketCAN device" << errorString; + return false; + } + + if (!device->connectDevice()) { + qDebug() << "Failed to connect to device"; + return false; + } + + return true; +} + +void SocketCanStream::streamThread() { + while (!QThread::currentThread()->isInterruptionRequested()) { + QThread::msleep(1); + + auto frames = device->readAllFrames(); + if (frames.size() == 0) continue; + + MessageBuilder msg; + auto evt = msg.initEvent(); + auto canData = evt.initCan(frames.size()); + + + for (uint i = 0; i < frames.size(); i++) { + if (!frames[i].isValid()) continue; + + canData[i].setAddress(frames[i].frameId()); + canData[i].setSrc(0); + + auto payload = frames[i].payload(); + canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size())); + } + + auto bytes = msg.toBytes(); + handleEvent((const char*)bytes.begin(), bytes.size()); + } +} + +AbstractOpenStreamWidget *SocketCanStream::widget(AbstractStream **stream) { + return new OpenSocketCanWidget(stream); +} + +OpenSocketCanWidget::OpenSocketCanWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) { + 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(); }); + + // Populate devices + refreshDevices(); +} + +void OpenSocketCanWidget::refreshDevices() { + device_edit->clear(); + for (auto device : QCanBus::instance()->availableDevices(QStringLiteral("socketcan"))) { + device_edit->addItem(device.name()); + } +} + + +bool OpenSocketCanWidget::open() { + try { + *stream = new SocketCanStream(qApp, config); + } catch (std::exception &e) { + QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to SocketCAN device: '%1'").arg(e.what())); + return false; + } + return true; +} diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h new file mode 100644 index 0000000000..9d34aaebac --- /dev/null +++ b/tools/cabana/streams/socketcanstream.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include "tools/cabana/streams/livestream.h" + +struct SocketCanStreamConfig { + QString device = ""; // TODO: support multiple devices/buses at once +}; + +class SocketCanStream : public LiveStream { + Q_OBJECT +public: + SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); + static AbstractOpenStreamWidget *widget(AbstractStream **stream); + + static bool available(); + + inline QString routeName() const override { + return QString("Live Streaming From Socket CAN %1").arg(config.device); + } + +protected: + void streamThread() override; + bool connect(); + + SocketCanStreamConfig config = {}; + std::unique_ptr device; +}; + +class OpenSocketCanWidget : public AbstractOpenStreamWidget { + Q_OBJECT + +public: + OpenSocketCanWidget(AbstractStream **stream); + bool open() override; + QString title() override { return tr("&SocketCAN"); } + +private: + void refreshDevices(); + + QComboBox *device_edit; + SocketCanStreamConfig config = {}; +}; diff --git a/tools/cabana/streamselector.cc b/tools/cabana/streamselector.cc index 6da44ecd1a..719ba72920 100644 --- a/tools/cabana/streamselector.cc +++ b/tools/cabana/streamselector.cc @@ -6,9 +6,11 @@ #include #include +#include "streams/socketcanstream.h" #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#include "tools/cabana/streams/socketcanstream.h" StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Open stream")); @@ -40,6 +42,9 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial addStreamWidget(ReplayStream::widget(stream)); addStreamWidget(PandaStream::widget(stream)); + if (SocketCanStream::available()) { + addStreamWidget(SocketCanStream::widget(stream)); + } addStreamWidget(DeviceStream::widget(stream)); QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 6214452870..78a01f2b75 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -73,6 +73,7 @@ function install_ubuntu_common_requirements() { libqt5sql5-sqlite \ libqt5svg5-dev \ libqt5charts5-dev \ + libqt5serialbus5-dev \ libqt5x11extras5-dev \ libreadline-dev \ libdw1 \