commit
2b3d1f2cfc
118 changed files with 2386 additions and 416 deletions
@ -1 +1 @@ |
|||||||
Subproject commit d4cf8728e2fa2d87d90098efa7ddeaf8f98a03db |
Subproject commit b29717c4c328d5cf34d46f682f25267150f82637 |
@ -1 +1 @@ |
|||||||
Subproject commit c8bc1fa01be9f22592efb991ee52d3d965d21968 |
Subproject commit e1049cde0a68f7d4a70b1ebd76befdc0e163ad55 |
@ -1 +1 @@ |
|||||||
Subproject commit eaac172af9cb342204e69ec52339cdf3c6a8ac4e |
Subproject commit c35e8139bf9e9d87b9efb6764ab7e65983e8d33e |
@ -1 +1 @@ |
|||||||
Subproject commit 1910db8d4c3f932fe85b186fba1d24795cb2b742 |
Subproject commit 1303af2db29a72eee180b10c6097fa5b19c29207 |
@ -1,3 +1,3 @@ |
|||||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||||
oid sha256:50c7fc8565ac69a4b9a0de122e961326820e78bf13659255a89d0ed04be030d5 |
oid sha256:c4d37af666344af6bb218e0b939b1152ad3784c15ac79e37bcf0124643c8286a |
||||||
size 95167481 |
size 58539563 |
||||||
|
@ -0,0 +1,49 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import json |
||||||
|
import time |
||||||
|
import unittest |
||||||
|
import subprocess |
||||||
|
|
||||||
|
import cereal.messaging as messaging |
||||||
|
from system.hardware import TICI |
||||||
|
from selfdrive.manager.process_config import managed_processes |
||||||
|
|
||||||
|
|
||||||
|
class TestRawgpsd(unittest.TestCase): |
||||||
|
@classmethod |
||||||
|
def setUpClass(cls): |
||||||
|
if not TICI: |
||||||
|
raise unittest.SkipTest |
||||||
|
|
||||||
|
def tearDown(self): |
||||||
|
managed_processes['rawgpsd'].stop() |
||||||
|
|
||||||
|
def test_startup_time(self): |
||||||
|
for _ in range(5): |
||||||
|
sm = messaging.SubMaster(['qcomGnss']) |
||||||
|
managed_processes['rawgpsd'].start() |
||||||
|
|
||||||
|
start_time = time.monotonic() |
||||||
|
for __ in range(10): |
||||||
|
sm.update(1 * 1000) |
||||||
|
if sm.updated['qcomGnss']: |
||||||
|
break |
||||||
|
assert sm.rcv_frame['qcomGnss'] > 0, "rawgpsd didn't start outputting messages in time" |
||||||
|
|
||||||
|
et = time.monotonic() - start_time |
||||||
|
assert et < 5, f"rawgpsd took {et:.1f}s to start" |
||||||
|
managed_processes['rawgpsd'].stop() |
||||||
|
|
||||||
|
def test_turns_off_gnss(self): |
||||||
|
for s in (0.1, 0.5, 1, 5): |
||||||
|
managed_processes['rawgpsd'].start() |
||||||
|
time.sleep(s) |
||||||
|
managed_processes['rawgpsd'].stop() |
||||||
|
|
||||||
|
ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') |
||||||
|
loc_status = json.loads(ls) |
||||||
|
assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
unittest.main() |
@ -1 +1 @@ |
|||||||
c40319a454840d8a2196ec1227d27b536ee14375 |
c171250d2cc013b3eca1cda4fb62f3d0dda28d4d |
||||||
|
@ -1 +1 @@ |
|||||||
53079010a5db8105854212157b5ee90029df7b92 |
f636c68e2b4ed88d3731930cf15b6dee984eb6dd |
||||||
|
@ -1 +1 @@ |
|||||||
Subproject commit 2e9b7637b3c3c8895fda9f964215db3a35fe3441 |
Subproject commit 870ea766eec7a38d7d590c81436f15271ba2667e |
@ -0,0 +1,4 @@ |
|||||||
|
moc_* |
||||||
|
*.moc |
||||||
|
|
||||||
|
_cabana |
@ -0,0 +1,9 @@ |
|||||||
|
# Cabana |
||||||
|
|
||||||
|
<img src="https://cabana.comma.ai/img/cabana.jpg" width="640" height="267" /> |
||||||
|
|
||||||
|
Cabana is a tool developed to view raw CAN data. One use for this is creating and editing [CAN Dictionaries](http://socialledge.com/sjsu/index.php/DBC_Format) (DBC files), and the tool provides direct integration with [commaai/opendbc](https://github.com/commaai/opendbc) (a collection of DBC files), allowing you to load the DBC files direct from source, and save to your fork. In addition, you can load routes from [comma connect](https://connect.comma.ai). |
||||||
|
|
||||||
|
## Usage Instructions |
||||||
|
|
||||||
|
See [openpilot wiki](https://github.com/commaai/openpilot/wiki/Cabana) |
@ -0,0 +1,20 @@ |
|||||||
|
import os |
||||||
|
Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', |
||||||
|
'cereal', 'transformations', 'widgets', 'opendbc') |
||||||
|
|
||||||
|
base_frameworks = qt_env['FRAMEWORKS'] |
||||||
|
base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', |
||||||
|
'capnp', 'kj', 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] |
||||||
|
|
||||||
|
if arch == "Darwin": |
||||||
|
base_frameworks.append('OpenCL') |
||||||
|
else: |
||||||
|
base_libs.append('OpenCL') |
||||||
|
|
||||||
|
qt_libs = ['qt_util', 'Qt5Charts'] + base_libs |
||||||
|
if arch in ['x86_64', 'Darwin'] and GetOption('extras'): |
||||||
|
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] |
||||||
|
|
||||||
|
Import('replay_lib') |
||||||
|
cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs |
||||||
|
qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'videowidget.cc', 'signaledit.cc', 'parser.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) |
@ -0,0 +1,4 @@ |
|||||||
|
#!/bin/sh |
||||||
|
cd "$(dirname "$0")" |
||||||
|
export LD_LIBRARY_PATH="../../opendbc/can:$LD_LIBRARY_PATH" |
||||||
|
exec ./_cabana "$1" |
@ -0,0 +1,32 @@ |
|||||||
|
#include <QApplication> |
||||||
|
#include <QCommandLineParser> |
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/util.h" |
||||||
|
#include "tools/cabana/mainwin.h" |
||||||
|
|
||||||
|
const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) { |
||||||
|
initApp(argc, argv); |
||||||
|
QApplication app(argc, argv); |
||||||
|
|
||||||
|
QCommandLineParser cmd_parser; |
||||||
|
cmd_parser.addHelpOption(); |
||||||
|
cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); |
||||||
|
cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); |
||||||
|
cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); |
||||||
|
cmd_parser.process(app); |
||||||
|
const QStringList args = cmd_parser.positionalArguments(); |
||||||
|
if (args.empty() && !cmd_parser.isSet("demo")) { |
||||||
|
cmd_parser.showHelp(); |
||||||
|
} |
||||||
|
|
||||||
|
const QString route = args.empty() ? DEMO_ROUTE : args.first(); |
||||||
|
Parser p(&app); |
||||||
|
if (!p.loadRoute(route, cmd_parser.value("data_dir"), true)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
MainWindow w; |
||||||
|
w.showMaximized(); |
||||||
|
return app.exec(); |
||||||
|
} |
@ -0,0 +1,271 @@ |
|||||||
|
#include "tools/cabana/chartswidget.h" |
||||||
|
|
||||||
|
#include <QGraphicsLayout> |
||||||
|
#include <QLabel> |
||||||
|
#include <QRubberBand> |
||||||
|
#include <QStackedLayout> |
||||||
|
#include <QtCharts/QLineSeries> |
||||||
|
#include <QtCharts/QValueAxis> |
||||||
|
|
||||||
|
int64_t get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { |
||||||
|
int64_t ret = 0; |
||||||
|
|
||||||
|
int i = sig.msb / 8; |
||||||
|
int bits = sig.size; |
||||||
|
while (i >= 0 && i < data_size && bits > 0) { |
||||||
|
int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; |
||||||
|
int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; |
||||||
|
int size = msb - lsb + 1; |
||||||
|
|
||||||
|
uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); |
||||||
|
ret |= d << (bits - size); |
||||||
|
|
||||||
|
bits -= size; |
||||||
|
i = sig.is_little_endian ? i - 1 : i + 1; |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
// ChartsWidget
|
||||||
|
|
||||||
|
ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { |
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
||||||
|
main_layout->setContentsMargins(0, 0, 0, 0); |
||||||
|
|
||||||
|
// title bar
|
||||||
|
title_bar = new QWidget(this); |
||||||
|
QHBoxLayout *title_layout = new QHBoxLayout(title_bar); |
||||||
|
title_label = new QLabel(tr("Charts")); |
||||||
|
|
||||||
|
title_layout->addWidget(title_label); |
||||||
|
title_layout->addStretch(); |
||||||
|
|
||||||
|
reset_zoom_btn = new QPushButton("⟲", this); |
||||||
|
reset_zoom_btn->setVisible(false); |
||||||
|
reset_zoom_btn->setFixedSize(30, 30); |
||||||
|
reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); |
||||||
|
title_layout->addWidget(reset_zoom_btn); |
||||||
|
|
||||||
|
remove_all_btn = new QPushButton(tr("✖")); |
||||||
|
remove_all_btn->setVisible(false); |
||||||
|
remove_all_btn->setToolTip(tr("Remove all charts")); |
||||||
|
remove_all_btn->setFixedSize(30, 30); |
||||||
|
title_layout->addWidget(remove_all_btn); |
||||||
|
|
||||||
|
dock_btn = new QPushButton(); |
||||||
|
dock_btn->setFixedSize(30, 30); |
||||||
|
updateDockButton(); |
||||||
|
title_layout->addWidget(dock_btn); |
||||||
|
|
||||||
|
main_layout->addWidget(title_bar, 0, Qt::AlignTop); |
||||||
|
|
||||||
|
// charts
|
||||||
|
QWidget *charts_container = new QWidget(this); |
||||||
|
QVBoxLayout *charts_main = new QVBoxLayout(charts_container); |
||||||
|
charts_layout = new QVBoxLayout(); |
||||||
|
charts_main->addLayout(charts_layout); |
||||||
|
charts_main->addStretch(); |
||||||
|
|
||||||
|
QScrollArea *charts_scroll = new QScrollArea(this); |
||||||
|
charts_scroll->setWidgetResizable(true); |
||||||
|
charts_scroll->setWidget(charts_container); |
||||||
|
charts_scroll->setFrameShape(QFrame::NoFrame); |
||||||
|
charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||||
|
|
||||||
|
main_layout->addWidget(charts_scroll); |
||||||
|
|
||||||
|
QObject::connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); |
||||||
|
QObject::connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); |
||||||
|
QObject::connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart); |
||||||
|
QObject::connect(reset_zoom_btn, &QPushButton::clicked, parser, &Parser::resetRange); |
||||||
|
QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll); |
||||||
|
QObject::connect(dock_btn, &QPushButton::clicked, [=]() { |
||||||
|
emit dock(!docking); |
||||||
|
docking = !docking; |
||||||
|
updateDockButton(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
void ChartsWidget::updateDockButton() { |
||||||
|
dock_btn->setText(docking ? "⬈" : "⬋"); |
||||||
|
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); |
||||||
|
} |
||||||
|
|
||||||
|
void ChartsWidget::addChart(const QString &id, const QString &sig_name) { |
||||||
|
const QString char_name = id + sig_name; |
||||||
|
if (charts.find(char_name) == charts.end()) { |
||||||
|
auto chart = new ChartWidget(id, sig_name, this); |
||||||
|
charts_layout->insertWidget(0, chart); |
||||||
|
charts[char_name] = chart; |
||||||
|
} |
||||||
|
remove_all_btn->setVisible(true); |
||||||
|
reset_zoom_btn->setVisible(true); |
||||||
|
title_label->setText(tr("Charts (%1)").arg(charts.size())); |
||||||
|
} |
||||||
|
|
||||||
|
void ChartsWidget::removeChart(const QString &id, const QString &sig_name) { |
||||||
|
if (auto it = charts.find(id + sig_name); it != charts.end()) { |
||||||
|
it->second->deleteLater(); |
||||||
|
charts.erase(it); |
||||||
|
if (charts.empty()) { |
||||||
|
remove_all_btn->setVisible(false); |
||||||
|
reset_zoom_btn->setVisible(false); |
||||||
|
} |
||||||
|
} |
||||||
|
title_label->setText(tr("Charts (%1)").arg(charts.size())); |
||||||
|
} |
||||||
|
|
||||||
|
void ChartsWidget::removeAll() { |
||||||
|
for (auto [_, chart] : charts) |
||||||
|
chart->deleteLater(); |
||||||
|
charts.clear(); |
||||||
|
remove_all_btn->setVisible(false); |
||||||
|
reset_zoom_btn->setVisible(false); |
||||||
|
} |
||||||
|
|
||||||
|
// ChartWidget
|
||||||
|
|
||||||
|
ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) { |
||||||
|
QStackedLayout *stacked = new QStackedLayout(this); |
||||||
|
stacked->setStackingMode(QStackedLayout::StackAll); |
||||||
|
|
||||||
|
QWidget *chart_widget = new QWidget(this); |
||||||
|
QVBoxLayout *chart_layout = new QVBoxLayout(chart_widget); |
||||||
|
chart_layout->setSpacing(0); |
||||||
|
chart_layout->setContentsMargins(0, 0, 0, 0); |
||||||
|
|
||||||
|
QWidget *header = new QWidget(this); |
||||||
|
header->setStyleSheet("background-color:white"); |
||||||
|
QHBoxLayout *header_layout = new QHBoxLayout(header); |
||||||
|
header_layout->setContentsMargins(11, 11, 11, 0); |
||||||
|
QLabel *title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id)); |
||||||
|
header_layout->addWidget(title); |
||||||
|
header_layout->addStretch(); |
||||||
|
|
||||||
|
QPushButton *remove_btn = new QPushButton("✖", this); |
||||||
|
remove_btn->setFixedSize(30, 30); |
||||||
|
remove_btn->setToolTip(tr("Remove chart")); |
||||||
|
QObject::connect(remove_btn, &QPushButton::clicked, [=]() { |
||||||
|
emit parser->hidePlot(id, sig_name); |
||||||
|
}); |
||||||
|
header_layout->addWidget(remove_btn); |
||||||
|
chart_layout->addWidget(header); |
||||||
|
|
||||||
|
QLineSeries *series = new QLineSeries(); |
||||||
|
series->setUseOpenGL(true); |
||||||
|
auto chart = new QChart(); |
||||||
|
chart->setTitle(sig_name); |
||||||
|
chart->addSeries(series); |
||||||
|
chart->createDefaultAxes(); |
||||||
|
chart->legend()->hide(); |
||||||
|
QFont font; |
||||||
|
font.setBold(true); |
||||||
|
chart->setTitleFont(font); |
||||||
|
chart->setMargins({0, 0, 0, 0}); |
||||||
|
chart->layout()->setContentsMargins(0, 0, 0, 0); |
||||||
|
QObject::connect(dynamic_cast<QValueAxis *>(chart->axisX()), &QValueAxis::rangeChanged, parser, &Parser::setRange); |
||||||
|
|
||||||
|
chart_view = new QChartView(chart); |
||||||
|
chart_view->setFixedHeight(300); |
||||||
|
chart_view->setRenderHint(QPainter::Antialiasing); |
||||||
|
chart_view->setRubberBand(QChartView::HorizontalRubberBand); |
||||||
|
if (auto rubber = chart_view->findChild<QRubberBand *>()) { |
||||||
|
QPalette pal; |
||||||
|
pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80)); |
||||||
|
rubber->setPalette(pal); |
||||||
|
} |
||||||
|
chart_layout->addWidget(chart_view); |
||||||
|
chart_layout->addStretch(); |
||||||
|
|
||||||
|
stacked->addWidget(chart_widget); |
||||||
|
line_marker = new LineMarker(this); |
||||||
|
stacked->addWidget(line_marker); |
||||||
|
line_marker->setAttribute(Qt::WA_TransparentForMouseEvents, true); |
||||||
|
line_marker->raise(); |
||||||
|
|
||||||
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); |
||||||
|
QObject::connect(parser, &Parser::updated, this, &ChartWidget::updateState); |
||||||
|
QObject::connect(parser, &Parser::rangeChanged, this, &ChartWidget::rangeChanged); |
||||||
|
QObject::connect(parser, &Parser::eventsMerged, this, &ChartWidget::updateSeries); |
||||||
|
|
||||||
|
updateSeries(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChartWidget::updateState() { |
||||||
|
auto chart = chart_view->chart(); |
||||||
|
auto axis_x = dynamic_cast<QValueAxis *>(chart->axisX()); |
||||||
|
int x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); |
||||||
|
if (line_marker_x != x) { |
||||||
|
line_marker->setX(x); |
||||||
|
line_marker_x = x; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChartWidget::updateSeries() { |
||||||
|
const Signal *sig = parser->getSig(id, sig_name); |
||||||
|
auto events = parser->replay->events(); |
||||||
|
if (!sig || !events) return; |
||||||
|
|
||||||
|
auto l = id.split(':'); |
||||||
|
int bus = l[0].toInt(); |
||||||
|
uint32_t address = l[1].toUInt(nullptr, 16); |
||||||
|
|
||||||
|
vals.clear(); |
||||||
|
vals.reserve(3 * 60 * 100); |
||||||
|
uint64_t route_start_time = parser->replay->routeStartTime(); |
||||||
|
for (auto &evt : *events) { |
||||||
|
if (evt->which == cereal::Event::Which::CAN) { |
||||||
|
for (auto c : evt->event.getCan()) { |
||||||
|
if (bus == c.getSrc() && address == c.getAddress()) { |
||||||
|
auto dat = c.getDat(); |
||||||
|
int64_t val = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig); |
||||||
|
if (sig->is_signed) { |
||||||
|
val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; |
||||||
|
} |
||||||
|
double value = val * sig->factor + sig->offset; |
||||||
|
double ts = (evt->mono_time - route_start_time) / (double)1e9; // seconds
|
||||||
|
vals.push_back({ts, value}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
QLineSeries *series = (QLineSeries *)chart_view->chart()->series()[0]; |
||||||
|
series->replace(vals); |
||||||
|
auto [begin, end] = parser->range(); |
||||||
|
chart_view->chart()->axisX()->setRange(begin, end); |
||||||
|
updateAxisY(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChartWidget::rangeChanged(qreal min, qreal max) { |
||||||
|
auto axis_x = dynamic_cast<QValueAxis *>(chart_view->chart()->axisX()); |
||||||
|
if (axis_x->min() != min || axis_x->max() != max) { |
||||||
|
axis_x->setRange(min, max); |
||||||
|
} |
||||||
|
updateAxisY(); |
||||||
|
} |
||||||
|
|
||||||
|
// auto zoom on yaxis
|
||||||
|
void ChartWidget::updateAxisY() { |
||||||
|
const auto axis_x = dynamic_cast<QValueAxis *>(chart_view->chart()->axisX()); |
||||||
|
// vals is a sorted list
|
||||||
|
auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); |
||||||
|
if (begin == vals.end()) |
||||||
|
return; |
||||||
|
|
||||||
|
auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); |
||||||
|
const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); |
||||||
|
chart_view->chart()->axisY()->setRange(min->y(), max->y()); |
||||||
|
} |
||||||
|
|
||||||
|
// LineMarker
|
||||||
|
|
||||||
|
void LineMarker::setX(double x) { |
||||||
|
x_pos = x; |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
void LineMarker::paintEvent(QPaintEvent *event) { |
||||||
|
QPainter p(this); |
||||||
|
p.setPen(QPen(Qt::black, 2)); |
||||||
|
p.drawLine(QPointF{x_pos, 50.}, QPointF{x_pos, (qreal)height() - 11}); |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <map> |
||||||
|
|
||||||
|
#include <QLabel> |
||||||
|
#include <QPushButton> |
||||||
|
#include <QVBoxLayout> |
||||||
|
#include <QWidget> |
||||||
|
#include <QtCharts/QChartView> |
||||||
|
#include <QtCharts/QLineSeries> |
||||||
|
|
||||||
|
#include "tools/cabana/parser.h" |
||||||
|
|
||||||
|
using namespace QtCharts; |
||||||
|
|
||||||
|
class LineMarker : public QWidget { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
LineMarker(QWidget *parent) : QWidget(parent) {} |
||||||
|
void setX(double x); |
||||||
|
|
||||||
|
private: |
||||||
|
void paintEvent(QPaintEvent *event) override; |
||||||
|
double x_pos = 0.0; |
||||||
|
}; |
||||||
|
|
||||||
|
class ChartWidget : public QWidget { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
ChartWidget(const QString &id, const QString &sig_name, QWidget *parent); |
||||||
|
inline QChart *chart() const { return chart_view->chart(); } |
||||||
|
|
||||||
|
private: |
||||||
|
void updateState(); |
||||||
|
void addData(const CanData &can_data, const Signal &sig); |
||||||
|
void updateSeries(); |
||||||
|
void rangeChanged(qreal min, qreal max); |
||||||
|
void updateAxisY(); |
||||||
|
|
||||||
|
QString id; |
||||||
|
QString sig_name; |
||||||
|
QChartView *chart_view = nullptr; |
||||||
|
LineMarker *line_marker = nullptr; |
||||||
|
double line_marker_x = 0.0; |
||||||
|
QList<QPointF> vals; |
||||||
|
}; |
||||||
|
|
||||||
|
class ChartsWidget : public QWidget { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
ChartsWidget(QWidget *parent = nullptr); |
||||||
|
void addChart(const QString &id, const QString &sig_name); |
||||||
|
void removeChart(const QString &id, const QString &sig_name); |
||||||
|
void removeAll(); |
||||||
|
inline bool hasChart(const QString &id, const QString &sig_name) { |
||||||
|
return charts.find(id + sig_name) != charts.end(); |
||||||
|
} |
||||||
|
|
||||||
|
signals: |
||||||
|
void dock(bool floating); |
||||||
|
|
||||||
|
private: |
||||||
|
void updateState(); |
||||||
|
void updateDockButton(); |
||||||
|
|
||||||
|
QWidget *title_bar; |
||||||
|
QLabel *title_label; |
||||||
|
bool docking = true; |
||||||
|
QPushButton *dock_btn; |
||||||
|
QPushButton *reset_zoom_btn; |
||||||
|
QPushButton *remove_all_btn; |
||||||
|
QVBoxLayout *charts_layout; |
||||||
|
std::map<QString, ChartWidget *> charts; |
||||||
|
}; |
@ -0,0 +1,264 @@ |
|||||||
|
|
||||||
|
#include "tools/cabana/detailwidget.h" |
||||||
|
|
||||||
|
#include <QDebug> |
||||||
|
#include <QDialogButtonBox> |
||||||
|
#include <QFormLayout> |
||||||
|
#include <QHeaderView> |
||||||
|
#include <QTimer> |
||||||
|
#include <QVBoxLayout> |
||||||
|
#include <bitset> |
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/util.h" |
||||||
|
#include "selfdrive/ui/qt/widgets/scrollview.h" |
||||||
|
|
||||||
|
inline const QString &getColor(int i) { |
||||||
|
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; |
||||||
|
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; |
||||||
|
} |
||||||
|
|
||||||
|
// DetailWidget
|
||||||
|
|
||||||
|
DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { |
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
||||||
|
|
||||||
|
name_label = new QLabel(this); |
||||||
|
name_label->setStyleSheet("font-weight:bold;"); |
||||||
|
name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); |
||||||
|
name_label->setAlignment(Qt::AlignCenter); |
||||||
|
main_layout->addWidget(name_label); |
||||||
|
|
||||||
|
// title
|
||||||
|
QHBoxLayout *title_layout = new QHBoxLayout(); |
||||||
|
time_label = new QLabel(this); |
||||||
|
title_layout->addWidget(time_label); |
||||||
|
title_layout->addStretch(); |
||||||
|
|
||||||
|
edit_btn = new QPushButton(tr("Edit"), this); |
||||||
|
edit_btn->setVisible(false); |
||||||
|
title_layout->addWidget(edit_btn); |
||||||
|
main_layout->addLayout(title_layout); |
||||||
|
|
||||||
|
// binary view
|
||||||
|
binary_view = new BinaryView(this); |
||||||
|
main_layout->addWidget(binary_view); |
||||||
|
|
||||||
|
// scroll area
|
||||||
|
QHBoxLayout *signals_layout = new QHBoxLayout(); |
||||||
|
signals_layout->addWidget(new QLabel(tr("Signals"))); |
||||||
|
signals_layout->addStretch(); |
||||||
|
add_sig_btn = new QPushButton(tr("Add signal"), this); |
||||||
|
add_sig_btn->setVisible(false); |
||||||
|
signals_layout->addWidget(add_sig_btn); |
||||||
|
main_layout->addLayout(signals_layout); |
||||||
|
|
||||||
|
QWidget *container = new QWidget(this); |
||||||
|
QVBoxLayout *container_layout = new QVBoxLayout(container); |
||||||
|
signal_edit_layout = new QVBoxLayout(); |
||||||
|
signal_edit_layout->setSpacing(2); |
||||||
|
container_layout->addLayout(signal_edit_layout); |
||||||
|
|
||||||
|
history_log = new HistoryLog(this); |
||||||
|
container_layout->addWidget(history_log); |
||||||
|
|
||||||
|
QScrollArea *scroll = new QScrollArea(this); |
||||||
|
scroll->setWidget(container); |
||||||
|
scroll->setWidgetResizable(true); |
||||||
|
scroll->setFrameShape(QFrame::NoFrame); |
||||||
|
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||||
|
scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); |
||||||
|
|
||||||
|
main_layout->addWidget(scroll); |
||||||
|
|
||||||
|
QObject::connect(add_sig_btn, &QPushButton::clicked, this, &DetailWidget::addSignal); |
||||||
|
QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); |
||||||
|
QObject::connect(parser, &Parser::updated, this, &DetailWidget::updateState); |
||||||
|
} |
||||||
|
|
||||||
|
void DetailWidget::setMsg(const CanData *c) { |
||||||
|
can_data = c; |
||||||
|
clearLayout(signal_edit_layout); |
||||||
|
edit_btn->setVisible(true); |
||||||
|
|
||||||
|
if (auto msg = parser->getMsg(can_data->address)) { |
||||||
|
name_label->setText(msg->name.c_str()); |
||||||
|
add_sig_btn->setVisible(true); |
||||||
|
for (int i = 0; i < msg->sigs.size(); ++i) { |
||||||
|
signal_edit_layout->addWidget(new SignalEdit(can_data->id, msg->sigs[i], getColor(i))); |
||||||
|
} |
||||||
|
} else { |
||||||
|
name_label->setText(tr("untitled")); |
||||||
|
add_sig_btn->setVisible(false); |
||||||
|
} |
||||||
|
|
||||||
|
binary_view->setMsg(can_data); |
||||||
|
history_log->clear(); |
||||||
|
} |
||||||
|
|
||||||
|
void DetailWidget::updateState() { |
||||||
|
if (!can_data) return; |
||||||
|
|
||||||
|
time_label->setText(QString("time: %1").arg(can_data->ts, 0, 'f', 3)); |
||||||
|
binary_view->setData(can_data->dat); |
||||||
|
history_log->updateState(); |
||||||
|
} |
||||||
|
|
||||||
|
void DetailWidget::editMsg() { |
||||||
|
EditMessageDialog dlg(can_data->id, this); |
||||||
|
if (dlg.exec()) { |
||||||
|
setMsg(can_data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void DetailWidget::addSignal() { |
||||||
|
AddSignalDialog dlg(can_data->id, this); |
||||||
|
if (dlg.exec()) { |
||||||
|
setMsg(can_data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// BinaryView
|
||||||
|
|
||||||
|
BinaryView::BinaryView(QWidget *parent) { |
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
||||||
|
table = new QTableWidget(this); |
||||||
|
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
||||||
|
table->horizontalHeader()->hide(); |
||||||
|
table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||||
|
table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||||
|
main_layout->addWidget(table); |
||||||
|
table->setColumnCount(9); |
||||||
|
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); |
||||||
|
} |
||||||
|
|
||||||
|
void BinaryView::setMsg(const CanData *can_data) { |
||||||
|
const Msg *msg = parser->getMsg(can_data->address); |
||||||
|
int row_count = msg ? msg->size : can_data->dat.size(); |
||||||
|
|
||||||
|
table->setRowCount(row_count); |
||||||
|
table->setColumnCount(9); |
||||||
|
for (int i = 0; i < table->rowCount(); ++i) { |
||||||
|
for (int j = 0; j < table->columnCount(); ++j) { |
||||||
|
auto item = new QTableWidgetItem(); |
||||||
|
item->setFlags(item->flags() ^ Qt::ItemIsEditable); |
||||||
|
item->setTextAlignment(Qt::AlignCenter); |
||||||
|
if (j == 8) { |
||||||
|
QFont font; |
||||||
|
font.setBold(true); |
||||||
|
item->setFont(font); |
||||||
|
} |
||||||
|
table->setItem(i, j, item); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (msg) { |
||||||
|
// set background color
|
||||||
|
for (int i = 0; i < msg->sigs.size(); ++i) { |
||||||
|
const auto &sig = msg->sigs[i]; |
||||||
|
int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit); |
||||||
|
for (int j = start; j <= start + sig.size - 1; ++j) { |
||||||
|
table->item(j / 8, j % 8)->setBackground(QColor(getColor(i))); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setFixedHeight(table->rowHeight(0) * table->rowCount() + 25); |
||||||
|
} |
||||||
|
|
||||||
|
void BinaryView::setData(const QByteArray &binary) { |
||||||
|
std::string s; |
||||||
|
for (int j = 0; j < binary.size(); ++j) { |
||||||
|
s += std::bitset<8>(binary[j]).to_string(); |
||||||
|
} |
||||||
|
|
||||||
|
setUpdatesEnabled(false); |
||||||
|
char hex[3] = {'\0'}; |
||||||
|
for (int i = 0; i < binary.size(); ++i) { |
||||||
|
for (int j = 0; j < 8; ++j) { |
||||||
|
table->item(i, j)->setText(QChar(s[i * 8 + j])); |
||||||
|
} |
||||||
|
sprintf(&hex[0], "%02X", (unsigned char)binary[i]); |
||||||
|
table->item(i, 8)->setText(hex); |
||||||
|
} |
||||||
|
setUpdatesEnabled(true); |
||||||
|
} |
||||||
|
|
||||||
|
// HistoryLog
|
||||||
|
|
||||||
|
HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) { |
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
||||||
|
QLabel *title = new QLabel("TIME BYTES"); |
||||||
|
main_layout->addWidget(title); |
||||||
|
|
||||||
|
QVBoxLayout *message_layout = new QVBoxLayout(); |
||||||
|
for (int i = 0; i < std::size(labels); ++i) { |
||||||
|
labels[i] = new QLabel(); |
||||||
|
labels[i]->setVisible(false); |
||||||
|
message_layout->addWidget(labels[i]); |
||||||
|
} |
||||||
|
main_layout->addLayout(message_layout); |
||||||
|
main_layout->addStretch(); |
||||||
|
} |
||||||
|
|
||||||
|
void HistoryLog::updateState() { |
||||||
|
int i = 0; |
||||||
|
for (; i < parser->history_log.size(); ++i) { |
||||||
|
const auto &c = parser->history_log[i]; |
||||||
|
auto label = labels[i]; |
||||||
|
label->setVisible(true); |
||||||
|
label->setText(QString("%1 %2").arg(c.ts, 0, 'f', 3).arg(toHex(c.dat))); |
||||||
|
} |
||||||
|
|
||||||
|
for (; i < std::size(labels); ++i) { |
||||||
|
labels[i]->setVisible(false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void HistoryLog::clear() { |
||||||
|
setUpdatesEnabled(false); |
||||||
|
for (auto l : labels) l->setVisible(false); |
||||||
|
setUpdatesEnabled(true); |
||||||
|
} |
||||||
|
|
||||||
|
// EditMessageDialog
|
||||||
|
|
||||||
|
EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : id(id), QDialog(parent) { |
||||||
|
setWindowTitle(tr("Edit message")); |
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
||||||
|
|
||||||
|
QFormLayout *form_layout = new QFormLayout(); |
||||||
|
form_layout->addRow("ID", new QLabel(id)); |
||||||
|
|
||||||
|
auto msg = const_cast<Msg *>(parser->getMsg(id)); |
||||||
|
name_edit = new QLineEdit(this); |
||||||
|
name_edit->setText(msg ? msg->name.c_str() : "untitled"); |
||||||
|
form_layout->addRow(tr("Name"), name_edit); |
||||||
|
|
||||||
|
size_spin = new QSpinBox(this); |
||||||
|
size_spin->setValue(msg ? msg->size : parser->can_msgs[id].dat.size()); |
||||||
|
form_layout->addRow(tr("Size"), size_spin); |
||||||
|
|
||||||
|
main_layout->addLayout(form_layout); |
||||||
|
|
||||||
|
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); |
||||||
|
main_layout->addWidget(buttonBox); |
||||||
|
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &EditMessageDialog::save); |
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); |
||||||
|
} |
||||||
|
|
||||||
|
void EditMessageDialog::save() { |
||||||
|
if (size_spin->value() <= 0 || name_edit->text().isEmpty()) return; |
||||||
|
|
||||||
|
if (auto msg = const_cast<Msg *>(parser->getMsg(id))) { |
||||||
|
msg->name = name_edit->text().toStdString(); |
||||||
|
msg->size = size_spin->value(); |
||||||
|
} else { |
||||||
|
Msg m = {}; |
||||||
|
m.address = Parser::addressFromId(id); |
||||||
|
m.name = name_edit->text().toStdString(); |
||||||
|
m.size = size_spin->value(); |
||||||
|
parser->addNewMsg(m); |
||||||
|
} |
||||||
|
QDialog::accept(); |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <QDialog> |
||||||
|
#include <QLabel> |
||||||
|
#include <QPushButton> |
||||||
|
#include <QTableWidget> |
||||||
|
#include <QVBoxLayout> |
||||||
|
#include <QWidget> |
||||||
|
|
||||||
|
#include "opendbc/can/common.h" |
||||||
|
#include "opendbc/can/common_dbc.h" |
||||||
|
#include "tools/cabana/parser.h" |
||||||
|
#include "tools/cabana/signaledit.h" |
||||||
|
|
||||||
|
class HistoryLog : public QWidget { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
HistoryLog(QWidget *parent); |
||||||
|
void clear(); |
||||||
|
void updateState(); |
||||||
|
|
||||||
|
private: |
||||||
|
QLabel *labels[LOG_SIZE] = {}; |
||||||
|
}; |
||||||
|
|
||||||
|
class BinaryView : public QWidget { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
BinaryView(QWidget *parent); |
||||||
|
void setMsg(const CanData *can_data); |
||||||
|
void setData(const QByteArray &binary); |
||||||
|
|
||||||
|
QTableWidget *table; |
||||||
|
}; |
||||||
|
|
||||||
|
class EditMessageDialog : public QDialog { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
EditMessageDialog(const QString &id, QWidget *parent); |
||||||
|
|
||||||
|
protected: |
||||||
|
void save(); |
||||||
|
|
||||||
|
QLineEdit *name_edit; |
||||||
|
QSpinBox *size_spin; |
||||||
|
QString id; |
||||||
|
}; |
||||||
|
|
||||||
|
class DetailWidget : public QWidget { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
DetailWidget(QWidget *parent); |
||||||
|
void setMsg(const CanData *c); |
||||||
|
|
||||||
|
private: |
||||||
|
void updateState(); |
||||||
|
void addSignal(); |
||||||
|
void editMsg(); |
||||||
|
|
||||||
|
const CanData *can_data = nullptr; |
||||||
|
QLabel *name_label, *time_label; |
||||||
|
QPushButton *edit_btn, *add_sig_btn; |
||||||
|
QVBoxLayout *signal_edit_layout; |
||||||
|
HistoryLog *history_log; |
||||||
|
BinaryView *binary_view; |
||||||
|
}; |
@ -0,0 +1,58 @@ |
|||||||
|
#include "tools/cabana/mainwin.h" |
||||||
|
|
||||||
|
#include <QHBoxLayout> |
||||||
|
#include <QScreen> |
||||||
|
#include <QVBoxLayout> |
||||||
|
|
||||||
|
MainWindow::MainWindow() : QWidget() { |
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
||||||
|
|
||||||
|
QHBoxLayout *h_layout = new QHBoxLayout(); |
||||||
|
main_layout->addLayout(h_layout); |
||||||
|
|
||||||
|
messages_widget = new MessagesWidget(this); |
||||||
|
h_layout->addWidget(messages_widget); |
||||||
|
|
||||||
|
detail_widget = new DetailWidget(this); |
||||||
|
detail_widget->setFixedWidth(600); |
||||||
|
h_layout->addWidget(detail_widget); |
||||||
|
|
||||||
|
// right widgets
|
||||||
|
QWidget *right_container = new QWidget(this); |
||||||
|
right_container->setFixedWidth(640); |
||||||
|
r_layout = new QVBoxLayout(right_container); |
||||||
|
|
||||||
|
video_widget = new VideoWidget(this); |
||||||
|
r_layout->addWidget(video_widget, 0, Qt::AlignTop); |
||||||
|
|
||||||
|
charts_widget = new ChartsWidget(this); |
||||||
|
r_layout->addWidget(charts_widget); |
||||||
|
|
||||||
|
h_layout->addWidget(right_container); |
||||||
|
|
||||||
|
QObject::connect(messages_widget, &MessagesWidget::msgChanged, detail_widget, &DetailWidget::setMsg); |
||||||
|
QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); |
||||||
|
} |
||||||
|
|
||||||
|
void MainWindow::dockCharts(bool dock) { |
||||||
|
charts_widget->setUpdatesEnabled(false); |
||||||
|
if (dock && floating_window) { |
||||||
|
r_layout->addWidget(charts_widget); |
||||||
|
floating_window->deleteLater(); |
||||||
|
floating_window = nullptr; |
||||||
|
} else if (!dock && !floating_window) { |
||||||
|
floating_window = new QWidget(nullptr); |
||||||
|
floating_window->setLayout(new QVBoxLayout()); |
||||||
|
floating_window->layout()->addWidget(charts_widget); |
||||||
|
floating_window->setWindowFlags(Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint); |
||||||
|
floating_window->setMinimumSize(QGuiApplication::primaryScreen()->size() / 2); |
||||||
|
floating_window->showMaximized(); |
||||||
|
} |
||||||
|
charts_widget->setUpdatesEnabled(true); |
||||||
|
} |
||||||
|
|
||||||
|
void MainWindow::closeEvent(QCloseEvent *event) { |
||||||
|
if (floating_window) |
||||||
|
floating_window->deleteLater(); |
||||||
|
QWidget::closeEvent(event); |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "tools/cabana/chartswidget.h" |
||||||
|
#include "tools/cabana/detailwidget.h" |
||||||
|
#include "tools/cabana/messageswidget.h" |
||||||
|
#include "tools/cabana/parser.h" |
||||||
|
#include "tools/cabana/videowidget.h" |
||||||
|
|
||||||
|
class MainWindow : public QWidget { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
MainWindow(); |
||||||
|
void dockCharts(bool dock); |
||||||
|
|
||||||
|
protected: |
||||||
|
void closeEvent(QCloseEvent *event) override; |
||||||
|
|
||||||
|
VideoWidget *video_widget; |
||||||
|
MessagesWidget *messages_widget; |
||||||
|
DetailWidget *detail_widget; |
||||||
|
ChartsWidget *charts_widget; |
||||||
|
QWidget *floating_window = nullptr; |
||||||
|
QVBoxLayout *r_layout; |
||||||
|
}; |
@ -0,0 +1,93 @@ |
|||||||
|
#include "tools/cabana/messageswidget.h" |
||||||
|
|
||||||
|
#include <QComboBox> |
||||||
|
#include <QHeaderView> |
||||||
|
#include <QPushButton> |
||||||
|
#include <QVBoxLayout> |
||||||
|
|
||||||
|
MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { |
||||||
|
QVBoxLayout *main_layout = new QVBoxLayout(this); |
||||||
|
|
||||||
|
QHBoxLayout *dbc_file_layout = new QHBoxLayout(); |
||||||
|
QComboBox *combo = new QComboBox(this); |
||||||
|
auto dbc_names = get_dbc_names(); |
||||||
|
for (const auto &name : dbc_names) { |
||||||
|
combo->addItem(QString::fromStdString(name)); |
||||||
|
} |
||||||
|
dbc_file_layout->addWidget(combo); |
||||||
|
|
||||||
|
dbc_file_layout->addStretch(); |
||||||
|
QPushButton *save_btn = new QPushButton(tr("Save DBC"), this); |
||||||
|
dbc_file_layout->addWidget(save_btn); |
||||||
|
main_layout->addLayout(dbc_file_layout); |
||||||
|
|
||||||
|
filter = new QLineEdit(this); |
||||||
|
filter->setPlaceholderText(tr("filter messages")); |
||||||
|
main_layout->addWidget(filter); |
||||||
|
|
||||||
|
table_widget = new QTableWidget(this); |
||||||
|
table_widget->setSelectionBehavior(QAbstractItemView::SelectRows); |
||||||
|
table_widget->setSelectionMode(QAbstractItemView::SingleSelection); |
||||||
|
table_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); |
||||||
|
table_widget->setColumnCount(4); |
||||||
|
table_widget->setColumnWidth(0, 250); |
||||||
|
table_widget->setColumnWidth(1, 80); |
||||||
|
table_widget->setColumnWidth(2, 80); |
||||||
|
table_widget->setHorizontalHeaderLabels({tr("Name"), tr("ID"), tr("Count"), tr("Bytes")}); |
||||||
|
table_widget->horizontalHeader()->setStretchLastSection(true); |
||||||
|
main_layout->addWidget(table_widget); |
||||||
|
|
||||||
|
QObject::connect(parser, &Parser::updated, this, &MessagesWidget::updateState); |
||||||
|
QObject::connect(save_btn, &QPushButton::clicked, [=]() { |
||||||
|
// TODO: save DBC to file
|
||||||
|
}); |
||||||
|
QObject::connect(combo, &QComboBox::currentTextChanged, [=](const QString &dbc) { |
||||||
|
parser->openDBC(dbc); |
||||||
|
}); |
||||||
|
QObject::connect(table_widget, &QTableWidget::itemSelectionChanged, [=]() { |
||||||
|
const CanData *c = &(parser->can_msgs[table_widget->selectedItems()[1]->text()]); |
||||||
|
parser->setCurrentMsg(c->id); |
||||||
|
emit msgChanged(c); |
||||||
|
}); |
||||||
|
|
||||||
|
// For test purpose
|
||||||
|
combo->setCurrentText("toyota_nodsu_pt_generated"); |
||||||
|
} |
||||||
|
|
||||||
|
void MessagesWidget::updateState() { |
||||||
|
auto getTableItem = [=](int row, int col) -> QTableWidgetItem * { |
||||||
|
auto item = table_widget->item(row, col); |
||||||
|
if (!item) { |
||||||
|
item = new QTableWidgetItem(); |
||||||
|
item->setFlags(item->flags() ^ Qt::ItemIsEditable); |
||||||
|
table_widget->setItem(row, col, item); |
||||||
|
} |
||||||
|
return item; |
||||||
|
}; |
||||||
|
|
||||||
|
table_widget->setRowCount(parser->can_msgs.size()); |
||||||
|
int i = 0; |
||||||
|
QString name, untitled = tr("untitled"); |
||||||
|
const QString filter_str = filter->text(); |
||||||
|
for (const auto &[_, c] : parser->can_msgs) { |
||||||
|
if (auto msg = parser->getMsg(c.address)) { |
||||||
|
name = msg->name.c_str(); |
||||||
|
} else { |
||||||
|
name = untitled; |
||||||
|
} |
||||||
|
if (!filter_str.isEmpty() && !name.contains(filter_str, Qt::CaseInsensitive)) { |
||||||
|
table_widget->hideRow(i++); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
getTableItem(i, 0)->setText(name); |
||||||
|
getTableItem(i, 1)->setText(c.id); |
||||||
|
getTableItem(i, 2)->setText(QString::number(parser->counters[c.id])); |
||||||
|
getTableItem(i, 3)->setText(toHex(c.dat)); |
||||||
|
table_widget->showRow(i); |
||||||
|
i++; |
||||||
|
} |
||||||
|
if (table_widget->currentRow() == -1) { |
||||||
|
table_widget->selectRow(0); |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue