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