From 1ecda86e51ad3dfefa87a9cebd28370d57ed55fb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 7 Feb 2023 02:17:58 +0800 Subject: [PATCH 01/40] cabana: concurrently update chart series (#27230) --- tools/cabana/chartswidget.cc | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index c6f5c277eb..34498364c6 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -529,20 +529,38 @@ void ChartView::updateSeries(const Signal *sig, const std::vector *even s.vals.reserve(settings.max_cached_minutes * 60 * 100); // [n]seconds * 100hz s.last_value_mono_time = 0; } - double route_start_time = can->routeStartTime(); + + struct Chunk { + std::vector::const_iterator first, second; + QVector vals; + }; + // split into one minitue chunks + QVector chunks; Event begin_event(cereal::Event::Which::INIT_DATA, s.last_value_mono_time); auto begin = std::upper_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); - for (auto it = begin; it != events->end(); ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - for (const auto &c : (*it)->event.getCan()) { - if (s.address == c.getAddress() && s.source == c.getSrc()) { - auto dat = c.getDat(); - double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig); - double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds - s.vals.push_back({ts, value}); + for (auto it = begin, second = begin; it != events->end(); it = second) { + second = std::lower_bound(it, events->end(), (*it)->mono_time + 1e9 * 60, [](auto &e, uint64_t ts) { return e->mono_time < ts; }); + chunks.push_back({it, second}); + } + + QtConcurrent::blockingMap(chunks, [&](Chunk &chunk) { + chunk.vals.reserve(60 * 100); // 100 hz + double route_start_time = can->routeStartTime(); + for (auto it = chunk.first; it != chunk.second; ++it) { + if ((*it)->which == cereal::Event::Which::CAN) { + for (const auto &c : (*it)->event.getCan()) { + if (s.address == c.getAddress() && s.source == c.getSrc()) { + auto dat = c.getDat(); + double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig); + double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds + chunk.vals.push_back({ts, value}); + } } } } + }); + for (auto &c : chunks) { + s.vals.append(c.vals); } if (events->size()) { s.last_value_mono_time = events->back()->mono_time; From f9de6a0dd876352dacc843ef2b28fb2456771239 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 7 Feb 2023 02:18:44 +0800 Subject: [PATCH 02/40] cabana: click on legend to show/hide series (#27231) * click on legend to show/hide series * update axis y * add move icon --- tools/cabana/chartswidget.cc | 27 ++++++++++++++++++++++++--- tools/cabana/chartswidget.h | 4 ++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 34498364c6..845649dfc1 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -317,6 +317,9 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { background->setPen(Qt::NoPen); background->setZValue(chart->zValue() - 1); + move_icon = new QGraphicsPixmapItem(utils::icon("grip-horizontal"), chart); + move_icon->setToolTip(tr("Drag and drop to combine charts")); + QToolButton *remove_btn = new QToolButton(); remove_btn->setIcon(utils::icon("x")); remove_btn->setAutoRaise(true); @@ -460,6 +463,7 @@ void ChartView::resizeEvent(QResizeEvent *event) { int x = event->size().width() - close_btn_proxy->size().width() - 11; close_btn_proxy->setPos(x, 8); manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - 5, 8); + move_icon->setPos(11, 8); } void ChartView::updatePlotArea(int left) { @@ -472,8 +476,12 @@ void ChartView::updatePlotArea(int left) { } void ChartView::updateTitle() { + for (QLegendMarker *marker : chart()->legend()->markers()) { + QObject::connect(marker, &QLegendMarker::clicked, this, &ChartView::handleMarkerClicked, Qt::UniqueConnection); + } for (auto &s : sigs) { - s.series->setName(QString("%1 %2 %3").arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id)); + auto decoration = s.series->isVisible() ? "none" : "line-through"; + s.series->setName(QString("%2%3 %4").arg(decoration).arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id)); } } @@ -578,6 +586,8 @@ void ChartView::updateAxisY() { double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); for (auto &s : sigs) { + if (!s.series->isVisible()) continue; + auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); auto last = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); for (auto it = first; it != last; ++it) { @@ -634,8 +644,7 @@ void ChartView::leaveEvent(QEvent *event) { } void ChartView::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton && !chart()->plotArea().contains(event->pos()) && - !manage_btn_proxy->geometry().contains(event->pos()) && !close_btn_proxy->geometry().contains(event->pos())) { + if (event->button() == Qt::LeftButton && move_icon->sceneBoundingRect().contains(event->pos())) { QMimeData *mimeData = new QMimeData; mimeData->setData(mime_type, QByteArray::number((qulonglong)this)); QDrag *drag = new QDrag(this); @@ -810,6 +819,18 @@ void ChartView::setSeriesType(QAbstractSeries::SeriesType type) { } } +void ChartView::handleMarkerClicked() { + auto marker = qobject_cast(sender()); + Q_ASSERT(marker); + if (sigs.size() > 1) { + auto series = marker->series(); + series->setVisible(!series->isVisible()); + marker->setVisible(true); + updateAxisY(); + updateTitle(); + } +} + // SeriesSelector SeriesSelector::SeriesSelector(QWidget *parent) { diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index f0f9e5cd9c..77f04534a4 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -5,9 +5,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -56,6 +58,7 @@ private slots: void signalUpdated(const Signal *sig); void signalRemoved(const Signal *sig); void manageSeries(); + void handleMarkerClicked(); private: QList::iterator removeItem(const QList::iterator &it); @@ -80,6 +83,7 @@ private: QValueAxis *axis_x; QValueAxis *axis_y; QVector track_pts; + QGraphicsPixmapItem *move_icon; QGraphicsProxyWidget *close_btn_proxy; QGraphicsProxyWidget *manage_btn_proxy; QGraphicsRectItem *background; From 68bcdaaff7f9050402d983f460f2c85866b4ca32 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 6 Feb 2023 10:40:45 -0800 Subject: [PATCH 03/40] settings: highlight nightly in branch switcher (#27227) --- selfdrive/ui/qt/offroad/software_settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 12d62e63fb..6db6a6cdfe 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -54,7 +54,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { connect(targetBranchBtn, &ButtonControl::clicked, [=]() { auto current = params.get("GitBranch"); QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(","); - for (QString b : {current.c_str(), "devel-staging", "devel", "master-ci", "master"}) { + for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) { auto i = branches.indexOf(b); if (i >= 0) { branches.removeAt(i); From 9a51275cd246bcbf53da764495a944af8a0b51bd Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 6 Feb 2023 15:43:25 -0800 Subject: [PATCH 04/40] setup: fix button label on ethernet (#27235) * setup: continue without wi-fi on ethernet * switch around --- selfdrive/ui/qt/setup/setup.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 69dafcf741..f1ffabf6c6 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -178,8 +178,8 @@ QWidget * Setup::network_setup() { QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) { cont->setEnabled(success); if (success) { - const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL; - cont->setText(cell ? tr("Continue without Wi-Fi") : tr("Continue")); + const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI; + cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi")); } else { cont->setText(tr("Waiting for internet")); } From 1a99fd36101fbe4cbe9853b1df5dfe6c59fea5c4 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 7 Feb 2023 08:39:16 +0800 Subject: [PATCH 05/40] cabana: fixed wrong plot area after changed column count (#27228) --- tools/cabana/chartswidget.cc | 33 +++++++++++++++++---------------- tools/cabana/chartswidget.h | 9 +++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 845649dfc1..a271df9dd7 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -149,7 +149,6 @@ void ChartsWidget::updateState() { for (auto c : charts) { c->updatePlot(cur_sec, range.first, range.second); } - alignCharts(); charts_layout->parentWidget()->setUpdatesEnabled(true); } @@ -186,13 +185,14 @@ ChartView *ChartsWidget::createChart() { auto chart = new ChartView(this); chart->setFixedHeight(settings.chart_height); chart->setMinimumWidth(CHART_MIN_WIDTH); - chart->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight); QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged); QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged); + QObject::connect(chart, &ChartView::axisYLabelWidthChanged, this, &ChartsWidget::alignCharts); charts.push_back(chart); updateLayout(); return chart; @@ -231,14 +231,14 @@ void ChartsWidget::updateLayout() { columns_cb_action->setVisible(show_column_cb); n = std::min(column_count, n); - if (charts.size() != charts_layout->count() || n != charts_layout->columnCount()) { + if (charts.size() != charts_layout->count() || n != current_column_count) { + current_column_count = n; charts_layout->parentWidget()->setUpdatesEnabled(false); for (int i = 0; i < charts.size(); ++i) { charts_layout->addWidget(charts[charts.size() - i - 1], i / n, i % n); } QTimer::singleShot(0, [this]() { charts_layout->parentWidget()->setUpdatesEnabled(true); }); } - alignCharts(true); } void ChartsWidget::resizeEvent(QResizeEvent *event) { @@ -275,16 +275,14 @@ void ChartsWidget::removeAll() { emit seriesChanged(); } -void ChartsWidget::alignCharts(bool force) { +void ChartsWidget::alignCharts() { int plot_left = 0; for (auto c : charts) { plot_left = std::max(plot_left, c->y_label_width); } plot_left = std::max((plot_left / 10) * 10 + 10, 50); - if (std::exchange(align_to, plot_left) != align_to || force) { - for (auto c : charts) { - c->updatePlotArea(align_to); - } + for (auto c : charts) { + c->updatePlotArea(plot_left); } } @@ -459,7 +457,7 @@ void ChartView::manageSeries() { void ChartView::resizeEvent(QResizeEvent *event) { QChartView::resizeEvent(event); - updatePlotArea(); + updatePlotArea(align_to); int x = event->size().width() - close_btn_proxy->size().width() - 11; close_btn_proxy->setPos(x, 8); manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - 5, 8); @@ -467,12 +465,14 @@ void ChartView::resizeEvent(QResizeEvent *event) { } void ChartView::updatePlotArea(int left) { - align_to = left > 0 ? left : align_to; QRect r = rect(); - background->setRect(r); - chart()->legend()->setGeometry(QRect(r.left(), r.top(), r.width(), 45)); - chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 22, r.height() - 80)); - chart()->layout()->invalidate(); + if (align_to != left || r != background->rect()) { + align_to = left; + background->setRect(r); + chart()->legend()->setGeometry(QRect(r.left(), r.top(), r.width(), 45)); + chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 22, r.height() - 80)); + chart()->layout()->invalidate(); + } } void ChartView::updateTitle() { @@ -600,13 +600,14 @@ void ChartView::updateAxisY() { double delta = std::abs(max - min) < 1e-3 ? 1 : (max - min) * 0.05; auto [min_y, max_y, tick_count] = getNiceAxisNumbers(min - delta, max + delta, axis_y->tickCount()); - if (min_y != axis_y->min() || max_y != axis_y->max()) { + if (min_y != axis_y->min() || max_y != axis_y->max() || y_label_width == 0) { axis_y->setRange(min_y, max_y); axis_y->setTickCount(tick_count); QFontMetrics fm(axis_y->labelsFont()); int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; y_label_width = qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 20; // left margin 20 + emit axisYLabelWidthChanged(y_label_width); } } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 77f04534a4..66ef98991f 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -33,7 +33,7 @@ public: void updateSeries(const Signal *sig = nullptr, const std::vector *events = nullptr, bool clear = true); void updatePlot(double cur, double min, double max); void setSeriesType(QAbstractSeries::SeriesType type); - void updatePlotArea(int left = 0); + void updatePlotArea(int left); struct SigItem { QString msg_id; @@ -51,6 +51,7 @@ signals: void zoomIn(double min, double max); void zoomReset(); void remove(); + void axisYLabelWidthChanged(int w); private slots: void msgRemoved(uint32_t address); @@ -78,7 +79,7 @@ private: QXYSeries *createSeries(QAbstractSeries::SeriesType type); void updateSeriesPoints(); - int y_label_width = 50; + int y_label_width = 0; int align_to = 0; QValueAxis *axis_x; QValueAxis *axis_y; @@ -115,7 +116,7 @@ signals: private: void resizeEvent(QResizeEvent *event) override; - void alignCharts(bool force = false); + void alignCharts(); void newChart(); ChartView * createChart(); void removeChart(ChartView *chart); @@ -148,7 +149,7 @@ private: QAction *columns_cb_action; QComboBox *columns_cb; int column_count = 1; - int align_to = 0; + int current_column_count = 0; }; class SeriesSelector : public QDialog { From b462463fbfdc3f4336206bf12b2f07e35473ac3a Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 6 Feb 2023 17:09:13 -0800 Subject: [PATCH 06/40] installer: do not translate git progress strings (#27238) --- selfdrive/ui/installer/installer.cc | 6 +++--- selfdrive/ui/translations/main_de.ts | 12 ------------ selfdrive/ui/translations/main_ja.ts | 12 ------------ selfdrive/ui/translations/main_ko.ts | 12 ------------ selfdrive/ui/translations/main_pt-BR.ts | 12 ------------ selfdrive/ui/translations/main_zh-CHS.ts | 12 ------------ selfdrive/ui/translations/main_zh-CHT.ts | 12 ------------ 7 files changed, 3 insertions(+), 75 deletions(-) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 7d8bbf74e0..1af72c04df 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -141,9 +141,9 @@ void Installer::cachedFetch(const QString &cache) { void Installer::readProgress() { const QVector> stages = { // prefix, weight in percentage - {tr("Receiving objects: "), 91}, - {tr("Resolving deltas: "), 2}, - {tr("Updating files: "), 7}, + {"Receiving objects: ", 91}, + {"Resolving deltas: ", 2}, + {"Updating files: ", 7}, }; auto line = QString(proc.readAllStandardError()); diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 2606df5efe..0bf91bf440 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -312,18 +312,6 @@ Installing... Installiere... - - Receiving objects: - Empfange Objekte: - - - Resolving deltas: - Unterschiede verarbeiten: - - - Updating files: - Dateien aktualisieren: - MapETA diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 16f35dffaa..15f803dd99 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -311,18 +311,6 @@ Installing... インストールしています... - - Receiving objects: - オブジェクトをダウンロードしています: - - - Resolving deltas: - デルタを解決しています: - - - Updating files: - ファイルを更新しています: - MapETA diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index f50d2d9b3f..d0bc63c9e3 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -311,18 +311,6 @@ Installing... 설치중... - - Receiving objects: - 수신중: - - - Resolving deltas: - 델타병합: - - - Updating files: - 파일갱신: - MapETA diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 445de72fc3..75807eebad 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -312,18 +312,6 @@ Installing... Instalando... - - Receiving objects: - Recebendo objetos: - - - Resolving deltas: - Resolvendo deltas: - - - Updating files: - Atualizando arquivos: - MapETA diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 792a918770..ac96d9c711 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -311,18 +311,6 @@ Installing... 正在安装…… - - Receiving objects: - 正在接收: - - - Resolving deltas: - 正在处理: - - - Updating files: - 正在更新文件: - MapETA diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index e8e2ab6bd3..8ccf028f67 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -311,18 +311,6 @@ Installing... 安裝中… - - Receiving objects: - 接收對象: - - - Resolving deltas: - 分析差異: - - - Updating files: - 更新檔案: - MapETA From 8f6521ded3c06b52f0d3c1389e4077be3f184329 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 7 Feb 2023 10:57:37 +0800 Subject: [PATCH 07/40] cabana: improve charts toolbar (#27233) * improve toolbar * add text --- tools/cabana/chartswidget.cc | 19 +++++++++++-------- tools/cabana/chartswidget.h | 2 ++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index a271df9dd7..d482b71cb3 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -22,6 +22,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // toolbar QToolBar *toolbar = new QToolBar(tr("Charts"), this); toolbar->setIconSize({16, 16}); + toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QAction *new_plot_btn = toolbar->addAction(utils::icon("file-plus"), ""); new_plot_btn->setToolTip(tr("New Plot")); @@ -36,17 +37,16 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); toolbar->addWidget(stretch_label); - toolbar->addWidget(new QLabel(tr("Range:"))); - toolbar->addWidget(range_lb = new QLabel(this)); + range_lb_action = toolbar->addWidget(range_lb = new QLabel(this)); range_slider = new QSlider(Qt::Horizontal, this); range_slider->setToolTip(tr("Set the chart range")); range_slider->setRange(1, settings.max_cached_minutes * 60); range_slider->setSingleStep(1); range_slider->setPageStep(60); // 1 min - toolbar->addWidget(range_slider); + range_slider_action = toolbar->addWidget(range_slider); reset_zoom_btn = toolbar->addAction(utils::icon("zoom-out"), ""); - reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); + reset_zoom_btn->setToolTip(tr("Reset zoom")); remove_all_btn = toolbar->addAction(utils::icon("x"), ""); remove_all_btn->setToolTip(tr("Remove all charts")); dock_btn = toolbar->addAction(""); @@ -159,12 +159,15 @@ void ChartsWidget::setMaxChartRange(int value) { } void ChartsWidget::updateToolBar() { - range_lb->setText(QString(" %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0'))); title_label->setText(tr("Charts: %1").arg(charts.size())); - dock_btn->setIcon(utils::icon(docking ? "arrow-up-right" : "arrow-down-left")); - dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); + range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0'))); + range_lb_action->setVisible(!is_zoomed); + range_slider_action->setVisible(!is_zoomed); + reset_zoom_btn->setVisible(is_zoomed); + reset_zoom_btn->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : ""); remove_all_btn->setEnabled(!charts.isEmpty()); - reset_zoom_btn->setEnabled(is_zoomed); + dock_btn->setIcon(utils::icon(docking ? "arrow-up-right-square" : "arrow-down-left-square")); + dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } void ChartsWidget::settingChanged() { diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 66ef98991f..dc92be89f9 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -134,6 +134,8 @@ private: QLabel *title_label; QLabel *range_lb; QSlider *range_slider; + QAction *range_lb_action; + QAction *range_slider_action; bool docking = true; QAction *dock_btn; QAction *reset_zoom_btn; From ae99a6d15d363ac01f970f266dd7bc7a13ae7366 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 7 Feb 2023 10:58:24 +0800 Subject: [PATCH 08/40] cabana: paint points in drawForeground instead of switching opengl mode (#27239) --- tools/cabana/chartswidget.cc | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index d482b71cb3..a5abb8dc64 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -503,7 +503,7 @@ void ChartView::updateSeriesPoints() { // Show points when zoomed in enough for (auto &s : sigs) { auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - auto end = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); + auto end = std::lower_bound(begin, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); int num_points = std::max(end - begin, 1); int pixels_per_point = width() / num_points; @@ -512,21 +512,6 @@ void ChartView::updateSeriesPoints() { ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 1, 8)); } else { s.series->setPointsVisible(pixels_per_point > 20); - - // TODO: On MacOS QChartWidget doesn't work with the OpenGL settings that CameraWidget needs. -#ifndef __APPLE - // OpenGL mode lacks certain features (such as showing points), only use when drawing many points - bool use_opengl = pixels_per_point < 1; - s.series->setUseOpenGL(use_opengl); - - // Qt doesn't properly apply device pixel ratio in OpenGL mode - QApplication *application = static_cast(QApplication::instance()); - float scale = use_opengl ? application->devicePixelRatio() : 1.0; - - QPen pen = s.series->pen(); - pen.setWidth(2.0 * scale); - s.series->setPen(pen); -#endif } } } @@ -592,7 +577,7 @@ void ChartView::updateAxisY() { if (!s.series->isVisible()) continue; auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - auto last = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); + auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); for (auto it = first; it != last; ++it) { if (it->y() < min) min = it->y(); if (it->y() > max) max = it->y(); @@ -783,6 +768,19 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { } } } + + // paint points. OpenGL mode lacks certain features (such as showing points) + painter->setPen(Qt::NoPen); + for (auto &s : sigs) { + if (s.series->useOpenGL() && s.series->isVisible() && s.series->pointsVisible()) { + auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); + auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); + for (auto it = first; it != last; ++it) { + painter->setBrush(s.series->color()); + painter->drawEllipse(chart()->mapToPosition(*it), 4, 4); + } + } + } } QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type) { From 6fb5661abba34971e547941e0f6a6625bef3b75a Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 6 Feb 2023 20:02:30 -0800 Subject: [PATCH 09/40] setup: add serial to request headers (#27237) * setup: add dongle ID to request headers * rename openpilot * fix str * hardware: get_serial * set serial instead * fix typo * static * fixes --- selfdrive/ui/qt/setup/setup.cc | 5 +++++ system/hardware/base.h | 2 ++ system/hardware/tici/hardware.h | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index f1ffabf6c6..231c4c2ad6 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -29,6 +29,9 @@ void Setup::download(QString url) { auto version = util::read_file("/VERSION"); + struct curl_slist *list = NULL; + list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str()); + char tmpfile[] = "/tmp/installer_XXXXXX"; FILE *fp = fdopen(mkstemp(tmpfile), "w"); @@ -38,6 +41,7 @@ void Setup::download(QString url) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); int ret = curl_easy_perform(curl); @@ -50,6 +54,7 @@ void Setup::download(QString url) { emit finished(false); } + curl_slist_free_all(list); curl_easy_cleanup(curl); fclose(fp); } diff --git a/system/hardware/base.h b/system/hardware/base.h index f6e0b42d73..6cfc1d8743 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -16,6 +16,8 @@ public: static int get_voltage() { return 0; }; static int get_current() { return 0; }; + static std::string get_serial() { return "cccccc"; } + static void reboot() {} static void poweroff() {} static void set_brightness(int percent) {} diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index 521a257627..5f6fb2dc50 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -21,6 +21,23 @@ public: static int get_voltage() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()); }; static int get_current() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()); }; + static std::string get_serial() { + static std::string serial(""); + if (serial.empty()) { + std::ifstream stream("/proc/cmdline"); + std::string cmdline; + std::getline(stream, cmdline); + + auto start = cmdline.find("serialno="); + if (start == std::string::npos) { + serial = "cccccc"; + } else { + auto end = cmdline.find(" ", start + 9); + serial = cmdline.substr(start + 9, end - start - 9); + } + } + return serial; + } static void reboot() { std::system("sudo reboot"); }; static void poweroff() { std::system("sudo poweroff"); }; From 55ad7ee3cb50256bdc16c6777e2432bcf9f702eb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 7 Feb 2023 13:14:07 +0800 Subject: [PATCH 10/40] cabana: remove toolbutton padding (#27240) * remove toolbutton padding * fix incorrect checked state in setSeriesType --- tools/cabana/chartswidget.cc | 18 +++++++++--------- tools/cabana/chartswidget.h | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index a5abb8dc64..3cd2c80326 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -22,7 +21,6 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // toolbar QToolBar *toolbar = new QToolBar(tr("Charts"), this); toolbar->setIconSize({16, 16}); - toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QAction *new_plot_btn = toolbar->addAction(utils::icon("file-plus"), ""); new_plot_btn->setToolTip(tr("New Plot")); @@ -45,8 +43,11 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { range_slider->setPageStep(60); // 1 min range_slider_action = toolbar->addWidget(range_slider); - reset_zoom_btn = toolbar->addAction(utils::icon("zoom-out"), ""); + reset_zoom_action = toolbar->addWidget(reset_zoom_btn = new QToolButton()); + reset_zoom_btn->setIcon(utils::icon("zoom-out")); reset_zoom_btn->setToolTip(tr("Reset zoom")); + reset_zoom_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + remove_all_btn = toolbar->addAction(utils::icon("x"), ""); remove_all_btn->setToolTip(tr("Remove all charts")); dock_btn = toolbar->addAction(""); @@ -84,7 +85,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange); QObject::connect(new_plot_btn, &QAction::triggered, this, &ChartsWidget::newChart); QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll); - QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset); + QObject::connect(reset_zoom_btn, &QToolButton::clicked, this, &ChartsWidget::zoomReset); QObject::connect(columns_cb, SIGNAL(activated(int)), SLOT(setColumnCount(int))); QObject::connect(&settings, &Settings::changed, this, &ChartsWidget::settingChanged); QObject::connect(dock_btn, &QAction::triggered, [this]() { @@ -163,8 +164,8 @@ void ChartsWidget::updateToolBar() { range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0'))); range_lb_action->setVisible(!is_zoomed); range_slider_action->setVisible(!is_zoomed); - reset_zoom_btn->setVisible(is_zoomed); - reset_zoom_btn->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : ""); + reset_zoom_action->setVisible(is_zoomed); + reset_zoom_btn->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : ""); remove_all_btn->setEnabled(!charts.isEmpty()); dock_btn->setIcon(utils::icon(docking ? "arrow-up-right-square" : "arrow-down-left-square")); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); @@ -799,11 +800,10 @@ QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type) { } void ChartView::setSeriesType(QAbstractSeries::SeriesType type) { + line_series_action->setChecked(type == QAbstractSeries::SeriesTypeLine); + scatter_series_action->setChecked(type == QAbstractSeries::SeriesTypeScatter); if (type != series_type) { series_type = type; - line_series_action->setChecked(type == QAbstractSeries::SeriesTypeLine); - scatter_series_action->setChecked(type == QAbstractSeries::SeriesTypeScatter); - for (auto &s : sigs) { chart()->removeSeries(s.series); s.series->deleteLater(); diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index dc92be89f9..3e32dc069f 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -138,7 +139,8 @@ private: QAction *range_slider_action; bool docking = true; QAction *dock_btn; - QAction *reset_zoom_btn; + QAction *reset_zoom_action; + QToolButton *reset_zoom_btn; QAction *remove_all_btn; QGridLayout *charts_layout; QList charts; From 074e74ad08e199f189d4d84f73b2a9df1061eb97 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 7 Feb 2023 11:37:03 -0800 Subject: [PATCH 11/40] setup: check installer is elf (#27241) --- selfdrive/ui/qt/setup/setup.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 231c4c2ad6..0617214f08 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -20,6 +20,17 @@ const std::string USER_AGENT = "AGNOSSetup-"; const QString DASHCAM_URL = "https://dashcam.comma.ai"; +bool is_elf(char *fname) { + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) { + return false; + } + char buf[4]; + size_t n = fread(buf, 1, 4, fp); + fclose(fp); + return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F'; +} + void Setup::download(QString url) { CURL *curl = curl_easy_init(); if (!curl) { @@ -44,10 +55,9 @@ void Setup::download(QString url) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); int ret = curl_easy_perform(curl); - long res_status = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status); - if (ret == CURLE_OK && res_status == 200) { + if (ret == CURLE_OK && res_status == 200 && is_elf(tmpfile)) { rename(tmpfile, "/tmp/installer"); emit finished(true); } else { From 1a0a6387409203e1a2611eb35882de7a931ca703 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 7 Feb 2023 11:37:25 -0800 Subject: [PATCH 12/40] setup: replace software selection widget with input dialog (#27236) * setup: replace software selection widget with input dialog * revert https://github.com/commaai/openpilot/pull/21617/commits/3f7047651c31b209c9c4a0b8ff7bd408aca3f9e5 --- selfdrive/ui/qt/setup/setup.cc | 116 +++-------------------- selfdrive/ui/qt/setup/setup.h | 1 - selfdrive/ui/translations/main_de.ts | 12 --- selfdrive/ui/translations/main_ja.ts | 12 --- selfdrive/ui/translations/main_ko.ts | 12 --- selfdrive/ui/translations/main_pt-BR.ts | 12 --- selfdrive/ui/translations/main_zh-CHS.ts | 12 --- selfdrive/ui/translations/main_zh-CHT.ts | 12 --- 8 files changed, 14 insertions(+), 175 deletions(-) diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 0617214f08..392be68a12 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -185,7 +185,20 @@ QWidget * Setup::network_setup() { QPushButton *cont = new QPushButton(); cont->setObjectName("navBtn"); cont->setProperty("primary", true); - QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); + QObject::connect(cont, &QPushButton::clicked, [=]() { + auto w = currentWidget(); + QTimer::singleShot(0, [=]() { + setCurrentWidget(downloading_widget); + }); + QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); + if (!url.isEmpty()) { + QTimer::singleShot(1000, this, [=]() { + download(url); + }); + } else { + setCurrentWidget(w); + } + }); blayout->addWidget(cont); // setup timer for testing internet connection @@ -212,106 +225,6 @@ QWidget * Setup::network_setup() { return widget; } -QWidget * radio_button(QString title, QButtonGroup *group) { - QPushButton *btn = new QPushButton(title); - btn->setCheckable(true); - group->addButton(btn); - btn->setStyleSheet(R"( - QPushButton { - height: 230; - padding-left: 100px; - padding-right: 100px; - text-align: left; - font-size: 80px; - font-weight: 400; - border-radius: 10px; - background-color: #4F4F4F; - } - QPushButton:checked { - background-color: #465BEA; - } - )"); - - // checkmark icon - QPixmap pix(":/img_circled_check.svg"); - btn->setIcon(pix); - btn->setIconSize(QSize(0, 0)); - btn->setLayoutDirection(Qt::RightToLeft); - QObject::connect(btn, &QPushButton::toggled, [=](bool checked) { - btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0)); - }); - return btn; -} - -QWidget * Setup::software_selection() { - QWidget *widget = new QWidget(); - QVBoxLayout *main_layout = new QVBoxLayout(widget); - main_layout->setContentsMargins(55, 50, 55, 50); - main_layout->setSpacing(0); - - // title - QLabel *title = new QLabel(tr("Choose Software to Install")); - title->setStyleSheet("font-size: 90px; font-weight: 500;"); - main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); - - main_layout->addSpacing(50); - - // dashcam + custom radio buttons - QButtonGroup *group = new QButtonGroup(widget); - group->setExclusive(true); - - QWidget *dashcam = radio_button(tr("Dashcam"), group); - main_layout->addWidget(dashcam); - - main_layout->addSpacing(30); - - QWidget *custom = radio_button(tr("Custom Software"), group); - main_layout->addWidget(custom); - - main_layout->addStretch(); - - // back + continue buttons - QHBoxLayout *blayout = new QHBoxLayout; - main_layout->addLayout(blayout); - blayout->setSpacing(50); - - QPushButton *back = new QPushButton(tr("Back")); - back->setObjectName("navBtn"); - QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); - blayout->addWidget(back); - - QPushButton *cont = new QPushButton(tr("Continue")); - cont->setObjectName("navBtn"); - cont->setEnabled(false); - cont->setProperty("primary", true); - blayout->addWidget(cont); - - QObject::connect(cont, &QPushButton::clicked, [=]() { - auto w = currentWidget(); - QTimer::singleShot(0, [=]() { - setCurrentWidget(downloading_widget); - }); - QString url = DASHCAM_URL; - if (group->checkedButton() != dashcam) { - url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); - } - if (!url.isEmpty()) { - QTimer::singleShot(1000, this, [=]() { - download(url); - }); - } else { - setCurrentWidget(w); - } - }); - - connect(group, QOverload::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) { - btn->setChecked(true); - cont->setEnabled(true); - }); - - return widget; -} - QWidget * Setup::downloading() { QWidget *widget = new QWidget(); QVBoxLayout *main_layout = new QVBoxLayout(widget); @@ -387,7 +300,6 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) { addWidget(getting_started()); addWidget(network_setup()); - addWidget(software_selection()); downloading_widget = downloading(); addWidget(downloading_widget); diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h index 8027e8bd4f..f990b5a6cb 100644 --- a/selfdrive/ui/qt/setup/setup.h +++ b/selfdrive/ui/qt/setup/setup.h @@ -14,7 +14,6 @@ private: QWidget *low_voltage(); QWidget *getting_started(); QWidget *network_setup(); - QWidget *software_selection(); QWidget *downloading(); QWidget *download_failed(); diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 0bf91bf440..b40812af7c 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -674,18 +674,6 @@ location set Waiting for internet Auf Internet warten - - Choose Software to Install - Software zum installieren auswählen - - - Dashcam - Dashcam - - - Custom Software - Spezifische Software - Enter URL URL eingeben diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 15f803dd99..ed08a52df5 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -672,18 +672,6 @@ location set Waiting for internet インターネット接続を待機中 - - Choose Software to Install - インストールするソフトウェアを選択してください - - - Dashcam - ドライブレコーダー - - - Custom Software - カスタムソフトウェア - Enter URL URL を入力 diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index d0bc63c9e3..7b742a5048 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -672,18 +672,6 @@ location set Waiting for internet 네트워크 접속을 기다립니다 - - Choose Software to Install - 설치할 소프트웨어를 선택하세요 - - - Dashcam - Dashcam - - - Custom Software - Custom Software - Enter URL URL 입력 diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 75807eebad..a25c51de3c 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -676,18 +676,6 @@ trabalho definido Waiting for internet Esperando pela internet - - Choose Software to Install - Escolher Software para Instalar - - - Dashcam - Dashcam - - - Custom Software - Sofware Customizado - Enter URL Preencher URL diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index ac96d9c711..68c1bb766f 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -670,18 +670,6 @@ location set Waiting for internet 等待网络连接 - - Choose Software to Install - 选择要安装的软件 - - - Dashcam - Dashcam(行车记录仪) - - - Custom Software - 自定义软件 - Enter URL 输入网址 diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 8ccf028f67..71315e118f 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -672,18 +672,6 @@ location set Waiting for internet 連接至網路中 - - Choose Software to Install - 選擇要安裝的軟體 - - - Dashcam - 行車記錄器 - - - Custom Software - 定制的軟體 - Enter URL 輸入網址 From 01bdf382c4bd8a30444c0bde3b737a95f5813f42 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 7 Feb 2023 12:52:50 -0800 Subject: [PATCH 13/40] CI: temporarily remove timeouts --- .github/workflows/prebuilt.yaml | 1 - .github/workflows/release.yaml | 1 - .github/workflows/selfdrive_tests.yaml | 20 -------------------- .github/workflows/tools_tests.yaml | 4 ---- 4 files changed, 26 deletions(-) diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index c8b4c51e38..1fd093e2ac 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -16,7 +16,6 @@ jobs: build_prebuilt: name: build prebuilt runs-on: ubuntu-20.04 - timeout-minutes: 60 if: github.repository == 'commaai/openpilot' env: IMAGE_NAME: openpilot-prebuilt diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8df89dcc38..7f83f61e0a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,7 +8,6 @@ jobs: build_masterci: name: build master-ci runs-on: ubuntu-20.04 - timeout-minutes: 60 if: github.repository == 'commaai/openpilot' steps: - name: Wait for green check mark diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index d5be221303..834b2141d0 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -32,7 +32,6 @@ jobs: build_release: name: build release runs-on: ubuntu-20.04 - timeout-minutes: 30 env: STRIPPED_DIR: /tmp/releasepilot steps: @@ -66,7 +65,6 @@ jobs: build_all: name: build all runs-on: ubuntu-20.04 - timeout-minutes: 30 steps: - uses: actions/checkout@v3 with: @@ -84,7 +82,6 @@ jobs: #build_mac: # name: build macos # runs-on: macos-latest - # timeout-minutes: 60 # steps: # - uses: actions/checkout@v3 # with: @@ -141,7 +138,6 @@ jobs: docker_push: name: docker push runs-on: ubuntu-20.04 - timeout-minutes: 22 if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast steps: @@ -150,14 +146,12 @@ jobs: submodules: true - name: Build Docker image run: eval "$BUILD" - timeout-minutes: 13 - name: Push to container registry run: | $DOCKER_LOGIN docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest - name: Build CL Docker image run: eval "$BUILD_CL" - timeout-minutes: 4 - name: Push to container registry run: | $DOCKER_LOGIN @@ -166,7 +160,6 @@ jobs: static_analysis: name: static analysis runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -174,13 +167,11 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: pre-commit - timeout-minutes: 5 run: ${{ env.RUN }} "pre-commit run --all" valgrind: name: valgrind runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -198,7 +189,6 @@ jobs: unit_tests: name: unit tests runs-on: ubuntu-20.04 - timeout-minutes: 30 steps: - uses: actions/checkout@v3 with: @@ -207,7 +197,6 @@ jobs: - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Run unit tests - timeout-minutes: 15 run: | ${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \ $UNIT_TEST common && \ @@ -242,7 +231,6 @@ jobs: process_replay: name: process replay runs-on: ubuntu-20.04 - timeout-minutes: 25 steps: - uses: actions/checkout@v3 with: @@ -280,7 +268,6 @@ jobs: test_modeld: name: model tests runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -293,12 +280,10 @@ jobs: run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run model replay with ONNX - timeout-minutes: 2 run: | ${{ env.RUN_CL }} "ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ coverage xml" - name: Run unit tests - timeout-minutes: 5 run: | ${{ env.RUN_CL }} "$UNIT_TEST selfdrive/modeld && \ coverage xml" @@ -308,7 +293,6 @@ jobs: test_longitudinal: name: longitudinal runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -323,7 +307,6 @@ jobs: cd selfdrive/test/longitudinal_maneuvers && \ coverage run ./test_longitudinal.py && \ coverage xml" - timeout-minutes: 2 - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v2 - uses: actions/upload-artifact@v2 @@ -336,7 +319,6 @@ jobs: test_cars: name: cars runs-on: ubuntu-20.04 - timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -355,7 +337,6 @@ jobs: - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test car models - timeout-minutes: 12 run: | ${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \ coverage xml && \ @@ -369,7 +350,6 @@ jobs: car_docs_diff: name: PR comments runs-on: ubuntu-20.04 - timeout-minutes: 20 if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 94cc3c2580..3b32d3c790 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -30,7 +30,6 @@ jobs: plotjuggler: name: plotjuggler runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -38,7 +37,6 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Unit test - timeout-minutes: 2 run: | ${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \ apt-get update && \ @@ -49,7 +47,6 @@ jobs: simulator: name: simulator runs-on: ubuntu-20.04 - timeout-minutes: 30 env: IMAGE_NAME: openpilot-sim if: github.repository == 'commaai/openpilot' @@ -74,7 +71,6 @@ jobs: docs: name: build docs runs-on: ubuntu-20.04 - timeout-minutes: 25 steps: - uses: actions/checkout@v3 with: From 9af82c090e1bcb0d76b4c2468d538c75b6e9c650 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 7 Feb 2023 15:01:47 -0800 Subject: [PATCH 14/40] ublox_day_fix --- selfdrive/locationd/ublox_msg.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index 3ff768ba06..33ac2fe088 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -358,9 +358,6 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ eph.setAge(data->e_n()); eph.setP4(data->p4()); eph.setSvURA(glonass_URA_lookup.at(data->f_t())); - if (msg->sv_id() != data->n()) { - LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) - } eph.setSvType(data->m()); } @@ -374,6 +371,13 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ // the year can be fetched later in laika (note rollovers and leap year) uint8_t n_4 = data->n_4(); uint16_t year = get_glonass_year(n_4, current_day); + if (current_day > 1461) { + // impossible day within last 4 year, reject ephemeris + // TODO: check if this can be detected via hamming code + LOGE("INVALID DATA: current day out of range: %d, %d", current_day, n_4); + glonass_strings[msg->sv_id()].clear(); + return kj::Array(); + } uint16_t last_leap_year = 1996 + 4*(n_4-1); uint16_t days_till_this_year = (year - last_leap_year)*365; From 4f6fbdf590fdcd9e1327cb42905fa17542d64f73 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 7 Feb 2023 15:02:21 -0800 Subject: [PATCH 15/40] Revert "ublox_day_fix" This reverts commit 9af82c090e1bcb0d76b4c2468d538c75b6e9c650. --- selfdrive/locationd/ublox_msg.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index 33ac2fe088..3ff768ba06 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -358,6 +358,9 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ eph.setAge(data->e_n()); eph.setP4(data->p4()); eph.setSvURA(glonass_URA_lookup.at(data->f_t())); + if (msg->sv_id() != data->n()) { + LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) + } eph.setSvType(data->m()); } @@ -371,13 +374,6 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ // the year can be fetched later in laika (note rollovers and leap year) uint8_t n_4 = data->n_4(); uint16_t year = get_glonass_year(n_4, current_day); - if (current_day > 1461) { - // impossible day within last 4 year, reject ephemeris - // TODO: check if this can be detected via hamming code - LOGE("INVALID DATA: current day out of range: %d, %d", current_day, n_4); - glonass_strings[msg->sv_id()].clear(); - return kj::Array(); - } uint16_t last_leap_year = 1996 + 4*(n_4-1); uint16_t days_till_this_year = (year - last_leap_year)*365; From 6330cdd88513d9dad3e5ab00811c4a5223924423 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 7 Feb 2023 18:49:06 -0700 Subject: [PATCH 16/40] GPS: add tow to gps ephemeris (#27246) * add tow to gps ephemeris * bump cereal * update refs --------- Co-authored-by: Kurt Nistelberger --- cereal | 2 +- selfdrive/locationd/ublox_msg.cc | 1 + selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cereal b/cereal index bdbac40160..fa3e77b7c8 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit bdbac40160a550c88e481a3bf700a1016901bd9b +Subproject commit fa3e77b7c8eee8752f19427b34adcb1ae5c70ec5 diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index 3ff768ba06..877ace9a06 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -213,6 +213,7 @@ kj::Array UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *m eph.setAf1(subframe_1->af_1() * pow(2, -43)); eph.setAf0(subframe_1->af_0() * pow(2, -31)); eph.setSvHealth(subframe_1->sv_health()); + eph.setTowCount(subframe.how()->tow_count()); iodc_lsb = subframe_1->iodc_lsb(); } diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 2c66c7075c..f98caa80a7 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -7c2a98f4a94b6dded6a90d8913ab85a63069c186 \ No newline at end of file +d862b2c7d383d859db30d6c1cc665c29c7a2a9f3 \ No newline at end of file From 5a0cbc35a544f448e8db1e1578de7607b39a3e76 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 7 Feb 2023 18:49:51 -0700 Subject: [PATCH 17/40] Ubloxd: glonass ephem parsing (#27248) temp fix Co-authored-by: Kurt Nistelberger --- selfdrive/locationd/ublox_msg.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index 877ace9a06..8fa721a091 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -359,9 +359,6 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ eph.setAge(data->e_n()); eph.setP4(data->p4()); eph.setSvURA(glonass_URA_lookup.at(data->f_t())); - if (msg->sv_id() != data->n()) { - LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) - } eph.setSvType(data->m()); } @@ -375,6 +372,13 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ // the year can be fetched later in laika (note rollovers and leap year) uint8_t n_4 = data->n_4(); uint16_t year = get_glonass_year(n_4, current_day); + if (current_day > 1461) { + // impossible day within last 4 year, reject ephemeris + // TODO: check if this can be detected via hamming code + LOGE("INVALID DATA: current day out of range: %d, %d", current_day, n_4); + glonass_strings[msg->sv_id()].clear(); + return kj::Array(); + } uint16_t last_leap_year = 1996 + 4*(n_4-1); uint16_t days_till_this_year = (year - last_leap_year)*365; From 9799359ffdc56c379955a892a92961f559f4bc19 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 7 Feb 2023 17:54:23 -0800 Subject: [PATCH 18/40] bump laika --- laika_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laika_repo b/laika_repo index b30afe44d2..278b44ba8c 160000 --- a/laika_repo +++ b/laika_repo @@ -1 +1 @@ -Subproject commit b30afe44d283072b6ee0ea17f290cca5d002baf7 +Subproject commit 278b44ba8c2dec28adc5b30cce13dabc195f15fc From 4d05b5b02a706eac246c6f5b545ecfc7513a6525 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 7 Feb 2023 20:11:36 -0800 Subject: [PATCH 19/40] Bump panda (#27249) * bumppanda * bumptomaster * bumppandaagain --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index e7f36a2992..82151aaffe 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e7f36a2992b353d7d66fbb697d166d4de8718c99 +Subproject commit 82151aaffefbb460887d09f4f5e29092d3b669ba From fe64e853639e00d6d2cc8910d66b9b8c6f56c808 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 7 Feb 2023 21:25:46 -0800 Subject: [PATCH 20/40] readd slot number logging --- selfdrive/locationd/ublox_msg.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index 8fa721a091..b746989466 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -359,6 +359,9 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ eph.setAge(data->e_n()); eph.setP4(data->p4()); eph.setSvURA(glonass_URA_lookup.at(data->f_t())); + if (msg->sv_id() != data->n()) { + LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) + } eph.setSvType(data->m()); } From 583057aa413f533e761ce869abd69f2fedef39b9 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 8 Feb 2023 13:46:59 +0800 Subject: [PATCH 21/40] cabana: add space between signal and msg name (#27252) --- tools/cabana/chartswidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 3cd2c80326..f3982bb7a2 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -485,7 +485,7 @@ void ChartView::updateTitle() { } for (auto &s : sigs) { auto decoration = s.series->isVisible() ? "none" : "line-through"; - s.series->setName(QString("%2%3 %4").arg(decoration).arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id)); + s.series->setName(QString("%2 %3 %4").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id)); } } From 86d86fa9dafc823a9cccd9c06ba995cd510d5535 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 7 Feb 2023 23:38:05 -0800 Subject: [PATCH 22/40] GM: reduce steering message rate (#27201) * Lower active rate and raise limits to compensate * bump panda * bump panda * Update ref_commit * bump panda to master --- panda | 2 +- selfdrive/car/gm/values.py | 6 +++--- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/panda b/panda index 82151aaffe..7d27eb10fb 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 82151aaffefbb460887d09f4f5e29092d3b669ba +Subproject commit 7d27eb10fb32095e73f60c59a123cae08cc53719 diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index e633ec5f62..7628d8420b 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -11,10 +11,10 @@ Ecu = car.CarParams.Ecu class CarControllerParams: STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output - STEER_STEP = 2 # Active control frames per command (50hz) + STEER_STEP = 3 # Active control frames per command (~33hz) INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz) - STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness - STEER_DELTA_DOWN = 17 + STEER_DELTA_UP = 10 # Delta rates require review due to observed EPS weakness + STEER_DELTA_DOWN = 25 STEER_DRIVER_ALLOWANCE = 50 STEER_DRIVER_MULTIPLIER = 4 STEER_DRIVER_FACTOR = 100 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f98caa80a7..170beafe88 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -d862b2c7d383d859db30d6c1cc665c29c7a2a9f3 \ No newline at end of file +a6b17ab903022b42677abe2968f3c6e50c331980 From 5b9f2c3fcd09409211f518b9074afa046a1915d6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Feb 2023 00:09:49 -0800 Subject: [PATCH 23/40] GM: refactor steering control code (#27251) * hopefully easier to understand? * space * no confusing elif, move comment to relevant part * better --- selfdrive/car/gm/carcontroller.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 56f6ff5a31..affbcdc0fa 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -49,25 +49,26 @@ class CarController: can_sends = [] # Steering (Active: 50Hz, inactive: 10Hz) - # Attempt to sync with camera on startup at 50Hz, first few msgs are blocked - init_lka_counter = not self.sent_lka_steering_cmd - # Also send at 50Hz until we're in sync with camera so counters align when relay closes, preventing a fault - # openpilot can subtly drift, so this is activated throughout a drive to stay synced - out_of_sync = self.lka_steering_cmd_counter % 4 != (CS.camera_lka_steering_cmd_counter + 1) % 4 - sync_steer = (init_lka_counter or out_of_sync) and self.CP.networkLocation == NetworkLocation.fwdCamera + steer_step = self.params.STEER_STEP if CC.latActive else self.params.INACTIVE_STEER_STEP - steer_step = self.params.INACTIVE_STEER_STEP - if CC.latActive or sync_steer: - steer_step = self.params.STEER_STEP + if self.CP.networkLocation == NetworkLocation.fwdCamera: + # Also send at 50Hz: + # - on startup, first few msgs are blocked + # - until we're in sync with camera so counters align when relay closes, preventing a fault. + # openpilot can subtly drift, so this is activated throughout a drive to stay synced + out_of_sync = self.lka_steering_cmd_counter % 4 != (CS.camera_lka_steering_cmd_counter + 1) % 4 + if not self.sent_lka_steering_cmd or out_of_sync: + steer_step = self.params.STEER_STEP - # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the - # next Panda loopback confirmation in the current CS frame. if CS.loopback_lka_steering_cmd_updated: self.lka_steering_cmd_counter += 1 self.sent_lka_steering_cmd = True - elif (self.frame - self.last_steer_frame) >= steer_step: + + # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just + # received the ASCMLKASteeringCmd loopback confirmation in the current CS frame + if (self.frame - self.last_steer_frame) >= steer_step and not CS.loopback_lka_steering_cmd_updated: # Initialize ASCMLKASteeringCmd counter using the camera until we get a msg on the bus - if init_lka_counter: + if not self.sent_lka_steering_cmd: self.lka_steering_cmd_counter = CS.pt_lka_steering_cmd_counter + 1 if CC.latActive: From 303e21cbccc5bbbc8450d6a09221b77e127631b7 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 9 Feb 2023 04:40:22 +0800 Subject: [PATCH 24/40] cabana: improve the welcome page (#27261) --- tools/cabana/detailwidget.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 46f7a148f4..a7ad572dc1 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -232,9 +232,14 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) { return hlayout; }; + auto lb = new QLabel(tr("<-Select a message to to view details")); + lb->setAlignment(Qt::AlignHCenter); + main_layout->addWidget(lb); main_layout->addLayout(newShortcutRow("Pause", "Space")); main_layout->addLayout(newShortcutRow("Help", "Alt + H")); main_layout->addStretch(0); setStyleSheet("QLabel{color:darkGray;}"); + setBackgroundRole(QPalette::Base); + setAutoFillBackground(true); } From 80bf755b075b2b56373b4ca49c2c95d1837e7648 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 9 Feb 2023 04:40:43 +0800 Subject: [PATCH 25/40] cabana: set correct geometry for legend (#27260) --- tools/cabana/chartswidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index f3982bb7a2..4f1f293615 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -310,7 +310,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { axis_y->setLabelFormat("%.1f"); chart->addAxis(axis_x, Qt::AlignBottom); chart->addAxis(axis_y, Qt::AlignLeft); - chart->legend()->layout()->setContentsMargins(0, 0, 40, 0); + chart->legend()->layout()->setContentsMargins(16, 0, 40, 0); chart->legend()->setShowToolTips(true); chart->setMargins({0, 0, 0, 0}); From 56bc5b39e3eae122d6b8f85c72c023665b32d89f Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Wed, 8 Feb 2023 16:00:30 -0500 Subject: [PATCH 26/40] VW MQB: Add FW for 2018 Volkswagen Tiguan (#27264) --- selfdrive/car/volkswagen/values.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 9f6dee2689..72fcc5d92d 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -729,6 +729,7 @@ FW_VERSIONS = { }, CAR.TIGUAN_MK2: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8703N906026D \xf1\x893680', b'\xf1\x8704E906027NB\xf1\x899504', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906027G \xf1\x899893', @@ -746,12 +747,14 @@ FW_VERSIONS = { b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DL300011N \xf1\x892001', b'\xf1\x870DL300011N \xf1\x892012', + b'\xf1\x870DL300012P \xf1\x892103', b'\xf1\x870DL300013A \xf1\x893005', b'\xf1\x870DL300013G \xf1\x892119', b'\xf1\x870DL300013G \xf1\x892120', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', + b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100', @@ -763,6 +766,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', + b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1', b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', @@ -771,6 +775,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572J \xf1\x890156', + b'\xf1\x872Q0907572M \xf1\x890233', b'\xf1\x872Q0907572Q \xf1\x890342', b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572T \xf1\x890383', From fcaa6fb06fd68a71b129487d80ee00918695fc5b Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Wed, 8 Feb 2023 22:03:23 +0100 Subject: [PATCH 27/40] cabana: extend color palette, make chart colors match signal view (#27258) * cabana: extend color palette, make chart colors match signal view * vary saturation too * cleanup --- tools/cabana/binaryview.cc | 4 +--- tools/cabana/chartswidget.cc | 1 + tools/cabana/historylog.cc | 2 +- tools/cabana/signaledit.cc | 2 +- tools/cabana/util.cc | 15 +++++++++++++++ tools/cabana/util.h | 8 +++----- 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 4d7b3148c6..ddad4cd446 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -173,7 +173,6 @@ void BinaryViewModel::refresh() { if ((dbc_msg = dbc()->msg(msg_id))) { row_count = dbc_msg->size; items.resize(row_count * column_count); - int i = 0; for (auto sig : dbc_msg->getSignals()) { auto [start, end] = getSignalRange(sig); for (int j = start; j <= end; ++j) { @@ -185,10 +184,9 @@ void BinaryViewModel::refresh() { } if (j == start) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; if (j == end) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; - items[idx].bg_color = getColor(i); + items[idx].bg_color = getColor(sig); items[idx].sigs.push_back(sig); } - ++i; } } else { row_count = can->lastMessage(msg_id).dat.size(); diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 4f1f293615..4faca8314b 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -525,6 +525,7 @@ void ChartView::updateSeries(const Signal *sig, const std::vector *even s.vals.clear(); s.vals.reserve(settings.max_cached_minutes * 60 * 100); // [n]seconds * 100hz s.last_value_mono_time = 0; + s.series->setColor(getColor(sig)); } struct Chunk { diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 95bd94e76b..3a3397af42 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -49,7 +49,7 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i } return show_signals ? QString::fromStdString(sigs[section - 1]->name).replace('_', ' ') : "Data"; } else if (role == Qt::BackgroundRole && section > 0 && show_signals) { - return QBrush(QColor(getColor(section - 1))); + return QBrush(getColor(sigs[section - 1])); } } return {}; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index b871ffa21a..2f1a968645 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -279,7 +279,7 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } // color label - auto bg_color = QColor(getColor(item->row())); + auto bg_color = getColor(item->sig); QRect rc{option.rect.left() + 3, option.rect.top(), 22, option.rect.height()}; painter->setPen(Qt::NoPen); painter->setBrush(item->highlight ? bg_color.darker(125) : bg_color); diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index f9d19dfda3..d978e4aa12 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -3,6 +3,10 @@ #include #include #include +#include + +#include +#include #include "selfdrive/ui/qt/util.h" @@ -96,6 +100,17 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem & } } +QColor getColor(const Signal *sig) { + float h = 19 * (float)sig->lsb / 64.0; + h = fmod(h, 1.0); + + size_t hash = qHash(QString::fromStdString(sig->name)); + float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0; + float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0; + + return QColor::fromHsvF(h, s, v); +} + NameValidator::NameValidator(QObject *parent) : QRegExpValidator(QRegExp("^(\\w+)"), parent) { } QValidator::State NameValidator::validate(QString &input, int &pos) const { diff --git a/tools/cabana/util.h b/tools/cabana/util.h index b76652f4b0..af5032fd42 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -7,6 +7,8 @@ #include #include +#include "opendbc/can/common_dbc.h" + class ChangeTracker { public: void compute(const QByteArray &dat, double ts, uint32_t freq); @@ -33,11 +35,7 @@ public: inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); } inline char toHex(uint value) { return "0123456789ABCDEF"[value & 0xF]; } -inline const QString &getColor(int i) { - // TODO: add more colors - static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; - return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; -} +QColor getColor(const Signal *sig); class NameValidator : public QRegExpValidator { Q_OBJECT From 99f12ca7f31a1d6ac8a54254f30d4a31ce628268 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 9 Feb 2023 05:29:16 +0800 Subject: [PATCH 28/40] cabana: fix coredump caused by getColor in updateSeries (#27265) --- tools/cabana/chartswidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 4faca8314b..8debb75153 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -525,7 +525,7 @@ void ChartView::updateSeries(const Signal *sig, const std::vector *even s.vals.clear(); s.vals.reserve(settings.max_cached_minutes * 60 * 100); // [n]seconds * 100hz s.last_value_mono_time = 0; - s.series->setColor(getColor(sig)); + s.series->setColor(getColor(s.sig)); } struct Chunk { From f4f081c56209a2fc0669e76e881821b43830462c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 9 Feb 2023 07:18:07 +0800 Subject: [PATCH 29/40] cabana: imporve series selector dialog (#27266) * imporve series selector dialog * typo --- tools/cabana/chartswidget.cc | 170 +++++++++++++++++------------------ tools/cabana/chartswidget.h | 26 +++--- 2 files changed, 96 insertions(+), 100 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 8debb75153..134a162679 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -251,12 +252,14 @@ void ChartsWidget::resizeEvent(QResizeEvent *event) { } void ChartsWidget::newChart() { - SeriesSelector dlg(this); + SeriesSelector dlg(tr("New Chart"), this); if (dlg.exec() == QDialog::Accepted) { - QList series_list = dlg.series(); - if (!series_list.isEmpty()) { + auto items = dlg.seletedItems(); + if (!items.isEmpty()) { auto c = createChart(); - c->addSeries(series_list); + for (auto it : items) { + c->addSeries(it->msg_id, it->sig); + } } } } @@ -425,33 +428,24 @@ void ChartView::msgRemoved(uint32_t address) { } } -void ChartView::addSeries(const QList &series_list) { - for (auto &s : series_list) { - if (auto m = dbc()->msg(s[0])) { - auto it = m->sigs.find(s[2]); - if (it != m->sigs.end() && !hasSeries(s[0], &(it->second))) { - addSeries(s[0], &(it->second)); - } - } - } -} - void ChartView::manageSeries() { - SeriesSelector dlg(this); + SeriesSelector dlg(tr("Mange Chart"), this); for (auto &s : sigs) { - dlg.addSeries(s.msg_id, msgName(s.msg_id), QString::fromStdString(s.sig->name)); + dlg.addSelected(s.msg_id, s.sig); } - - int ret = dlg.exec(); - if (ret == QDialog::Accepted) { - QList series_list = dlg.series(); - if (series_list.isEmpty()) { + if (dlg.exec() == QDialog::Accepted) { + auto items = dlg.seletedItems(); + if (items.isEmpty()) { emit remove(); } else { - addSeries(series_list); + for (auto s : items) { + if (!hasSeries(s->msg_id, s->sig)) { + addSeries(s->msg_id, s->sig); + } + } for (auto it = sigs.begin(); it != sigs.end(); /**/) { - bool exists = std::any_of(series_list.cbegin(), series_list.cend(), [&](auto &s) { - return s[0] == it->msg_id && s[2] == it->sig->name.c_str(); + bool exists = std::any_of(items.cbegin(), items.cend(), [&](auto &s) { + return s->msg_id == it->msg_id && s->sig == it->sig; }); it = exists ? ++it : removeItem(it); } @@ -737,11 +731,9 @@ void ChartView::dropEvent(QDropEvent *event) { event->accept(); } else { ChartView *source_chart = (ChartView *)event->source(); - QList series; for (auto &s : source_chart->sigs) { - series.push_back({s.msg_id, msgName(s.msg_id), QString::fromStdString(s.sig->name)}); + addSeries(s.msg_id, s.sig); } - addSeries(series); emit source_chart->remove(); event->acceptProposedAction(); } @@ -836,40 +828,37 @@ void ChartView::handleMarkerClicked() { // SeriesSelector -SeriesSelector::SeriesSelector(QWidget *parent) { - setWindowTitle(tr("Manage Chart Series")); - QHBoxLayout *contents_layout = new QHBoxLayout(); +SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent) { + setWindowTitle(title); + QGridLayout *main_layout = new QGridLayout(this); - QVBoxLayout *left_layout = new QVBoxLayout(); - left_layout->addWidget(new QLabel(tr("Select Signals:"))); - - msgs_combo = new QComboBox(this); + // left column + main_layout->addWidget(new QLabel(tr("Available Signals")), 0, 0); + main_layout->addWidget(msgs_combo = new QComboBox(this), 1, 0); msgs_combo->setEditable(true); - msgs_combo->lineEdit()->setPlaceholderText(tr("Select Msg")); + msgs_combo->lineEdit()->setPlaceholderText(tr("Select a msg...")); msgs_combo->setInsertPolicy(QComboBox::NoInsert); msgs_combo->completer()->setCompletionMode(QCompleter::PopupCompletion); msgs_combo->completer()->setFilterMode(Qt::MatchContains); - left_layout->addWidget(msgs_combo); - sig_list = new QListWidget(this); - sig_list->setSortingEnabled(true); - sig_list->setToolTip(tr("Double click on an item to add signal to chart")); - left_layout->addWidget(sig_list); + main_layout->addWidget(available_list = new QListWidget(this), 2, 0); - QVBoxLayout *right_layout = new QVBoxLayout(); - right_layout->addWidget(new QLabel(tr("Chart Signals:"))); - chart_series = new QListWidget(this); - chart_series->setSortingEnabled(true); - chart_series->setToolTip(tr("Double click on an item to remove signal from chart")); - right_layout->addWidget(chart_series); - contents_layout->addLayout(left_layout); - contents_layout->addLayout(right_layout); + // buttons + QVBoxLayout *btn_layout = new QVBoxLayout(); + QPushButton *add_btn = new QPushButton(utils::icon("chevron-right"), "", this); + QPushButton *remove_btn = new QPushButton(utils::icon("chevron-left"), "", this); + btn_layout->addStretch(0); + btn_layout->addWidget(add_btn); + btn_layout->addWidget(remove_btn); + btn_layout->addStretch(0); + main_layout->addLayout(btn_layout, 0, 1, 3, 1); + + // right column + main_layout->addWidget(new QLabel(tr("Selected Signals")), 0, 2); + main_layout->addWidget(selected_list = new QListWidget(this), 1, 2, 2, 1); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->addLayout(contents_layout); - main_layout->addWidget(buttonBox); + main_layout->addWidget(buttonBox, 3, 2); for (auto it = can->can_msgs.cbegin(); it != can->can_msgs.cend(); ++it) { if (auto m = dbc()->msg(it.key())) { @@ -877,56 +866,59 @@ SeriesSelector::SeriesSelector(QWidget *parent) { } } msgs_combo->model()->sort(0); + msgs_combo->setCurrentIndex(-1); + QObject::connect(msgs_combo, qOverload(&QComboBox::currentIndexChanged), this, &SeriesSelector::updateAvailableList); + QObject::connect(available_list, &QListWidget::currentRowChanged, [=](int row) { add_btn->setEnabled(row != -1); }); + QObject::connect(selected_list, &QListWidget::currentRowChanged, [=](int row) { remove_btn->setEnabled(row != -1); }); + QObject::connect(available_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::add); + QObject::connect(selected_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::remove); + QObject::connect(add_btn, &QPushButton::clicked, [this]() { if (auto item = available_list->currentItem()) add(item); }); + QObject::connect(remove_btn, &QPushButton::clicked, [this]() { if (auto item = selected_list->currentItem()) remove(item);}); QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - QObject::connect(msgs_combo, SIGNAL(currentIndexChanged(int)), SLOT(msgSelected(int))); - QObject::connect(sig_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::addSignal); - QObject::connect(chart_series, &QListWidget::itemDoubleClicked, [](QListWidgetItem *item) { delete item; }); - - if (int index = msgs_combo->currentIndex(); index >= 0) { - msgSelected(index); - } } -void SeriesSelector::msgSelected(int index) { +void SeriesSelector::add(QListWidgetItem *item) { + auto it = (ListItem *)item; + addItemToList(selected_list, it->msg_id, it->sig, true); + delete item; +} + +void SeriesSelector::remove(QListWidgetItem *item) { + auto it = (ListItem *)item; + if (it->msg_id == msgs_combo->currentData().toString()) { + addItemToList(available_list, it->msg_id, it->sig); + } + delete item; +} + +void SeriesSelector::updateAvailableList(int index) { + if (index == -1) return; + available_list->clear(); QString msg_id = msgs_combo->itemData(index).toString(); - sig_list->clear(); - if (auto m = dbc()->msg(msg_id)) { - for (auto &[name, s] : m->sigs) { - QStringList data({msg_id, m->name, name}); - QListWidgetItem *item = new QListWidgetItem(name, sig_list); - item->setData(Qt::UserRole, data); - sig_list->addItem(item); + auto selected_items = seletedItems(); + for (auto &[name, s] : dbc()->msg(msg_id)->sigs) { + bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig=&s](auto it) { return it->msg_id == msg_id && it->sig == sig; }); + if (!is_selected) { + addItemToList(available_list, msg_id, &s); } } } -void SeriesSelector::addSignal(QListWidgetItem *item) { - QStringList data = item->data(Qt::UserRole).toStringList(); - addSeries(data[0], data[1], data[2]); -} +void SeriesSelector::addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name) { + QString text = QString(" %1").arg(getColor(sig).name(), sig->name.c_str()); + if (show_msg_name) text += QString(" %0 %1").arg(msgName(id), id); -void SeriesSelector::addSeries(const QString &id, const QString &msg_name, const QString &sig_name) { - QStringList data({id, msg_name, sig_name}); - for (int i = 0; i < chart_series->count(); ++i) { - if (chart_series->item(i)->data(Qt::UserRole).toStringList() == data) { - return; - } - } - QListWidgetItem *new_item = new QListWidgetItem(chart_series); - new_item->setData(Qt::UserRole, data); - chart_series->addItem(new_item); - QLabel *label = new QLabel(QString("%0 %1 %2").arg(data[2]).arg(data[1]).arg(data[0]), chart_series); + QLabel *label = new QLabel(text); label->setContentsMargins(5, 0, 5, 0); + auto new_item = new ListItem(id, sig, parent); new_item->setSizeHint(label->sizeHint()); - chart_series->setItemWidget(new_item, label); + parent->setItemWidget(new_item, label); } -QList SeriesSelector::series() { - QList ret; - for (int i = 0; i < chart_series->count(); ++i) { - ret.push_back(chart_series->item(i)->data(Qt::UserRole).toStringList()); - } +QList SeriesSelector::seletedItems() { + QList ret; + for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i)); return ret; } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 3e32dc069f..953026d935 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -28,7 +28,6 @@ class ChartView : public QChartView { public: ChartView(QWidget *parent = nullptr); void addSeries(const QString &msg_id, const Signal *sig); - void addSeries(const QList &series_list); void removeSeries(const QString &msg_id, const Signal *sig); bool hasSeries(const QString &msg_id, const Signal *sig) const; void updateSeries(const Signal *sig = nullptr, const std::vector *events = nullptr, bool clear = true); @@ -157,19 +156,24 @@ private: }; class SeriesSelector : public QDialog { - Q_OBJECT - public: - SeriesSelector(QWidget *parent); - void addSeries(const QString &id, const QString& msg_name, const QString &sig_name); - QList series(); + struct ListItem : public QListWidgetItem { + ListItem(const QString &msg_id, const Signal *sig, QListWidget *parent) : msg_id(msg_id), sig(sig), QListWidgetItem(parent) {} + QString msg_id; + const Signal *sig; + }; -private slots: - void msgSelected(int index); - void addSignal(QListWidgetItem *item); + SeriesSelector(QString title, QWidget *parent); + QList seletedItems(); + inline void addSelected(const QString &id, const Signal *sig) { addItemToList(selected_list, id, sig, true); } private: + void updateAvailableList(int index); + void addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name = false); + void add(QListWidgetItem *item); + void remove(QListWidgetItem *item); + QComboBox *msgs_combo; - QListWidget *sig_list; - QListWidget *chart_series; + QListWidget *available_list; + QListWidget *selected_list; }; From d096ec1329dcf55bdda3125c76b6a89849892dc4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Feb 2023 15:18:27 -0800 Subject: [PATCH 30/40] bump opendbc (#27253) --- opendbc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc b/opendbc index 0b7439f551..d103b156b3 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 0b7439f551168f202fb033b7e3e177048687e726 +Subproject commit d103b156b36f2bc9d0d6ad9ba1af02a42ecc2b46 From 276586fdf051caa86feef1820e6732567d22c19c Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Thu, 9 Feb 2023 00:18:56 +0100 Subject: [PATCH 31/40] cabana: bit level change frequency highlighting (#27259) * cabana: bit level change highlighting * change default color --- tools/cabana/binaryview.cc | 25 ++++++++++++++++++++++++- tools/cabana/binaryview.h | 2 +- tools/cabana/streams/abstractstream.cc | 1 + tools/cabana/streams/abstractstream.h | 1 + tools/cabana/util.cc | 8 ++++++++ tools/cabana/util.h | 1 + 6 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index ddad4cd446..a46c25bdfa 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -7,6 +7,8 @@ #include #include +#include + #include "tools/cabana/commands.h" #include "tools/cabana/streams/abstractstream.h" @@ -212,6 +214,19 @@ void BinaryViewModel::updateState() { for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < 8; ++j) { items[i * column_count + j].val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0'; + + // Bit update frequency based highlighting + bool has_signal = items[i * column_count + j].sigs.size() > 0; + double offset = has_signal ? 50 : 0; + + double min_f = last_msg.bit_change_counts[i][7 - j] == 0 ? offset : offset + 25; + double max_f = 255.0; + + double factor = 0.25; + double scaler = max_f / log2(1.0 + factor); + + double alpha = std::clamp(offset + log2(1.0 + factor * (double)last_msg.bit_change_counts[i][7 - j] / (double)last_msg.count) * scaler, min_f, max_f); + items[i * column_count + j].bg_color.setAlpha(alpha); } hex[0] = toHex(binary[i] >> 4); hex[1] = toHex(binary[i] & 0xf); @@ -264,10 +279,18 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->setPen(option.palette.color(QPalette::BrightText)); } else if (!item->sigs.isEmpty() && (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig))) { bool sig_hovered = item->sigs.contains(bin_view->hovered_sig); - painter->fillRect(option.rect, sig_hovered ? item->bg_color.darker(125) : item->bg_color); // 4/5x brightness + int min_alpha = item->sigs.contains(bin_view->hovered_sig) ? 255 : 50; + QColor bg = item->bg_color; + if (bg.alpha() < min_alpha) { + bg.setAlpha(min_alpha); + } + painter->fillRect(option.rect, sig_hovered ? bg.darker(125) : bg); // 4/5x brightness painter->setPen(sig_hovered ? option.palette.color(QPalette::BrightText) : Qt::black); + } else { + painter->fillRect(option.rect, item->bg_color); } + painter->drawText(option.rect, Qt::AlignCenter, item->val); if (item->is_msb || item->is_lsb) { painter->setFont(small_font); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index f697ea28cb..d006672793 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -36,7 +36,7 @@ public: } struct Item { - QColor bg_color = QApplication::style()->standardPalette().color(QPalette::Base); + QColor bg_color = QColor(102, 86, 169, 0); bool is_msb = false; bool is_lsb = false; QString val = "-"; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 8a77e0eb39..94d653bcb0 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -34,6 +34,7 @@ bool AbstractStream::updateEvent(const Event *event) { change_trackers[id].compute(data.dat, data.ts, data.freq); data.colors = change_trackers[id].colors; data.last_change_t = change_trackers[id].last_change_t; + data.bit_change_counts = change_trackers[id].bit_change_counts; } double ts = millis_since_boot(); diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 0dbb3d96a6..8c10d959cb 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -16,6 +16,7 @@ struct CanData { QByteArray dat; QVector colors; QVector last_change_t; + QVector> bit_change_counts; }; class AbstractStream : public QObject { diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index d978e4aa12..2806e175d7 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -18,6 +18,7 @@ void ChangeTracker::compute(const QByteArray &dat, double ts, uint32_t freq) { if (prev_dat.size() != dat.size()) { colors.resize(dat.size()); last_change_t.resize(dat.size()); + bit_change_counts.resize(dat.size()); std::fill(colors.begin(), colors.end(), QColor(0, 0, 0, 0)); std::fill(last_change_t.begin(), last_change_t.end(), ts); } else { @@ -39,6 +40,13 @@ void ChangeTracker::compute(const QByteArray &dat, double ts, uint32_t freq) { colors[i] = blend(colors[i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue } + // Track bit level changes + for (int bit = 0; bit < 8; bit++){ + if ((cur ^ last) & (1 << bit)) { + bit_change_counts[i][bit] += 1; + } + } + last_change_t[i] = ts; } else { // Fade out diff --git a/tools/cabana/util.h b/tools/cabana/util.h index af5032fd42..5d5b44d63c 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -17,6 +17,7 @@ public: QVector last_change_t; QVector colors; + QVector> bit_change_counts; private: const int periodic_threshold = 10; From ec6a55057ffe381fb65ba946e29cb59f0ee694e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Feb 2023 17:51:53 -0800 Subject: [PATCH 32/40] Rename GM CS variable to be consistent to the CAN parsers --- selfdrive/car/gm/carcontroller.py | 2 +- selfdrive/car/gm/carstate.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index affbcdc0fa..97f8b60e59 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -56,7 +56,7 @@ class CarController: # - on startup, first few msgs are blocked # - until we're in sync with camera so counters align when relay closes, preventing a fault. # openpilot can subtly drift, so this is activated throughout a drive to stay synced - out_of_sync = self.lka_steering_cmd_counter % 4 != (CS.camera_lka_steering_cmd_counter + 1) % 4 + out_of_sync = self.lka_steering_cmd_counter % 4 != (CS.cam_lka_steering_cmd_counter + 1) % 4 if not self.sent_lka_steering_cmd or out_of_sync: steer_step = self.params.STEER_STEP diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 8ea8ea8874..b73b7e7059 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -19,7 +19,7 @@ class CarState(CarStateBase): self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"] self.loopback_lka_steering_cmd_updated = False self.pt_lka_steering_cmd_counter = 0 - self.camera_lka_steering_cmd_counter = 0 + self.cam_lka_steering_cmd_counter = 0 self.buttons_counter = 0 def update(self, pt_cp, cam_cp, loopback_cp): @@ -35,7 +35,7 @@ class CarState(CarStateBase): self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 if self.CP.networkLocation == NetworkLocation.fwdCamera: self.pt_lka_steering_cmd_counter = pt_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] - self.camera_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] + self.cam_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] ret.wheelSpeeds = self.get_wheel_speeds( pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"], From 134766fe26443b5f2dca8e9df274229894028035 Mon Sep 17 00:00:00 2001 From: eFini Date: Thu, 9 Feb 2023 10:05:23 +0800 Subject: [PATCH 33/40] Toyota: Car Port for Toyota C-HR 2021 (TSS2) (#27212) * Toyota C-HR 2021 TSS2 Port * remove --------- Co-authored-by: Shane Smiskol --- docs/CARS.md | 5 +++-- selfdrive/car/tests/routes.py | 1 + selfdrive/car/toyota/interface.py | 2 +- selfdrive/car/toyota/values.py | 26 +++++++++++++++++++++++--- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index c48698b84f..b73f09334d 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 233 Supported Cars +# 234 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -174,7 +174,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| |Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| -|Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Toyota|Camry 2018-20|All|Stock|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Toyota|Camry 2021-22|All|openpilot|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index ce38eeb0de..535d3f5415 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -179,6 +179,7 @@ routes = [ CarTestRoute("0a0de17a1e6a2d15|2020-09-21--21-24-41", TOYOTA.PRIUS_TSS2), CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI), CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR), + CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2), CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHRH), CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V), diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 8e180e2301..438a9c11fc 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -72,7 +72,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.5533 ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max - elif candidate in (CAR.CHR, CAR.CHRH): + elif candidate in (CAR.CHR, CAR.CHRH, CAR.CHR_TSS2): stop_and_go = True ret.wheelbase = 2.63906 ret.steerRatio = 13.6 diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 3046814fac..7e801990a7 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -49,6 +49,7 @@ class CAR: CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5 CAMRYH_TSS2 = "TOYOTA CAMRY HYBRID 2021" CHR = "TOYOTA C-HR 2018" + CHR_TSS2 = "TOYOTA C-HR 2021" CHRH = "TOYOTA C-HR HYBRID 2018" COROLLA = "TOYOTA COROLLA 2017" COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019" @@ -115,7 +116,8 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-22", footnotes=[Footnote.CAMRY]), CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-23"), - CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-21"), + CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-20"), + CAR.CHR_TSS2: ToyotaCarInfo("Toyota C-HR 2021"), CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-19"), CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), CAR.COROLLA_TSS2: [ @@ -633,6 +635,23 @@ FW_VERSIONS = { b'8646FF407000 ', ], }, + CAR.CHR_TSS2: { + (Ecu.abs, 0x7b0, None): [ + b'F1526F4270\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B10091\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x0189663F459000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821FF410300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00', + ], + }, CAR.CHRH: { (Ecu.engine, 0x700, None): [ b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -2030,6 +2049,7 @@ DBC = { CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_RXH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), CAR.CHRH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CAMRYH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), @@ -2072,7 +2092,7 @@ EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_I # Toyota/Lexus Safety Sense 2.0 and 2.5 TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, - CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2} + CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2, CAR.CHR_TSS2} NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} @@ -2080,7 +2100,7 @@ NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC} # these cars have a radar which sends ACC messages instead of the camera -RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022} +RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022, CAR.CHR_TSS2} EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS, CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH, From ccfa73ff12da9ef5d2d939fb9018d9103128d299 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Feb 2023 18:33:19 -0800 Subject: [PATCH 34/40] Remove Bolt EV platform from torque params Was merged into EUV --- selfdrive/car/torque_data/override.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 0a49d39914..462ce91b36 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -26,7 +26,6 @@ COMMA BODY: [.nan, 1000, .nan] RAM 1500 5TH GEN: [2.0, 2.0, 0.0] RAM HD 5TH GEN: [1.4, 1.4, 0.0] SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] -CHEVROLET BOLT EV 2022: [2.0, 2.0, 0.05] CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] From 955c1dff454babe200862f0011e639ec500938b3 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 8 Feb 2023 19:06:59 -0800 Subject: [PATCH 35/40] ui: dmoji (#27070) * will draw * gradient * bg * add sense of depth * refactor * cleanup * fix endpoint * clean up * use array * 1 matmul * lives in modeldata * standstill only * remove bad pts * do less math * smooth * kind * fix d * lineswidth * fade in and out * hide * quick * gray out * fix joints * offset * rest icon * clean up * circle as state * longlat * new design * gap and g * scaledown, fix jts and unlock not standstill * width * different * intermediate * sand * motion effect * vis * fix bg * clean up * t * btn_size * small speedup * sligh * clean up * facelift * match .2 * .4 is good * size * no ss * rhd compat * less filt * more line * engaged * not white * drop prop * this is fine * independent falg * sg * dont look like hitman * cleanup * little more --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/assets/img_driver_face.png | Bin 24112 -> 19875 bytes selfdrive/ui/qt/onroad.cc | 62 +++++++++++++++++++++++---- selfdrive/ui/qt/onroad.h | 2 + selfdrive/ui/ui.cc | 35 ++++++++++++++- selfdrive/ui/ui.h | 18 ++++++++ 5 files changed, 108 insertions(+), 9 deletions(-) diff --git a/selfdrive/assets/img_driver_face.png b/selfdrive/assets/img_driver_face.png index ddde478cd789ec47ae87593d8d413336de653218..03765a0376dc33e72f88fb68d0f3879bffff90ff 100644 GIT binary patch literal 19875 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 ami>RG56lgMCO}*O0000 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 diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 7a4b4240c3..33b1ea8e27 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -231,7 +231,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par experimental_btn = new ExperimentalButton(this); main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); - dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size}); + dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -275,7 +275,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { setProperty("speed", cur_speed); setProperty("setSpeed", set_speed); setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph")); - setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); + setProperty("hideDM", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE)); setProperty("status", s.status); // update engageability/experimental mode button @@ -286,6 +286,9 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode()); setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD()); } + + // DM icon transition + dm_fade_state = fmax(0.0, fmin(1.0, dm_fade_state+0.2*(0.5-(float)(dmActive)))); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -435,12 +438,6 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { configFont(p, "Inter", 66, "Regular"); drawText(p, rect().center().x(), 290, speedUnit, 200); - // dm icon - if (!hideDM) { - int dm_icon_x = rightHandDM ? rect().right() - btn_size / 2 - (bdr_s * 2) : btn_size / 2 + (bdr_s * 2); - drawIcon(p, dm_icon_x, rect().bottom() - footer_h / 2, - dm_img, blackColor(70), dmActive ? 1.0 : 0.2); - } p.restore(); } @@ -544,6 +541,49 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.restore(); } +void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) { + const UIScene &scene = s->scene; + + painter.save(); + + // base icon + int x = rightHandDM ? rect().right() - (btn_size - 24) / 2 - (bdr_s * 2) : (btn_size - 24) / 2 + (bdr_s * 2); + int y = rect().bottom() - footer_h / 2; + float opacity = dmActive ? 0.65 : 0.2; + drawIcon(painter, x, y, dm_img, blackColor(0), opacity); + + // circle background + painter.setOpacity(1.0); + painter.setPen(Qt::NoPen); + painter.setBrush(blackColor(70)); + painter.drawEllipse(x - btn_size / 2, y - btn_size / 2, btn_size, btn_size); + + // face + QPointF face_kpts_draw[std::size(default_face_kpts_3d)]; + float kp; + for (int i = 0; i < std::size(default_face_kpts_3d); ++i) { + kp = (scene.face_kpts_draw[i].v[2] - 8) / 120 + 1.0; + face_kpts_draw[i] = QPointF(scene.face_kpts_draw[i].v[0] * kp + x, scene.face_kpts_draw[i].v[1] * kp + y); + } + + painter.setPen(QPen(QColor::fromRgbF(1.0, 1.0, 1.0, opacity), 5.2, Qt::SolidLine, Qt::RoundCap)); + painter.drawPolyline(face_kpts_draw, std::size(default_face_kpts_3d)); + + // tracking arcs + const int arc_l = 133; + const float arc_t_default = 6.7; + const float arc_t_extend = 12.0; + QColor arc_color = QColor::fromRgbF(0.09, 0.945, 0.26, 0.4*(1.0-dm_fade_state)*(s->engaged())); + float delta_x = -scene.driver_pose_sins[1] * arc_l / 2; + float delta_y = -scene.driver_pose_sins[0] * arc_l / 2; + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[1] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(std::fmin(x + delta_x, x), y - arc_l / 2, fabs(delta_x), arc_l), (scene.driver_pose_sins[1]>0 ? 90 : -90) * 16, 180 * 16); + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[0] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(x - arc_l / 2, std::fmin(y + delta_y, y), arc_l, fabs(delta_y)), (scene.driver_pose_sins[0]>0 ? 0 : 180) * 16, 180 * 16); + + painter.restore(); +} + void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) { painter.save(); @@ -654,6 +694,12 @@ void AnnotatedCameraWidget::paintGL() { } } + // DMoji + if (!hideDM && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) { + update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM); + drawDriverState(painter, s); + } + drawHud(painter); double cur_draw_t = millis_since_boot(); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 73c2e03795..73c2a37892 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -80,6 +80,7 @@ private: bool dmActive = false; bool hideDM = false; bool rightHandDM = false; + float dm_fade_state = 1.0; bool has_us_speed_limit = false; bool has_eu_speed_limit = false; bool v_ego_cluster_seen = false; @@ -97,6 +98,7 @@ protected: void drawLaneLines(QPainter &painter, const UIState *s); void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd); void drawHud(QPainter &p); + void drawDriverState(QPainter &painter, const UIState *s); inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 4df963167a..6c850b8ca4 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -112,6 +112,39 @@ void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) { update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); } +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { + UIScene &scene = s->scene; + const auto driver_orient = is_rhd ? driverstate.getRightDriverData().getFaceOrientation() : driverstate.getLeftDriverData().getFaceOrientation(); + for (int i = 0; i < std::size(scene.driver_pose_vals); i++) { + float v_this = (i == 0 ? (driver_orient[i] < 0 ? 0.7 : 0.9) : 0.4) * driver_orient[i]; + scene.driver_pose_diff[i] = fabs(scene.driver_pose_vals[i] - v_this); + scene.driver_pose_vals[i] = 0.8 * v_this + (1 - 0.8) * scene.driver_pose_vals[i]; + scene.driver_pose_sins[i] = sinf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + scene.driver_pose_coss[i] = cosf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + } + + const mat3 r_xyz = (mat3){{ + scene.driver_pose_coss[1]*scene.driver_pose_coss[2], + scene.driver_pose_coss[1]*scene.driver_pose_sins[2], + -scene.driver_pose_sins[1], + + -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_coss[0]*scene.driver_pose_sins[2], + -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_coss[0]*scene.driver_pose_coss[2], + -scene.driver_pose_sins[0]*scene.driver_pose_coss[1], + + scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_sins[0]*scene.driver_pose_sins[2], + scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_sins[0]*scene.driver_pose_coss[2], + scene.driver_pose_coss[0]*scene.driver_pose_coss[1], + }}; + + // transform vertices + for (int kpi = 0; kpi < std::size(default_face_kpts_3d); kpi++) { + vec3 kpt_this = default_face_kpts_3d[kpi]; + kpt_this = matvecmul3(r_xyz, kpt_this); + scene.face_kpts_draw[kpi] = (vec3){{(float)kpt_this.v[0], (float)kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; + } +} + static void update_sockets(UIState *s) { s->sm->update(0); } @@ -213,7 +246,7 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", - "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", + "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", }); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 9e1c54948b..e3eb97a762 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -25,6 +25,16 @@ typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; +const vec3 default_face_kpts_3d[] = { + {-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00}, + {-34.10, -32.00, 8.00}, {-36.16, -21.03, 8.00}, {-36.16, 6.40, 8.00}, {-35.47, 10.51, 8.00}, {-32.73, 19.43, 8.00}, + {-29.30, 26.29, 8.00}, {-24.50, 33.83, 8.00}, {-19.01, 41.37, 8.00}, {-14.21, 46.17, 8.00}, {-12.16, 47.54, 8.00}, + {-4.61, 49.60, 8.00}, {4.99, 49.60, 8.00}, {12.53, 47.54, 8.00}, {14.59, 46.17, 8.00}, {19.39, 41.37, 8.00}, + {24.87, 33.83, 8.00}, {29.67, 26.29, 8.00}, {33.10, 19.43, 8.00}, {35.84, 10.51, 8.00}, {36.53, 6.40, 8.00}, + {36.53, -21.03, 8.00}, {34.47, -32.00, 8.00}, {32.42, -37.49, 8.00}, {30.36, -40.91, 8.00}, {24.19, -46.40, 8.00}, + {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, +}; + struct Alert { QString text1; QString text2; @@ -103,6 +113,13 @@ typedef struct UIScene { // lead QPointF lead_vertices[2]; + // DMoji state + float driver_pose_vals[3]; + float driver_pose_diff[3]; + float driver_pose_sins[3]; + float driver_pose_coss[3]; + vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; + float light_sensor; bool started, ignition, is_metric, map_on_left, longitudinal_control; uint64_t started_frame; @@ -183,6 +200,7 @@ public slots: void ui_update_params(UIState *s); int get_path_length_idx(const cereal::ModelDataV2::XYZTData::Reader &line, const float path_height); void update_model(UIState *s, const cereal::ModelDataV2::Reader &model); +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::ModelDataV2::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line, float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); From a522dce0173c1f73d36b2d287f71cccc705305a1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 8 Feb 2023 19:14:24 -0800 Subject: [PATCH 36/40] add dmoji to release notes --- RELEASES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 9103fbc4b5..2e8dbb44bd 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,8 +1,10 @@ -Version 0.9.1 (2022-12-XX) +Version 0.9.1 (2023-2-20) ======================== * Adjust alert volume using ambient noise level * Removed driver monitoring timer resetting on interaction if face detected and distracted -* New German translation thanks to Vrabetz and CzokNorris! +* UI updates + * Driver monitoring icon shows driver's head pose + * German translation thanks to Vrabetz and CzokNorris! * Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! * Genesis GV60 2023 support thanks to sunnyhaibin! * Hyundai Tucson 2022-23 support From d74470252dc9c30dee3fc001d8d6ab4871cb8780 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Wed, 8 Feb 2023 19:18:36 -0800 Subject: [PATCH 37/40] Chrysler: torque tuning + increased steer rate (#26934) * increase chrysler steer rate * use torque control * update offline values for chrysler, jeep * bump panda * add blacklist * add lag buffer to the torque vals --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/car/chrysler/interface.py | 22 ++++++++++++++-------- selfdrive/car/chrysler/values.py | 13 +++++++++++-- selfdrive/car/torque_data/params.yaml | 14 +++++++------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index da086105e3..2b2fde8dc3 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -2,7 +2,7 @@ from cereal import car from panda import Panda from selfdrive.car import STD_CARGO_KG, get_safety_config -from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags +from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, CHRYSLER_OLD_TUNING_BLACKLIST, ChryslerFlags from selfdrive.car.interfaces import CarInterfaceBase @@ -24,6 +24,7 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT ret.minSteerSpeed = 3.8 # m/s + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) if candidate not in RAM_CARS: # Newer FW versions standard on the following platforms, or flashed by a dealer onto older platforms have a higher minimum steering speed. new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019) @@ -36,9 +37,12 @@ class CarInterface(CarInterfaceBase): ret.mass = 2242. + STD_CARGO_KG ret.wheelbase = 3.089 ret.steerRatio = 16.2 # Pacifica Hybrid 2017 - ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] - ret.lateralTuning.pid.kf = 0.00006 + + if candidate in CHRYSLER_OLD_TUNING_BLACKLIST: + ret.lateralTuning.init('pid') + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] + ret.lateralTuning.pid.kf = 0.00006 # Jeep elif candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019): @@ -46,9 +50,12 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.71 ret.steerRatio = 16.7 ret.steerActuatorDelay = 0.2 - ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] - ret.lateralTuning.pid.kf = 0.00006 + + if candidate in CHRYSLER_OLD_TUNING_BLACKLIST: + ret.lateralTuning.init('pid') + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] + ret.lateralTuning.pid.kf = 0.00006 # Ram elif candidate == CAR.RAM_1500: @@ -56,7 +63,6 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 3.88 ret.steerRatio = 16.3 ret.mass = 2493. + STD_CARGO_KG - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) ret.minSteerSpeed = 14.5 # Older EPS FW allow steer to zero if any(fw.ecu == 'eps' and fw.fwVersion[:4] <= b"6831" for fw in car_fw): diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 7629a2f086..e932238e6a 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -46,8 +46,12 @@ class CarControllerParams: self.STEER_DELTA_DOWN = 6 self.STEER_MAX = 261 # EPS allows more, up to 350? else: - self.STEER_DELTA_UP = 3 - self.STEER_DELTA_DOWN = 3 + if CP.carFingerprint in CHRYSLER_OLD_TUNING_BLACKLIST: + self.STEER_DELTA_UP = 3 + self.STEER_DELTA_DOWN = 3 + else: + self.STEER_DELTA_UP = 6 + self.STEER_DELTA_DOWN = 6 self.STEER_MAX = 261 # higher than this faults the EPS STEER_THRESHOLD = 120 @@ -56,6 +60,11 @@ RAM_DT = {CAR.RAM_1500, } RAM_HD = {CAR.RAM_HD, } RAM_CARS = RAM_DT | RAM_HD +# the increased steer rate hasn't been verified on these cars. +# remove from this list once it's been tested and confirmed to not fault +CHRYSLER_OLD_TUNING_BLACKLIST = {CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID, + CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE} + @dataclass class ChryslerCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 7da508a156..6f8cfe0ce6 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -4,11 +4,11 @@ ACURA RDX 2020: [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] AUDI A3 3RD GEN: [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] AUDI Q3 2ND GEN: [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] CHEVROLET VOLT PREMIER 2017: [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] -CHRYSLER PACIFICA 2018: [1.593387270257916, 1.3366521181047952, 0.13776367250652022] -CHRYSLER PACIFICA 2020: [1.4323553627965695, 1.509076559398423, 0.14328246159386085] -CHRYSLER PACIFICA HYBRID 2017: [1.3032470208409048, 1.06831764583744, 0.13287170990024627] -CHRYSLER PACIFICA HYBRID 2018: [1.6068280248761635, 1.2943025830995154, 0.1358557824293823] -CHRYSLER PACIFICA HYBRID 2019: [1.4624643614072217, 1.1958788168371808, 0.15748488008472716] +CHRYSLER PACIFICA 2018: [2.07140, 1.3366521181047952, 0.13776367250652022] +CHRYSLER PACIFICA 2020: [1.86206, 1.509076559398423, 0.14328246159386085] +CHRYSLER PACIFICA HYBRID 2017: [1.79422, 1.06831764583744, 0.116237] +CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818] +CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520] GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221] GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644] HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] @@ -39,8 +39,8 @@ HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.1403992098658639 HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272] -JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185] -JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003] +JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048] +JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003] KIA EV6 2022: [3.2, 2.093457, 0.05] KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716] KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] From dae133181701f5f1cc84daa081a8c71d8c7e084a Mon Sep 17 00:00:00 2001 From: Jason Wen <47793918+sunnyhaibin@users.noreply.github.com> Date: Wed, 8 Feb 2023 22:21:50 -0500 Subject: [PATCH 38/40] HKG: Car Port for Kia Niro Hybrid 2023 (HDA1) (#26827) * HKG: Car Port for Kia Niro Hybrid 2023 (HDA1) * bump opendbc * bump * Found gear signal * Only set one flag * bump opendbc * Update CARS.md * Update routes.py * bump opendbc * Update CARS.md * some values cleanup * new route with rlogs * set from data * merge (3854+3391+3227+3336)/4 * fine move back down here for now fine move back down here for now * Update selfdrive/car/hyundai/interface.py --------- Co-authored-by: Shane Smiskol --- RELEASES.md | 1 + docs/CARS.md | 3 ++- selfdrive/car/hyundai/carstate.py | 8 ++++++-- selfdrive/car/hyundai/interface.py | 11 +++++++---- selfdrive/car/hyundai/values.py | 17 +++++++++++++++-- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/override.yaml | 1 + 7 files changed, 33 insertions(+), 9 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 2e8dbb44bd..d99fe8183f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,7 @@ Version 0.9.1 (2023-2-20) * Genesis GV60 2023 support thanks to sunnyhaibin! * Hyundai Tucson 2022-23 support * Kia K5 Hybrid 2020 support thanks to sunnyhaibin! +* Kia Niro Hybrid 2023 support thanks to sunnyhaibin! * Kia Sorento 2022-23 support thanks to sunnyhaibin! * Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Volkswagen Crafter and MAN TGE 2017-23 support thanks to jyoung8607! diff --git a/docs/CARS.md b/docs/CARS.md index b73f09334d..a72bf74278 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 234 Supported Cars +# 235 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -107,6 +107,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| |Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| |Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Kia|Niro Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| |Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| |Kia|Niro Plug-in Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D|| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|| diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index da1a7bfa78..06efa54605 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -21,7 +21,9 @@ class CarState(CarStateBase): self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) - self.gear_msg_canfd = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER" + self.gear_msg_canfd = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \ + "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ + "GEAR_SHIFTER" if CP.carFingerprint in CANFD_CAR: self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"] elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]: @@ -425,7 +427,9 @@ class CarState(CarStateBase): def get_can_parser_canfd(CP): cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" - gear_msg = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER" + gear_msg = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \ + "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ + "GEAR_SHIFTER" signals = [ ("WHEEL_SPEED_1", "WHEEL_SPEEDS"), ("WHEEL_SPEED_2", "WHEEL_SPEEDS"), diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index c708e2b78e..d3a29dc0cb 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -35,9 +35,12 @@ class CarInterface(CarInterfaceBase): # non-HDA2 if 0x1cf not in fingerprint[4]: ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value - # ICE cars do not have 0x130; GEARS message on 0x40 instead + # ICE cars do not have 0x130; GEARS message on 0x40 or 0x70 instead if 0x130 not in fingerprint[4]: - ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value + if 0x40 not in fingerprint[4]: + ret.flags |= HyundaiFlags.CANFD_ALT_GEARS_2.value + else: + ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value if candidate not in CANFD_RADAR_SCC_CAR: ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value @@ -129,8 +132,8 @@ class CarInterface(CarInterfaceBase): ret.mass = 1985. + STD_CARGO_KG ret.wheelbase = 2.78 ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable - elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021): - ret.mass = 1737. + STD_CARGO_KG + elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN): + ret.mass = 3452. * CV.LB_TO_KG + STD_CARGO_KG # average of all the cars ret.wheelbase = 2.7 ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec tire_stiffness_factor = 0.385 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 4c8e924c49..af0bc18af9 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -61,6 +61,8 @@ class HyundaiFlags(IntFlag): ENABLE_BLINKERS = 32 + CANFD_ALT_GEARS_2 = 64 + class CAR: # Hyundai @@ -100,6 +102,7 @@ class CAR: KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021" + KIA_NIRO_HEV_2ND_GEN = "KIA NIRO HYBRID 2ND GEN" KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN" KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT" KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" @@ -208,6 +211,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h), ], + CAR.KIA_NIRO_HEV_2ND_GEN: HyundaiCarInfo("Kia Niro Hybrid 2023", harness=Harness.hyundai_a), CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018 CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g), CAR.KIA_OPTIMA_H: [ @@ -1627,6 +1631,14 @@ FW_VERSIONS = { b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', ], }, + CAR.KIA_NIRO_HEV_2ND_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', + ], + }, } CHECKSUM = { @@ -1644,7 +1656,7 @@ FEATURES = { "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN} # The radar does SCC on these cars when HDA I, rather than the camera CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN} @@ -1652,7 +1664,7 @@ CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, C # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } -HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020} # these cars use a different gas signal +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN} # these cars use a different gas signal EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.GENESIS_GV60_EV_1ST_GEN} # these cars require a special panda safety mode due to missing counters and checksums in the messages @@ -1714,4 +1726,5 @@ DBC = { CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SORENTO_4TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_NIRO_HEV_2ND_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 535d3f5415..98db2520c3 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -127,6 +127,7 @@ routes = [ CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021), + CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN), CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN), CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 462ce91b36..52c9e8d547 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -38,6 +38,7 @@ GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1] GENESIS GV60 ELECTRIC 1ST GEN: [2.5, 2.5, 0.1] KIA SORENTO 4TH GEN: [2.5, 2.5, 0.1] +KIA NIRO HYBRID 2ND GEN: [2.42, 2.5, 0.12] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] From 542345a7c2606e0c2ea9b88c7d91a22c3a97f88a Mon Sep 17 00:00:00 2001 From: Jason Wen <47793918+sunnyhaibin@users.noreply.github.com> Date: Thu, 9 Feb 2023 00:25:31 -0500 Subject: [PATCH 39/40] Hyundai CAN-FD: use common PT bus function (#27268) * Hyundai CAN-FD: PT bus detection cleanup * Do this when ICE CAN-FD is supported! * Only needs to be checked for non-HDA2, HDA2 uses 0x1cf * Unused Co-authored-by: Shane Smiskol * Keep it the same for now --------- Co-authored-by: Shane Smiskol --- selfdrive/car/hyundai/carstate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 06efa54605..0bab188790 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -6,6 +6,7 @@ from cereal import car from common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine +from selfdrive.car.hyundai.hyundaicanfd import get_e_can_bus from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams from selfdrive.car.interfaces import CarStateBase @@ -515,8 +516,7 @@ class CarState(CarStateBase): ("ACCELERATOR_BRAKE_ALT", 100), ] - bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, get_e_can_bus(CP)) @staticmethod def get_cam_can_parser_canfd(CP): From 1e49c54ffb274a7987626ebfaa8eb3e75ac6fe7c Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Wed, 8 Feb 2023 22:21:31 -0800 Subject: [PATCH 40/40] new model: improved height estimation (#27234) * 07a3a2b2-6dbe-43c3-9bb9-a8023932e054/449 c3d7d18a-6207-42e7-84d9-601ecb3c6f0e/700 * 07a3a2b2-6dbe-43c3-9bb9-a8023932e054/449 db92e7f3-3f4b-4f0f-95ff-76cb6ed6894a/700 * update ref commit --- selfdrive/modeld/models/supercombo.onnx | 2 +- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 2daf01ea14..a483fa4db4 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d8f2f9d53908e7a5496412956548dab1aa08ed0b5fb9d1c685f275369f07a57 +oid sha256:736ddc08497d7596bae4d9515a8efb996676be80e67a6d34d632bb8af2ed3fa9 size 45962515 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index fa4fa9540d..c64f522352 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -2a255ecea3665a3d295210195fd8ef0791ab49a0 +ba947edbb131a2a36ced7c490dfcf3280ad5b167