From fbf2f3816b18321ed921cf03042327134be68edb Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Mon, 14 Nov 2022 15:02:26 -0800 Subject: [PATCH 01/29] [locationd] Add input checks (#26460) * add input checks with same decay as reset_tracker * add observation timings check * typo * bugfix * improve offline locationd visibility * sbugfix offline lld Co-authored-by: Adeeb Shihadeh --- selfdrive/locationd/liblocationd.cc | 12 +++++ selfdrive/locationd/locationd.cc | 75 ++++++++++++++++++++++++++--- selfdrive/locationd/locationd.h | 9 +++- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/selfdrive/locationd/liblocationd.cc b/selfdrive/locationd/liblocationd.cc index 49404668a4..da57fb7ff4 100755 --- a/selfdrive/locationd/liblocationd.cc +++ b/selfdrive/locationd/liblocationd.cc @@ -26,4 +26,16 @@ extern "C" { memcpy(std_buff, stdev.data(), sizeof(double) * stdev.size()); } + bool is_gps_ok(Localizer *localizer){ + return localizer->is_gps_ok(); + } + + bool are_inputs_ok(Localizer *localizer){ + return localizer->are_inputs_ok(); + } + + void observation_timings_invalid_reset(Localizer *localizer){ + localizer->observation_timings_invalid_reset(); + } + } diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 8d9e247655..4325900c0e 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -19,6 +19,9 @@ const double VALID_TIME_SINCE_RESET = 1.0; // s const double VALID_POS_STD = 50.0; // m const double MAX_RESET_TRACKER = 5.0; const double SANE_GPS_UNCERTAINTY = 1500.0; // m +const double INPUT_INVALID_THRESHOLD = 5.0; // same as reset tracker +const double DECAY = 0.99995; // same as reset tracker +const double MAX_FILTER_REWIND_TIME = 0.8; // s // TODO: GPS sensor time offsets are empirically calculated // They should be replaced with synced time from a real clock @@ -200,6 +203,14 @@ VectorXd Localizer::get_stdev() { return this->kf->get_P().diagonal().array().sqrt(); } +bool Localizer::are_inputs_ok() { + return this->critical_services_valid(this->observation_values_invalid) && !this->observation_timings_invalid; +} + +void Localizer::observation_timings_invalid_reset(){ + this->observation_timings_invalid = false; +} + void Localizer::handle_sensor(double current_time, const cereal::SensorEventData::Reader& log) { // TODO does not yet account for double sensor readings in the log @@ -209,10 +220,15 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData } double sensor_time = 1e-9 * log.getTimestamp(); - + // sensor time and log time should be close if (std::abs(current_time - sensor_time) > 0.1) { LOGE("Sensor reading ignored, sensor timestamp more than 100ms off from log time"); + this->observation_timings_invalid = true; + return; + } + else if (!this->is_timestamp_valid(sensor_time)) { + this->observation_timings_invalid = true; return; } @@ -227,6 +243,10 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData auto meas = Vector3d(-v[2], -v[1], -v[0]); if (meas.norm() < ROTATION_SANITY_CHECK) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_GYRO, { meas }); + this->observation_values_invalid["gyroscope"] *= DECAY; + } + else{ + this->observation_values_invalid["gyroscope"] += 1.0; } } @@ -242,6 +262,10 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData auto meas = Vector3d(-v[2], -v[1], -v[0]); if (meas.norm() < ACCEL_SANITY_CHECK) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_ACCEL, { meas }); + this->observation_values_invalid["accelerometer"] *= DECAY; + } + else{ + this->observation_values_invalid["accelerometer"] += 1.0; } } } @@ -335,8 +359,14 @@ void Localizer::handle_car_state(double current_time, const cereal::CarState::Re void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log) { VectorXd rot_device = this->device_from_calib * floatlist2vector(log.getRot()); VectorXd trans_device = this->device_from_calib * floatlist2vector(log.getTrans()); + + if (!this->is_timestamp_valid(current_time)) { + this->observation_timings_invalid = true; + return; + } if ((rot_device.norm() > ROTATION_SANITY_CHECK) || (trans_device.norm() > TRANS_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } @@ -344,10 +374,12 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry VectorXd trans_calib_std = floatlist2vector(log.getTransStd()); if ((rot_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK) || (trans_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } if ((rot_calib_std.norm() > 10 * ROTATION_SANITY_CHECK) || (trans_calib_std.norm() > 10 * TRANS_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } @@ -363,12 +395,19 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry { rot_device }, { rot_device_cov }); this->kf->predict_and_observe(current_time, OBSERVATION_CAMERA_ODO_TRANSLATION, { trans_device }, { trans_device_cov }); + this->observation_values_invalid["cameraOdometry"] *= DECAY; } void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log) { + if (!this->is_timestamp_valid(current_time)) { + this->observation_timings_invalid = true; + return; + } + if (log.getRpyCalib().size() > 0) { auto live_calib = floatlist2vector(log.getRpyCalib()); if ((live_calib.minCoeff() < -CALIB_RPY_SANITY_CHECK) || (live_calib.maxCoeff() > CALIB_RPY_SANITY_CHECK)) { + this->observation_values_invalid["liveCalibration"] += 1.0; return; } @@ -376,6 +415,7 @@ void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibra this->device_from_calib = euler2rot(this->calib); this->calib_from_device = this->device_from_calib.transpose(); this->calibrated = log.getCalStatus() == 1; + this->observation_values_invalid["liveCalibration"] *= DECAY; } } @@ -407,8 +447,8 @@ void Localizer::time_check(double current_time) { void Localizer::update_reset_tracker() { // reset tracker is tuned to trigger when over 1reset/10s over 2min period - if (this->isGpsOK()) { - this->reset_tracker *= .99995; + if (this->is_gps_ok()) { + this->reset_tracker *= DECAY; } else { this->reset_tracker = 0.0; } @@ -483,10 +523,28 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build return msg_builder.toBytes(); } -bool Localizer::isGpsOK() { +bool Localizer::is_gps_ok() { return this->gps_valid; } +bool Localizer::critical_services_valid(std::map critical_services) { + for (auto &kv : critical_services){ + if (kv.second >= INPUT_INVALID_THRESHOLD){ + return false; + } + } + return true; +} + +bool Localizer::is_timestamp_valid(double current_time) { + double filter_time = this->kf->get_filter_time(); + if (!std::isnan(filter_time) && ((filter_time - current_time) > MAX_FILTER_REWIND_TIME)) { + LOGE("Observation timestamp is older than the max rewind threshold of the filter"); + return false; + } + return true; +} + void Localizer::determine_gps_mode(double current_time) { // 1. If the pos_std is greater than what's not acceptable and localizer is in gps-mode, reset to no-gps-mode // 2. If the pos_std is greater than what's not acceptable and localizer is in no-gps-mode, fake obs @@ -521,10 +579,15 @@ int Localizer::locationd_thread() { uint64_t cnt = 0; bool filterInitialized = false; + const std::vector critical_input_services = {"cameraOdometry", "liveCalibration", "accelerometer", "gyroscope"}; + for (std::string service : critical_input_services) { + this->observation_values_invalid.insert({service, 0.0}); + } while (!do_exit) { sm.update(); if (filterInitialized){ + this->observation_timings_invalid_reset(); for (const char* service : service_list) { if (sm.updated(service) && sm.valid(service)){ const cereal::Event::Reader log = sm[service]; @@ -538,8 +601,8 @@ int Localizer::locationd_thread() { // 100Hz publish for notcars, 20Hz for cars const char* trigger_msg = sm["carParams"].getCarParams().getNotCar() ? "accelerometer" : "cameraOdometry"; if (sm.updated(trigger_msg)) { - bool inputsOK = sm.allAliveAndValid(); - bool gpsOK = this->isGpsOK(); + bool inputsOK = sm.allAliveAndValid() && this->are_inputs_ok(); + bool gpsOK = this->is_gps_ok(); bool sensorsOK = sm.allAliveAndValid({"accelerometer", "gyroscope"}); MessageBuilder msg_builder; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index d6bb5347c5..f0872d9f56 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "cereal/messaging/messaging.h" @@ -32,8 +33,12 @@ public: void finite_check(double current_time = NAN); void time_check(double current_time = NAN); void update_reset_tracker(); - bool isGpsOK(); + bool is_gps_ok(); + bool critical_services_valid(std::map critical_services); + bool is_timestamp_valid(double current_time); void determine_gps_mode(double current_time); + bool are_inputs_ok(); + void observation_timings_invalid_reset(); kj::ArrayPtr get_message_bytes(MessageBuilder& msg_builder, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid); @@ -73,4 +78,6 @@ private: bool gps_mode = false; bool gps_valid = false; bool ublox_available = true; + bool observation_timings_invalid = false; + std::map observation_values_invalid; }; From 8e91ce1eb4f91c976570056fd8979a4afcf43a53 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 15 Nov 2022 11:09:19 +0800 Subject: [PATCH 02/29] Cabana: Move history logs to a tabbed widget (#26481) * tabwidget * cleanup * update state before show * cleanup * remove spacing * fix right panel stretch issue * fix missing } --- tools/cabana/detailwidget.cc | 55 +++++++++++++++++++++++------------- tools/cabana/detailwidget.h | 6 ++-- tools/cabana/historylog.cc | 4 +-- tools/cabana/mainwin.cc | 7 +++-- tools/cabana/signaledit.cc | 15 ---------- tools/cabana/signaledit.h | 6 ++-- 6 files changed, 47 insertions(+), 46 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 260c9dfec7..192d1fd66c 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -64,31 +64,35 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart frame_layout->addWidget(warning_widget); main_layout->addWidget(title_frame); - QWidget *container = new QWidget(this); - QVBoxLayout *container_layout = new QVBoxLayout(container); - container_layout->setSpacing(0); - container_layout->setContentsMargins(0, 0, 0, 0); - - scroll = new QScrollArea(this); - scroll->setWidget(container); - scroll->setWidgetResizable(true); - scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - main_layout->addWidget(scroll); - + // msg widget + QWidget *msg_widget = new QWidget(this); + QVBoxLayout *msg_layout = new QVBoxLayout(msg_widget); + msg_layout->setContentsMargins(0, 0, 0, 0); // binary view binary_view = new BinaryView(this); - container_layout->addWidget(binary_view); - + msg_layout->addWidget(binary_view); // signals signals_layout = new QVBoxLayout(); - container_layout->addLayout(signals_layout); + signals_layout->setSpacing(0); + msg_layout->addLayout(signals_layout); + msg_layout->addStretch(0); + + scroll = new QScrollArea(this); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidget(msg_widget); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - // history log + tab_widget = new QTabWidget(this); + tab_widget->setTabPosition(QTabWidget::South); + tab_widget->addTab(scroll, "Msg"); history_log = new HistoryLog(this); - container_layout->addWidget(history_log); + tab_widget->addTab(history_log, "Logs"); + main_layout->addWidget(tab_widget); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); + QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); }); QObject::connect(can, &CANMessages::msgsReceived, this, &DetailWidget::updateState); QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); }); QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu); @@ -151,6 +155,7 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { form = new SignalEdit(i); QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); + QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showFormClicked); QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); QObject::connect(form, &SignalEdit::showChart, charts, &ChartsWidget::showChart); @@ -176,16 +181,26 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { warning_label->setText(warnings.join('\n')); warning_widget->setVisible(!warnings.isEmpty()); - QTimer::singleShot(1, [this]() { setUpdatesEnabled(true); }); + setUpdatesEnabled(true); } void DetailWidget::updateState(const QHash * msgs) { time_label->setText(QString::number(can->currentSec(), 'f', 3)); - if (!msgs->contains(msg_id)) + if (msg_id.isEmpty() || (msgs && !msgs->contains(msg_id))) return; - binary_view->updateState(); - history_log->updateState(); + if (tab_widget->currentIndex() == 0) + binary_view->updateState(); + else + history_log->updateState(); +} + +void DetailWidget::showFormClicked() { + auto s = qobject_cast(sender()); + setUpdatesEnabled(false); + for (auto f : signal_list) + f->updateForm(f == s && !f->isFormVisible()); + setUpdatesEnabled(true); } void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool opened) { diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 5fc6d122fe..4346d1c5d5 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -28,6 +28,7 @@ public: QUndoStack *undo_stack = nullptr; private: + void showFormClicked(); void updateChartState(const QString &id, const Signal *sig, bool opened); void showTabBarContextMenu(const QPoint &pt); void addSignal(int start_bit, int size, bool little_endian); @@ -36,13 +37,14 @@ private: void removeSignal(const Signal *sig); void editMsg(); void removeMsg(); - void updateState(const QHash * msgs); + void updateState(const QHash * msgs = nullptr); QString msg_id; QLabel *name_label, *time_label, *warning_label; QWidget *warning_widget; QVBoxLayout *signals_layout; QTabBar *tabbar; + QTabWidget *tab_widget; QToolBar *toolbar; QAction *remove_msg_act; HistoryLog *history_log; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 28e344a46e..1b0898afbd 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -86,10 +86,8 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap); horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); verticalHeader()->setVisible(false); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); setFrameShape(QFrame::NoFrame); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); } diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 97d62cb4f4..f0419a2fb3 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -72,7 +72,7 @@ MainWindow::MainWindow() : QMainWindow() { video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); - r_layout->addWidget(charts_widget); + r_layout->addWidget(charts_widget, 1); main_layout->addWidget(right_container); setCentralWidget(central_widget); @@ -192,9 +192,10 @@ void MainWindow::saveDBCToFile() { if (!file_name.isEmpty()) { settings.last_dir = QFileInfo(file_name).absolutePath(); QFile file(file_name); - if (file.open(QIODevice::WriteOnly)) + if (file.open(QIODevice::WriteOnly)) { file.write(dbc()->generateDBC().toUtf8()); detail_widget->undo_stack->clear(); + } } } @@ -216,7 +217,7 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe void MainWindow::dockCharts(bool dock) { if (dock && floating_window) { floating_window->removeEventFilter(charts_widget); - r_layout->addWidget(charts_widget); + r_layout->addWidget(charts_widget, 1); floating_window->deleteLater(); floating_window = nullptr; } else if (!dock && !floating_window) { diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 8737154c16..eb22b78d5a 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -189,26 +188,12 @@ void SignalEdit::updateForm(bool visible) { icon->setText(visible ? "▼ " : "> "); } -void SignalEdit::showFormClicked() { - parentWidget()->setUpdatesEnabled(false); - for (auto &edit : parentWidget()->findChildren()) - edit->updateForm(edit == this && !form->isVisible()); - QTimer::singleShot(1, [this]() { parentWidget()->setUpdatesEnabled(true); }); -} - void SignalEdit::signalHovered(const Signal *s) { auto bg_color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx)); auto color = sig == s ? "white" : "black"; color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(bg_color.name())); } -void SignalEdit::hideEvent(QHideEvent *event) { - msg_id = ""; - sig = nullptr; - updateForm(false); - QWidget::hideEvent(event); -} - void SignalEdit::enterEvent(QEvent *event) { emit highlight(sig); QWidget::enterEvent(event); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index 335e49a869..da0b9758c7 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -34,6 +34,8 @@ public: void setSignal(const QString &msg_id, const Signal *sig); void setChartOpened(bool opened); void signalHovered(const Signal *sig); + void updateForm(bool show); + inline bool isFormVisible() const { return form->isVisible(); } const Signal *sig = nullptr; QString msg_id; @@ -42,14 +44,12 @@ signals: void showChart(const QString &name, const Signal *sig, bool show); void remove(const Signal *sig); void save(const Signal *sig, const Signal &new_sig); + void showFormClicked(); protected: - void hideEvent(QHideEvent *event) override; void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; void saveSignal(); - void updateForm(bool show); - void showFormClicked(); SignalForm *form = nullptr; ElidedLabel *title; From 2d766fee14e94011e261c8c557098a7223c87014 Mon Sep 17 00:00:00 2001 From: Jason Wen <47793918+sunnyhaibin@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:46:29 -0500 Subject: [PATCH 03/29] HKG: Car Port for 2022 Kia Stinger (#26397) * HKG: Car Port for 2022 Kia Stinger * Substitute KIA STINGER GT2 2018 torque params * bump panda * Add test route * Update CARS.md * Not this checksum * Update test route * Update CARS.md * Harness C -> Harness K Co-authored-by: Adeeb Shihadeh --- RELEASES.md | 1 + docs/CARS.md | 3 ++- selfdrive/car/hyundai/carcontroller.py | 2 +- selfdrive/car/hyundai/hyundaican.py | 3 ++- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/hyundai/values.py | 22 +++++++++++++++++++++- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/substitute.yaml | 1 + 8 files changed, 30 insertions(+), 5 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index bf89b667fa..d00ece4b73 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -30,6 +30,7 @@ Version 0.8.17 (2022-11-21) * Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin! * Kia Sportage 2023 support thanks to sunnyhaibin! * Kia Sportage Hybrid 2023 support thanks to sunnyhaibin! +* Kia Stinger 2022 support thanks to sunnyhaibin! Version 0.8.16 (2022-08-26) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index b8a313386d..ca3a224586 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. -# 214 Supported Cars +# 215 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -107,6 +107,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| +|Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 2f944edc0f..1ab90878b8 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -175,7 +175,7 @@ class CarController: if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022): + CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022): can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled)) # 5 Hz ACC options diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index dcb8430976..c2ffffbf22 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -20,7 +20,8 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, - CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): + CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, + CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 0306f7e104..0b5fd3bb39 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -158,7 +158,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.5 if candidate == CAR.KIA_OPTIMA_G4: ret.minSteerSpeed = 32 * CV.MPH_TO_MS - elif candidate == CAR.KIA_STINGER: + elif candidate in (CAR.KIA_STINGER, CAR.KIA_STINGER_2022): ret.mass = 1825. + STD_CARGO_KG ret.wheelbase = 2.78 ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 1dba3a5442..ecba7b7494 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -95,6 +95,7 @@ class CAR: KIA_SORENTO = "KIA SORENTO GT LINE 2018" KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" + KIA_STINGER_2022 = "KIA STINGER 2022" KIA_CEED = "KIA CEED INTRO ED 2019" KIA_EV6 = "KIA EV6 2022" @@ -181,6 +182,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), + CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", harness=Harness.hyundai_k), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), CAR.KIA_EV6: [ HyundaiCarInfo("Kia EV6 (without HDA II) 2022", "Highway Driving Assist", harness=Harness.hyundai_l), @@ -790,6 +792,23 @@ FW_VERSIONS = { b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', ], }, + CAR.KIA_STINGER_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', + ], + }, CAR.PALISADE: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ', @@ -1451,7 +1470,7 @@ FEATURES = { "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022}, # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "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}, + "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}, } CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, 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} @@ -1496,6 +1515,7 @@ DBC = { CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None), CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_STINGER_2022: dbc_dict('hyundai_kia_generic', None), CAR.KONA: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index d0051454a6..cd61528439 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -111,6 +111,7 @@ routes = [ CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11), CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), + CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022), CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2 CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1 diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index c043c9d455..77236e393e 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -37,6 +37,7 @@ HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 +KIA STINGER 2022: KIA STINGER GT2 2018 GENESIS G90 2017: GENESIS G70 2018 GENESIS G80 2017: GENESIS G70 2018 GENESIS G70 2020: HYUNDAI SONATA 2020 From 7fcafa402239c30d8344c46608ade6a8fd4831f4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 14 Nov 2022 23:28:43 -0800 Subject: [PATCH 04/29] ui: stretch abstract control title (#26499) stretch toggle title so it's easier to expand --- selfdrive/ui/qt/widgets/controls.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index d3b77935df..619fd3cb4c 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -38,7 +38,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons title_label = new QPushButton(title); title_label->setFixedHeight(120); title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left"); - hlayout->addWidget(title_label); + hlayout->addWidget(title_label, 1); // value next to control button value = new ElidedLabel(); From ffa32df062fe3cf241e887e7afbc582118bd1e36 Mon Sep 17 00:00:00 2001 From: ambientocclusion <1399123+ambientocclusion@users.noreply.github.com> Date: Tue, 15 Nov 2022 00:04:20 -0800 Subject: [PATCH 05/29] Multilang: add missing Japanese translations (#26482) Add new Japanese translations --- selfdrive/ui/translations/main_ja.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 87c60516fc..339826796b 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -60,11 +60,11 @@ Cellular Metered - + 従量制通信設定 Prevent large data uploads when on a metered connection - + 大量のデータのアップロードを防止します。 @@ -240,11 +240,11 @@ Reset - + リセット Review - + 確認 @@ -864,7 +864,7 @@ location set Uninstall - + アンインストール @@ -1004,19 +1004,19 @@ location set Experimental Mode - + 実験モード openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 <br> <h4>🌮 エンドツーエンドアクセル制御 🌮</h4> エンジンとブレーキの制御を全てopenpilotの運転モデルに委ねます。openpilotは人間が運転するのと同じように考え、赤信号や停止サインで車を停止します。openpilotが運転速度も決めるので、あなたが設定する速度は上限速度になります。 openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - + openpilotは、この車に搭載されているアクセル制御(ACC)を標準で利用します。openpilotによるアクセル制御を利用したい場合は、この設定を有効にしてください。 WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - + 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 @@ -1074,7 +1074,7 @@ location set Forget - + 削除 From d7f943e27524744f20b008a55ad3358fc9272034 Mon Sep 17 00:00:00 2001 From: Oxygen Date: Tue, 15 Nov 2022 16:23:37 +0800 Subject: [PATCH 06/29] Update missing items in main_zh-CHS.ts (#26492) Update missing iterms in main_zh-CHS.ts Co-authored-by: Shane Smiskol --- selfdrive/ui/translations/main_zh-CHS.ts | 50 ++++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 558a6cec4f..64b32c80d5 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -60,11 +60,11 @@ Cellular Metered - + 按流量计费的手机移动网络 Prevent large data uploads when on a metered connection - + 当使用按流量计费的连接时,避免上传大流量数据 @@ -140,7 +140,7 @@ Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。仅熄火时可用。 + 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。(仅熄火时可用) Reset Calibration @@ -204,7 +204,7 @@ Your device is pointed %1° %2 and %3° %4. - 您的设备校准为%1° %2、%3° %4。 + 您的设备校准为%1° %2、%3° %4。 down @@ -240,11 +240,11 @@ Reset - + 重置 Review - + 预览 @@ -814,35 +814,35 @@ location set SoftwarePanel Updates are only downloaded while the car is off. - + 车辆熄火时才能下载升级文件。 Current Version - + 当前版本 Download - + 下载 Install Update - + 安装更新 INSTALL - + 安装 Target Branch - + 目标分支 SELECT - + 选择 Select a branch - + 选择分支 UNINSTALL @@ -862,7 +862,7 @@ location set Uninstall - + 卸载 @@ -966,15 +966,15 @@ location set Experimental openpilot Longitudinal Control - + 试验性的openpilot纵向控制 openpilot longitudinal control is not currently available for this car. - + 目前此车辆无法使用openpilot纵向控制功能。 Enable experimental longitudinal control to enable this. - + 启用试验性的纵向控制功能。 Disengage on Accelerator Pedal @@ -1002,19 +1002,19 @@ location set Experimental Mode - + 测试模式 openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - + openpilot 默认 <b>轻松模式</b>驾驶车辆。 试验模式启用一些轻松模式之外的 <b>试验性功能</b>。 试验性功能包括: <br> <h4>🌮 端到端(End-to-End) 纵向控制 🌮</h4> 允许智能驾驶模型控制加速和制动。 openpilot 将模仿人类驾驶行为, 包括在遇到红灯和停车让行标识时停车。 鉴于智能驾驶模型判断实际行驶车速,设定速度仅为车速上限。 openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - + openpilot默认使用此车辆自带ACC,而非openpilot纵向控制功能。启用此按钮将切换到openpilot纵向控制功能。 WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - + 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 @@ -1064,15 +1064,15 @@ location set FORGET - 忘记 + 忽略 Forget Wi-Fi Network "%1"? - 忘记WiFi网络 "%1"? + 忽略WiFi网络 "%1"? Forget - + 忽略 From e58f6fbc11424e8059d6a86a4f0990f604c3a22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 15 Nov 2022 00:46:47 -0800 Subject: [PATCH 07/29] Rate limit honda steer (#26500) * Rate limit honda steer * Update ref_commit --- selfdrive/car/honda/carcontroller.py | 14 +++++++++++++- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 5fa475fe08..ba1f13fb4e 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -99,6 +99,12 @@ HUDData = namedtuple("HUDData", "lanes_visible", "fcw", "acc_alert", "steer_required"]) +def rate_limit_steer(new_steer, last_steer): + # TODO just hardcoded ramp to min/max in 0.2s for all Honda + MAX_DELTA = 5 * DT_CTRL + return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA) + + class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP @@ -116,6 +122,7 @@ class CarController: self.speed = 0.0 self.gas = 0.0 self.brake = 0.0 + self.last_steer = 0.0 def update(self, CC, CS): actuators = CC.actuators @@ -130,6 +137,10 @@ class CarController: accel = 0.0 gas, brake = 0.0, 0.0 + # *** rate limit steer *** + limited_steer = rate_limit_steer(actuators.steer, self.last_steer) + self.last_steer = limited_steer + # *** apply brake hysteresis *** pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady, CS.out.vEgo, self.CP.carFingerprint) @@ -143,7 +154,7 @@ class CarController: # **** process the car messages **** # steer torque is converted back to CAN reference (positive when steering right) - apply_steer = int(interp(-actuators.steer * self.params.STEER_MAX, + apply_steer = int(interp(-limited_steer * self.params.STEER_MAX, self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V)) # Send CAN commands @@ -250,6 +261,7 @@ class CarController: new_actuators.accel = self.accel new_actuators.gas = self.gas new_actuators.brake = self.brake + new_actuators.steer = self.last_steer self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f35e23d45f..a8053936d7 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -aa2d370836588fd80b648dbed8d156765ec804d5 +324408a87f49413da864616cb409537ce7d8beb2 From 844f7692d48c5b2b2b6f8bc90070d9fc83ef7171 Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Tue, 15 Nov 2022 19:44:25 +0100 Subject: [PATCH 08/29] loggerd: add missing utility include (#26502) --- selfdrive/loggerd/loggerd.h | 1 + selfdrive/loggerd/tests/test_logger.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/selfdrive/loggerd/loggerd.h b/selfdrive/loggerd/loggerd.h index 6eafbe08d0..1fa6349828 100644 --- a/selfdrive/loggerd/loggerd.h +++ b/selfdrive/loggerd/loggerd.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "cereal/messaging/messaging.h" #include "cereal/services.h" diff --git a/selfdrive/loggerd/tests/test_logger.cc b/selfdrive/loggerd/tests/test_logger.cc index 18a0e57df7..ba7835d632 100644 --- a/selfdrive/loggerd/tests/test_logger.cc +++ b/selfdrive/loggerd/tests/test_logger.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include "catch2/catch.hpp" #include "cereal/messaging/messaging.h" From bdc432d21824cc83627bb5d1abd5e99b4bc62fc3 Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Tue, 15 Nov 2022 11:55:43 -0700 Subject: [PATCH 09/29] Add video link for Volt (#26504) --- selfdrive/car/gm/values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index eace6b6aca..03392ba0f9 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -90,7 +90,7 @@ class GMCarInfo(CarInfo): CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"), - CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0), + CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ"), CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"), CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), From a662af57c7de750df4f1650ee4079c56665b817e Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 15 Nov 2022 13:07:46 -0800 Subject: [PATCH 10/29] CI: use github.head_ref to group PR action runs (#26505) * CI: use github.head_ref to group PR action runs for push triggers, github.ref is set to the "branch or tag ref that was pushed" for pull_request triggers, it is set to the "pull request merge branch" (master?) github.head_ref is only set when the trigger is pull_request https://docs.github.com/en/actions/learn-github-actions/contexts#github-context * only check github.ref for push event --- .github/workflows/selfdrive_tests.yaml | 2 +- .github/workflows/tools_tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 598f2c592b..f2cc51285d 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -7,7 +7,7 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true env: diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 549a2f4195..94cc3c2580 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -7,7 +7,7 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true env: From fbf3ef0895182ffab90ddda83fa485b02d5a76d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 15 Nov 2022 14:21:00 -0800 Subject: [PATCH 11/29] More Honda rate limit (#26509) * More Honda rate limit * Update ref_commit --- selfdrive/car/honda/carcontroller.py | 4 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index ba1f13fb4e..790dce1810 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -100,8 +100,8 @@ HUDData = namedtuple("HUDData", def rate_limit_steer(new_steer, last_steer): - # TODO just hardcoded ramp to min/max in 0.2s for all Honda - MAX_DELTA = 5 * DT_CTRL + # TODO just hardcoded ramp to min/max in 0.33s for all Honda + MAX_DELTA = 3 * DT_CTRL return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index a8053936d7..4aa9d60ab5 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -324408a87f49413da864616cb409537ce7d8beb2 +959e63af52d9efdb62156cab4b773f88b154fd75 From 9c5df76a6c8784af3bec64827a2dbab464d8ce39 Mon Sep 17 00:00:00 2001 From: hoomoose <94947902+hoomoose@users.noreply.github.com> Date: Tue, 15 Nov 2022 17:19:30 -0700 Subject: [PATCH 12/29] Hyundai: longitudinal support for all CAN-FD EV and Hybrids (#26345) * Update values.py * Update interface.py * Update interface.py * Update carcontroller.py * Update interface.py * Update interface.py * Update values.py * Update values.py * Update interface.py * Update values.py * Update interface.py * Update carcontroller.py * cleanup * update docs * bump panda Co-authored-by: Adeeb Shihadeh --- docs/CARS.md | 12 ++++++------ panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 5 +++-- selfdrive/car/hyundai/interface.py | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index ca3a224586..59b8145556 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -60,8 +60,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Elantra Hybrid 2021-23|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 K| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| |Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| -|Hyundai|Ioniq 5 (without HDA II) 2022-23|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| +|Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| +|Hyundai|Ioniq 5 (without HDA II) 2022-23|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| @@ -83,13 +83,13 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Tucson Diesel 2019|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 L| -|Hyundai|Tucson Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| +|Hyundai|Tucson Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| -|Kia|EV6 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| +|Kia|EV6 (without HDA II) 2022|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Kia|Forte 2019-21|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 G| |Kia|K5 2021-22|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 EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| @@ -105,7 +105,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| -|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| +|Kia|Sportage Hybrid 2023|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 N| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| diff --git a/panda b/panda index c0632cd32b..5dc5cd8e20 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c0632cd32b78279dfbb3ce28c7a10ded8090d40f +Subproject commit 5dc5cd8e20668dfb15d35b175fccbfd3f7b63b09 diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 1ab90878b8..e5fdbfd57a 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -85,7 +85,7 @@ class CarController: # *** common hyundai stuff *** # tester present - w/ no response (keeps relevant ECU disabled) - if self.frame % 100 == 0 and self.CP.openpilotLongitudinalControl: + if self.frame % 100 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and self.CP.openpilotLongitudinalControl: addr, bus = 0x7d0, 0 if self.CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, 5 @@ -122,7 +122,8 @@ class CarController: can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CP, CC.enabled)) if self.CP.openpilotLongitudinalControl: - can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) + if hda2: + can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) if self.frame % 2 == 0: can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, set_speed_in_units)) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 0b5fd3bb39..8738aabd17 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -219,7 +219,7 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] - ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2) + ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in CANFD_RADAR_SCC_CAR else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] @@ -283,7 +283,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, logcan, sendcan): - if CP.openpilotLongitudinalControl: + if CP.openpilotLongitudinalControl and not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value): addr, bus = 0x7d0, 0 if CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, 5 From 4eda53cef2adb949709166c2e5d204565376ce2e Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 15 Nov 2022 16:41:27 -0800 Subject: [PATCH 13/29] add laikad comment --- selfdrive/locationd/laikad.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 2769f394c5..6936d88acc 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -187,6 +187,7 @@ class Laikad: "gpsTimeOfWeek": tow, "positionECEF": measurement_msg(value=ecef_pos.tolist(), std=pos_std.tolist(), valid=kf_valid), "velocityECEF": measurement_msg(value=ecef_vel.tolist(), std=vel_std.tolist(), valid=kf_valid), + # TODO std is incorrectly the dimension of the measurements and not 3D "positionFixECEF": measurement_msg(value=self.last_pos_fix, std=self.last_pos_residual, valid=self.last_pos_fix_t == t), "ubloxMonoTime": gnss_mono_time, "correctedMeasurements": meas_msgs From 2ad9a4f95a82bf922c86b373026c0e96f9971a80 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 16:44:03 -0800 Subject: [PATCH 14/29] offroad ui: support storing confirmation of a toggle (#26510) * show confirmation toggle on first toggle of experimental mode * don't store confirmation if users toggle off *after* this PR * refactor * cleaner * not true * try here --- common/params.cc | 1 + selfdrive/ui/qt/offroad/settings.cc | 20 ++++++++------------ selfdrive/ui/qt/widgets/controls.h | 10 ++++++++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/common/params.cc b/common/params.cc index e17d1f1b13..9e3e32d584 100644 --- a/common/params.cc +++ b/common/params.cc @@ -103,6 +103,7 @@ std::unordered_map keys = { {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisablePowerDown", PERSISTENT}, {"ExperimentalMode", PERSISTENT}, + {"ExperimentalModeConfirmed", PERSISTENT}, {"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB {"DisableUpdates", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT}, diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index a03cf02010..ced0744692 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -28,20 +28,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon, confirm - std::vector> toggle_defs{ + std::vector> toggle_defs{ { "OpenpilotEnabledToggle", tr("Enable openpilot"), tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), "../assets/offroad/icon_openpilot.png", - false, }, { "ExperimentalMode", tr("Experimental Mode"), "", "../assets/offroad/icon_road.png", - false, }, { "ExperimentalLongitudinalEnabled", @@ -50,35 +48,30 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { .arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).")) .arg(tr("openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control.")), "../assets/offroad/icon_speed_limit.png", - true, }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), "../assets/offroad/icon_warning.png", - false, }, { "IsMetric", tr("Use Metric System"), tr("Display speed in km/h instead of mph."), "../assets/offroad/icon_metric.png", - false, }, { "RecordFront", tr("Record and Upload Driver Camera"), tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), "../assets/offroad/icon_monitoring.png", - false, }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), "../assets/offroad/icon_disengage_on_accelerator.svg", - false, }, #ifdef ENABLE_MAPS { @@ -86,20 +79,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Show ETA in 24h Format"), tr("Use 24h format instead of am/pm"), "../assets/offroad/icon_metric.png", - false, }, { "NavSettingLeftSide", tr("Show Map on Left Side of UI"), tr("Show map on left side when in split screen view."), "../assets/offroad/icon_road.png", - false, }, #endif }; - for (auto &[param, title, desc, icon, confirm] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, confirm, this); + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, false, this); bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); @@ -108,6 +99,11 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { toggles[param.toStdString()] = toggle; } + // Toggles with confirmation dialogs + toggles["ExperimentalMode"]->confirm = true; + toggles["ExperimentalMode"]->store_confirm = true; + toggles["ExperimentalLongitudinalEnabled"]->confirm = true; + connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); }); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index b67224b33d..c639b7f481 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -139,13 +139,16 @@ class ParamControl : public ToggleControl { Q_OBJECT public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool confirm, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { + ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool _confirm, QWidget *parent = nullptr) : confirm(_confirm), ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { QString content("

" + title + "


" "

" + getDescription() + "

"); ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this); - if (!confirm || !state || dialog.exec()) { + + bool confirmed = store_confirm && params.getBool(key + "Confirmed"); + if (!confirm || confirmed || !state || dialog.exec()) { + if (store_confirm && state) params.putBool(key + "Confirmed", true); params.putBool(key, state); } else { toggle.togglePosition(); @@ -153,6 +156,9 @@ public: }); } + bool confirm = false; + bool store_confirm = false; + void refresh() { if (params.getBool(key) != toggle.on) { toggle.togglePosition(); From 9c96b21367af3dda9b193a821fe2e175a63f4176 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 17:18:13 -0800 Subject: [PATCH 15/29] publish experimental mode state (#26512) * publish experimental mode state * remove that --- cereal | 2 +- selfdrive/controls/controlsd.py | 1 + selfdrive/ui/qt/onroad.cc | 8 +++++--- selfdrive/ui/ui.cc | 1 - selfdrive/ui/ui.h | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cereal b/cereal index afafa0a2a5..3bae09cf65 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit afafa0a2a537d775842ab2e1bf20cb9a33b34f9a +Subproject commit 3bae09cf6527674d7eda3a9956242aad94a8f3d2 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index f69e9e7fd1..a18bec83a8 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -777,6 +777,7 @@ class Controls: controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) controlsState.canErrorCounter = self.can_rcv_timeout_counter + controlsState.experimentalMode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl lat_tuning = self.CP.lateralTuning.which() if self.joystick_mode: diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 23986726c8..fcf29181e7 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -446,6 +446,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.save(); const UIScene &scene = s->scene; + SubMaster &sm = *(s->sm); + // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); @@ -461,8 +463,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint path QLinearGradient bg(0, height(), 0, height() / 4); float start_hue, end_hue; - if (scene.experimental_mode) { - const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration(); + if (sm["controlsState"].getControlsState().getExperimentalMode()) { + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration(); float acceleration_future = 0; if (acceleration.getZ().size() > 16) { acceleration_future = acceleration.getX()[16]; // 2.5 seconds @@ -555,7 +557,7 @@ void AnnotatedCameraWidget::paintGL() { } else if (v_ego > 15) { wide_cam_requested = false; } - wide_cam_requested = wide_cam_requested && s->scene.experimental_mode; + wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode(); // TODO: also detect when ecam vision stream isn't available // for replay of old routes, never go to widecam wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 970448359e..2d4533afe1 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -174,7 +174,6 @@ void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - s->scene.experimental_mode = params.getBool("ExperimentalMode"); } void UIState::updateStatus() { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 38e2ffe3ce..d6f5c3e2e0 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -104,7 +104,7 @@ typedef struct UIScene { QPointF lead_vertices[2]; float light_sensor; - bool started, ignition, is_metric, map_on_left, longitudinal_control, experimental_mode; + bool started, ignition, is_metric, map_on_left, longitudinal_control; uint64_t started_frame; } UIScene; From d4fd0c3c8798317e4af87642af30ddbcd39fc4d6 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 15 Nov 2022 18:41:43 -0800 Subject: [PATCH 16/29] [torqued] Make torqued work on Hyundai Tucson (#26511) * changes to get offline values * set tucon values * move to params.yaml --- selfdrive/car/torque_data/override.yaml | 1 - selfdrive/car/torque_data/params.yaml | 1 + selfdrive/locationd/torqued.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 2ef5a1cd0f..c0b8592ee0 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -30,7 +30,6 @@ CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] -HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0] HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0] KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.0] KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index c2ebadfc7a..a9023b4edc 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -38,6 +38,7 @@ HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0 HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] 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] KIA EV6 2022: [3.2, 3.0, 0.05] diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 42dff60087..588bca1578 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -16,9 +16,9 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 MIN_POINTS_TOTAL = 4000 -MIN_POINTS_TOTAL_QLOG = 800 +MIN_POINTS_TOTAL_QLOG = 600 FIT_POINTS_TOTAL = 2000 -FIT_POINTS_TOTAL_QLOG = 800 +FIT_POINTS_TOTAL_QLOG = 600 MIN_VEL = 15 # m/s FRICTION_FACTOR = 1.5 # ~85% of data coverage FACTOR_SANITY = 0.3 From f3efc8998cee2750e65a99f96a6e9c787c17a19f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 18:25:16 -0800 Subject: [PATCH 17/29] taco time --- RELEASES.md | 17 ++++++++--------- common/version.h | 2 +- selfdrive/ui/qt/offroad/settings.cc | 11 ++++++++--- 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, 47 insertions(+), 43 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index d00ece4b73..e15286c5f0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,29 +1,28 @@ -Version 0.8.17 (2022-11-21) +Version 0.9.0 (2022-11-21) ======================== * New driving model - * Internal feature space information content increased tenfold during training (to ~700 bits), which makes the model dramatically more accurate + * Internal feature space information content increased tenfold during training to ~700 bits, which makes the model dramatically more accurate * Less reliance on previous frames makes model more reactive and snappy * Trained in new reprojective simulator - * Trained in 36hrs from scratch, compared to one week for previous releases + * Trained in 36 hours from scratch, compared to one week for previous releases * Training now simulates both lateral and longitudinal behavior, which allows openpilot to slow down for turns, stop at traffic lights, and more in experimental mode -* Driver monitoring updates - * New bigger model with added end-to-end distracted trigger - * Reduced false positives during driver calibration * Experimental driving mode * End-to-end longitudinal control * Stops for traffic lights and stop signs * Slows down for turns - * openpilot defaults to chill mode, enable experimental in settings + * openpilot defaults to chill mode, enable experimental mode in settings +* Driver monitoring updates + * New bigger model with added end-to-end distracted trigger + * Reduced false positives during driver calibration * Self-tuning torque controller: learns parameters live for each car * Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models * UI updates - * Multi-language in navigation * Matched speeds shown on car's dash + * Multi-language in navigation * Improved update experience * Border turns grey while overriding steering * Bookmark events while driving; view them in comma connect * New onroad visualization for experimental mode -* AGNOS 6 * tools: new and improved cabana thanks to deanlee! * Experimental longitudinal support for Volkswagen, CAN-FD Hyundai, and new GM models * Genesis GV70 2022-23 support thanks to zunichky and sunnyhaibin! diff --git a/common/version.h b/common/version.h index 0a109c1faa..74a56a7c1b 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.17" +#define COMMA_VERSION "0.9.0" diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index ced0744692..cf6f589b65 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -46,7 +46,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Experimental openpilot Longitudinal Control"), QString("%1
%2") .arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).")) - .arg(tr("openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control.")), + .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.")), "../assets/offroad/icon_speed_limit.png", }, { @@ -119,10 +119,15 @@ void TogglesPanel::updateToggles() { const QString e2e_description = tr("\ openpilot defaults to driving in chill mode.\ Experimental mode enables alpha-level features that aren't ready for chill mode. \ - Experimental features are listed below:\ + Experimental features are listed below: \
\

🌮 End-to-End Longitudinal Control 🌮

\ - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound."); + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. \ + Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. \ +
\ +

New Driving Visualization

\ + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.\ + "); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 339826796b..f7329811a6 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1007,16 +1007,16 @@ location set 実験モード - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 <br> <h4>🌮 エンドツーエンドアクセル制御 🌮</h4> エンジンとブレーキの制御を全てopenpilotの運転モデルに委ねます。openpilotは人間が運転するのと同じように考え、赤信号や停止サインで車を停止します。openpilotが運転速度も決めるので、あなたが設定する速度は上限速度になります。 + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilotは、この車に搭載されているアクセル制御(ACC)を標準で利用します。openpilotによるアクセル制御を利用したい場合は、この設定を有効にしてください。 + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 31c910a910..56d7dfd965 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1007,16 +1007,16 @@ location set 실험적 모드 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험 모드의 특징은 아래에 나열되어 있습니다 <br> <h4>🌮 E2E 롱컨트롤 🌮</h4> 주행모델이 가속과 감속을 제어하도록 합니다. openpilot은 신호등과 정지표지판을 보고 멈추는 것을 포함하여 운전자가 생각하는것처럼 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 상한선으로만 작용합니다. + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 자동긴급제동(AEB)를 비활성화합니다. - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilot은 차량의 내장 ACC로 기본 설정됩니다. 롱컨트롤으로 전환하려면 이 옵션을 활성화하세요. + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 자동긴급제동(AEB)를 비활성화합니다. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 6a1d32f87a..f68400a3c7 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1011,16 +1011,16 @@ trabalho definido Modo Experimental - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: <br> <h4>🌮 Controle Longitudinal de Ponta a Ponta 🌮</h4> Deixe o modelo de condução controlar o acelerador e os freios. Uma vez que o modelo de condução decide qual velocidade dirigir, a velocidade definida só funcionará como um limite superior. + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB). - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - O padrão do openpilot é o ACC integrado do carro em vez do controle longitudinal do openpilot neste carro. Habilite isto para alternar para controle longitudinal do openpilot. + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB). + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 64b32c80d5..174f40696b 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1005,16 +1005,16 @@ location set 测试模式 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot 默认 <b>轻松模式</b>驾驶车辆。 试验模式启用一些轻松模式之外的 <b>试验性功能</b>。 试验性功能包括: <br> <h4>🌮 端到端(End-to-End) 纵向控制 🌮</h4> 允许智能驾驶模型控制加速和制动。 openpilot 将模仿人类驾驶行为, 包括在遇到红灯和停车让行标识时停车。 鉴于智能驾驶模型判断实际行驶车速,设定速度仅为车速上限。 + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilot默认使用此车辆自带ACC,而非openpilot纵向控制功能。启用此按钮将切换到openpilot纵向控制功能。 + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index c9810597b5..d1683a9562 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1007,16 +1007,16 @@ location set 實驗模式 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot 默認以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。 實驗功能如下: <br> <h4>🌮端到端縱向控制🌮</h4> 讓駕駛模型控制油門和剎車。 openpilot 將像人類一樣駕駛,包括紅燈和停車標誌時停車,因為是由駕駛模型來決定車速,所以定速的設定值只會作為上限。 + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告:openpilot 縱向控制在這輛車上仍屬實驗性質,啟用後會喪失自動緊急煞車 (AEB) 功能。 - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilot 默認使用汽車內置的主動巡航控制 (ACC),而不是使用 openpilot 縱向控制。啟用此選項可切換到 openpilot 縱向控制。 + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告:openpilot 縱向控制在這輛車上仍屬實驗性質,啟用後會喪失自動緊急煞車 (AEB) 功能。 + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + From 97a205c94df412d40db1a94bbc4bb735b5afea27 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 16 Nov 2022 11:07:50 +0800 Subject: [PATCH 18/29] UI: fix CameraView crash on deleting. (#26390) * fix crash on delete * TODO? * after makeCurrent --- selfdrive/ui/qt/offroad/driverview.cc | 1 + selfdrive/ui/qt/widgets/cameraview.cc | 10 ++++++++++ selfdrive/ui/qt/widgets/cameraview.h | 1 + 3 files changed, 12 insertions(+) diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 0ff786fb91..1377bb3b23 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -35,6 +35,7 @@ void DriverViewScene::showEvent(QShowEvent* event) { } void DriverViewScene::hideEvent(QHideEvent* event) { + // TODO: stop vipc thread ? params.putBool("IsDriverViewEnabled", false); } diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index a606d6893e..347cdb1dca 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -102,6 +102,7 @@ CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool CameraWidget::~CameraWidget() { makeCurrent(); + stopVipcThread(); if (isValid()) { glDeleteVertexArrays(1, &frame_vao); glDeleteBuffers(1, &frame_vbo); @@ -171,6 +172,15 @@ void CameraWidget::showEvent(QShowEvent *event) { } } +void CameraWidget::stopVipcThread() { + if (vipc_thread) { + vipc_thread->requestInterruption(); + vipc_thread->quit(); + vipc_thread->wait(); + vipc_thread = nullptr; + } +} + void CameraWidget::updateFrameMat() { int w = width(), h = height(); diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 0698d1fb9a..7cc3847f99 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -51,6 +51,7 @@ protected: void updateCalibration(const mat3 &calib); void vipcThread(); void clearFrames(); + void stopVipcThread(); bool zoomed_view; GLuint frame_vao, frame_vbo, frame_ibo; From c6e3d566e9b23b2046f933f3dd9e189708230a56 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 19:24:12 -0800 Subject: [PATCH 19/29] update experimental mode disabled description --- selfdrive/ui/qt/offroad/settings.cc | 4 ++-- selfdrive/ui/translations/main_ja.ts | 16 ++++++++-------- selfdrive/ui/translations/main_ko.ts | 16 ++++++++-------- selfdrive/ui/translations/main_pt-BR.ts | 16 ++++++++-------- selfdrive/ui/translations/main_zh-CHS.ts | 16 ++++++++-------- selfdrive/ui/translations/main_zh-CHT.ts | 16 ++++++++-------- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index cf6f589b65..997e344221 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -151,8 +151,8 @@ void TogglesPanel::updateToggles() { e2e_toggle->setEnabled(false); params.remove("ExperimentalMode"); - const QString no_long = tr("openpilot longitudinal control is not currently available for this car."); - const QString exp_long = tr("Enable experimental longitudinal control to enable this."); + const QString no_long = tr("Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control."); + const QString exp_long = tr("Enable experimental longitudinal control to allow experimental mode."); e2e_toggle->setDescription("" + (CP.getExperimentalLongitudinalAvailable() ? exp_long : no_long) + "

" + e2e_description); } diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index f7329811a6..21fdc48fef 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -970,14 +970,6 @@ location set Experimental openpilot Longitudinal Control 実験段階のopenpilotによるアクセル制御 - - openpilot longitudinal control is not currently available for this car. - openpilotによるアクセル制御は、この車では現在利用できません。 - - - Enable experimental longitudinal control to enable this. - ここ機能を使う為には、「実験段階のopenpilotによるアクセル制御」を先に有効化してください。 - Disengage on Accelerator Pedal アクセルを踏むと openpilot を中断 @@ -1018,6 +1010,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + +
Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 56d7dfd965..57c688ffe6 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -970,14 +970,6 @@ location set Experimental openpilot Longitudinal Control openpilot 롱컨트롤 (실험적) - - openpilot longitudinal control is not currently available for this car. - 현재 이 차량에는 openpilot 롱컨트롤을 사용할 수 없습니다. - - - Enable experimental longitudinal control to enable this. - openpilot 롱컨트롤을 활성화합니다. (실험적) - Disengage on Accelerator Pedal 가속페달 조작시 해제 @@ -1018,6 +1010,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index f68400a3c7..2d7195e0d2 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -974,14 +974,6 @@ trabalho definido Experimental openpilot Longitudinal Control Controle longitudinal experimental openpilot - - openpilot longitudinal control is not currently available for this car. - controle longitudinal openpilot não está disponível para este carro. - - - Enable experimental longitudinal control to enable this. - Habilite o controle longitudinal experimental para habilitar isso. - Disengage on Accelerator Pedal Desacionar Com Pedal Do Acelerador @@ -1022,6 +1014,14 @@ trabalho definido openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 174f40696b..fb9b6dd6f5 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -968,14 +968,6 @@ location set Experimental openpilot Longitudinal Control 试验性的openpilot纵向控制 - - openpilot longitudinal control is not currently available for this car. - 目前此车辆无法使用openpilot纵向控制功能。 - - - Enable experimental longitudinal control to enable this. - 启用试验性的纵向控制功能。 - Disengage on Accelerator Pedal 踩油门时取消控制 @@ -1016,6 +1008,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index d1683a9562..86d5482355 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -970,14 +970,6 @@ location set Experimental openpilot Longitudinal Control 使用 openpilot 縱向控制(實驗) - - openpilot longitudinal control is not currently available for this car. - openpilot 縱向控制目前不適用於這輛車。 - - - Enable experimental longitudinal control to enable this. - 打開縱向控制(實驗)以啟用此功能。 - Disengage on Accelerator Pedal 油門取消控車 @@ -1018,6 +1010,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater From 3b71a9780dab0eed22dc277ce26a4d95c97f07df Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 15 Nov 2022 19:45:15 -0800 Subject: [PATCH 20/29] [torqued] Non-zero offline friction (#26513) add non zero friction values --- selfdrive/car/torque_data/override.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index c0b8592ee0..6ec782444c 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -30,10 +30,10 @@ CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] -HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0] -KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.0] -KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0] -GENESIS GV70 1ST GEN: [2.42, 2.42, 0.01] +HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.1] +KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1] +KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1] +GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] From f2d97da9b46d81454ec227152e280f1261ae9883 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 20:14:35 -0800 Subject: [PATCH 21/29] jenkins: use tici-needs-can to build release --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 696446c65f..c34d253585 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -59,7 +59,7 @@ pipeline { branch 'devel-staging' } steps { - phone_steps("tici", [ + phone_steps("tici-needs-can", [ ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], ]) } From b3f75b0c5b151cbcb048a31bb33c8f49b2a818b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 20:46:42 -0800 Subject: [PATCH 22/29] ui: function for setting toggle confirmation settings (#26516) function for setting confirmation settings --- selfdrive/ui/qt/offroad/settings.cc | 7 +++---- selfdrive/ui/qt/widgets/controls.h | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 997e344221..85b09dc183 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -90,7 +90,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }; for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, false, this); + auto toggle = new ParamControl(param, title, desc, icon, this); bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); @@ -100,9 +100,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } // Toggles with confirmation dialogs - toggles["ExperimentalMode"]->confirm = true; - toggles["ExperimentalMode"]->store_confirm = true; - toggles["ExperimentalLongitudinalEnabled"]->confirm = true; + toggles["ExperimentalMode"]->setConfirmation(true, true); + toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index c639b7f481..e2fd9d1b9d 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -139,7 +139,7 @@ class ParamControl : public ToggleControl { Q_OBJECT public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool _confirm, QWidget *parent = nullptr) : confirm(_confirm), ToggleControl(title, desc, icon, false, parent) { + ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { QString content("

" + title + "


" @@ -156,8 +156,10 @@ public: }); } - bool confirm = false; - bool store_confirm = false; + void setConfirmation(bool _confirm, bool _store_confirm) { + confirm = _confirm; + store_confirm = _store_confirm; + }; void refresh() { if (params.getBool(key) != toggle.on) { @@ -172,6 +174,8 @@ public: private: std::string key; Params params; + bool confirm = false; + bool store_confirm = false; }; class ListWidget : public QWidget { From 62024176c6e0386401374873e73512ab76f4204a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 20:52:31 -0800 Subject: [PATCH 23/29] ui: toggle control supports active icons (#26514) * support active icons * function * fix crash if not setting icon but active icon * revert * clean up --- selfdrive/ui/qt/widgets/controls.cc | 6 +++--- selfdrive/ui/qt/widgets/controls.h | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 619fd3cb4c..456cf748f4 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -26,10 +26,10 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons hlayout->setSpacing(20); // left icon + icon_label = new QLabel(); if (!icon.isEmpty()) { - QPixmap pix(icon); - QLabel *icon_label = new QLabel(); - icon_label->setPixmap(pix.scaledToWidth(80, Qt::SmoothTransformation)); + icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + icon_label->setPixmap(icon_pixmap); icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); hlayout->addWidget(icon_label); } diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index e2fd9d1b9d..0540088a78 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -54,6 +54,9 @@ public: return description->text(); } + QLabel *icon_label; + QPixmap icon_pixmap; + public slots: void showDescription() { description->setVisible(true); @@ -153,6 +156,12 @@ public: } else { toggle.togglePosition(); } + + if (state && !active_icon_pixmap.isNull()) { + icon_label->setPixmap(active_icon_pixmap); + } else if (!icon_pixmap.isNull()) { + icon_label->setPixmap(icon_pixmap); + } }); } @@ -161,6 +170,10 @@ public: store_confirm = _store_confirm; }; + void setActiveIcon(const QString &icon) { + active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + } + void refresh() { if (params.getBool(key) != toggle.on) { toggle.togglePosition(); @@ -174,6 +187,7 @@ public: private: std::string key; Params params; + QPixmap active_icon_pixmap; bool confirm = false; bool store_confirm = false; }; From 797c626984080e1ff17206b99e67a11bba87992f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 21:07:11 -0800 Subject: [PATCH 24/29] ui: handle two dynamic toggle icon cases (#26517) handles two cases --- selfdrive/ui/qt/widgets/controls.h | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 0540088a78..770b9b92dd 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -153,15 +153,10 @@ public: if (!confirm || confirmed || !state || dialog.exec()) { if (store_confirm && state) params.putBool(key + "Confirmed", true); params.putBool(key, state); + setIcon(state); } else { toggle.togglePosition(); } - - if (state && !active_icon_pixmap.isNull()) { - icon_label->setPixmap(active_icon_pixmap); - } else if (!icon_pixmap.isNull()) { - icon_label->setPixmap(icon_pixmap); - } }); } @@ -175,8 +170,10 @@ public: } void refresh() { - if (params.getBool(key) != toggle.on) { + bool state = params.getBool(key); + if (state != toggle.on) { toggle.togglePosition(); + setIcon(state); } }; @@ -185,6 +182,14 @@ public: }; private: + void setIcon(bool state) { + if (state && !active_icon_pixmap.isNull()) { + icon_label->setPixmap(active_icon_pixmap); + } else if (!icon_pixmap.isNull()) { + icon_label->setPixmap(icon_pixmap); + } + }; + std::string key; Params params; QPixmap active_icon_pixmap; From 73ec91f3bc20409e672480428f125dd6c8b9f2a1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 16 Nov 2022 13:10:05 +0800 Subject: [PATCH 25/29] Cabana: fix right panel layout after undocking charts (#26497) * fix stretch * set window title --- tools/cabana/mainwin.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index f0419a2fb3..7781ab3b75 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -73,6 +73,7 @@ MainWindow::MainWindow() : QMainWindow() { video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); r_layout->addWidget(charts_widget, 1); + r_layout->addStretch(0); main_layout->addWidget(right_container); setCentralWidget(central_widget); @@ -217,11 +218,12 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe void MainWindow::dockCharts(bool dock) { if (dock && floating_window) { floating_window->removeEventFilter(charts_widget); - r_layout->addWidget(charts_widget, 1); + r_layout->insertWidget(2, charts_widget, 1); floating_window->deleteLater(); floating_window = nullptr; } else if (!dock && !floating_window) { floating_window = new QWidget(nullptr); + floating_window->setWindowTitle("Charts - Cabana"); floating_window->setLayout(new QVBoxLayout()); floating_window->layout()->addWidget(charts_widget); floating_window->installEventFilter(charts_widget); From 58b84fb401a804967aa0dd5ee66fafa90194fd30 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 22:18:26 -0800 Subject: [PATCH 26/29] ui: offroad experimental mode button (#26498) * draft * draft * before qpushbutton * icon, clean up button, clicked goes to toggles * fix icon * add imgs * img * make square * works with layouts! * fix gradient * this looks good * clean up * clean up * remove padding around couch * use scene's experimental_model, new onroad design * rename widget * def want 3 * update translations * add img * add 25px of padding! * make 300px (no change) * clean up old images * 5 px smaller * add white img * fix from merge * no style sheets * see how this looks on device * aliased vertical line (clean up) * clean up * imgs * couch * delete * bye bye * expand toggle support * clean up * fix dynamic icon * make exp icon dynamic * order * move to offroad --- .../assets/fonts/JetBrainsMono-Medium.ttf | Bin 0 -> 204140 bytes selfdrive/assets/img_couch.svg | 3 + selfdrive/assets/img_experimental.svg | 10 +++ selfdrive/assets/img_experimental_grey.svg | 4 + selfdrive/assets/img_experimental_white.svg | 4 + selfdrive/ui/SConscript | 2 +- selfdrive/ui/qt/home.cc | 19 ++++- selfdrive/ui/qt/home.h | 5 +- selfdrive/ui/qt/offroad/experimental_mode.cc | 75 ++++++++++++++++++ selfdrive/ui/qt/offroad/experimental_mode.h | 31 ++++++++ selfdrive/ui/qt/offroad/settings.cc | 23 +++++- selfdrive/ui/qt/offroad/settings.h | 5 ++ selfdrive/ui/qt/onroad.cc | 6 +- selfdrive/ui/qt/onroad.h | 1 + selfdrive/ui/qt/sidebar.h | 2 +- selfdrive/ui/qt/window.cc | 4 +- selfdrive/ui/qt/window.h | 2 +- selfdrive/ui/translations/main_ja.ts | 11 +++ selfdrive/ui/translations/main_ko.ts | 11 +++ selfdrive/ui/translations/main_pt-BR.ts | 11 +++ selfdrive/ui/translations/main_zh-CHS.ts | 11 +++ selfdrive/ui/translations/main_zh-CHT.ts | 11 +++ 22 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 selfdrive/assets/fonts/JetBrainsMono-Medium.ttf create mode 100644 selfdrive/assets/img_couch.svg create mode 100644 selfdrive/assets/img_experimental.svg create mode 100644 selfdrive/assets/img_experimental_grey.svg create mode 100644 selfdrive/assets/img_experimental_white.svg create mode 100644 selfdrive/ui/qt/offroad/experimental_mode.cc create mode 100644 selfdrive/ui/qt/offroad/experimental_mode.h diff --git a/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf b/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a6ba5529af2486ea7d2b92b7b8bd598bbee46e29 GIT binary patch literal 204140 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 literal 0 HcmV?d00001 diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg new file mode 100644 index 0000000000..5b3c048318 --- /dev/null +++ b/selfdrive/assets/img_couch.svg @@ -0,0 +1,3 @@ + + + diff --git a/selfdrive/assets/img_experimental.svg b/selfdrive/assets/img_experimental.svg new file mode 100644 index 0000000000..0eaec3b3cd --- /dev/null +++ b/selfdrive/assets/img_experimental.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/img_experimental_grey.svg new file mode 100644 index 0000000000..dc87105ac5 --- /dev/null +++ b/selfdrive/assets/img_experimental_grey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/img_experimental_white.svg new file mode 100644 index 0000000000..ae4f18fde2 --- /dev/null +++ b/selfdrive/assets/img_experimental_white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 84e055752a..669c214746 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -58,7 +58,7 @@ qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc"] + "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc"] qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) if GetOption('test'): qt_src.remove("main.cc") # replaced by test_runner diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 3fe00e6ed9..3f3c9a5885 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -4,6 +4,7 @@ #include #include +#include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/drive_stats.h" #include "selfdrive/ui/qt/widgets/prime.h" @@ -22,7 +23,8 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { slayout = new QStackedLayout(); main_layout->addLayout(slayout); - home = new OffroadHome(); + home = new OffroadHome(this); + QObject::connect(home, &OffroadHome::openSettings, this, &HomeWindow::openSettings); slayout->addWidget(home); onroad = new OnroadWindow(this); @@ -128,11 +130,24 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { main_layout->addSpacing(25); center_layout = new QStackedLayout(); + // Vertical experimental button and drive stats layout + QWidget* statsAndExperimentalModeButtonWidget = new QWidget(this); + QVBoxLayout* statsAndExperimentalModeButton = new QVBoxLayout(statsAndExperimentalModeButtonWidget); + statsAndExperimentalModeButton->setSpacing(30); + statsAndExperimentalModeButton->setMargin(0); + + ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); + QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); + + statsAndExperimentalModeButton->addWidget(experimental_mode, 1); + statsAndExperimentalModeButton->addWidget(new DriveStats, 1); + + // Horizontal experimental + drive stats and setup widget QWidget* statsAndSetupWidget = new QWidget(this); QHBoxLayout* statsAndSetup = new QHBoxLayout(statsAndSetupWidget); statsAndSetup->setMargin(0); statsAndSetup->setSpacing(30); - statsAndSetup->addWidget(new DriveStats, 1); + statsAndSetup->addWidget(statsAndExperimentalModeButtonWidget, 1); statsAndSetup->addWidget(new SetupWidget); center_layout->addWidget(statsAndSetupWidget); diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 6636da56ec..ed1c215467 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -22,6 +22,9 @@ class OffroadHome : public QFrame { public: explicit OffroadHome(QWidget* parent = 0); +signals: + void openSettings(int index = 0, const QString ¶m = ""); + private: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; @@ -45,7 +48,7 @@ public: explicit HomeWindow(QWidget* parent = 0); signals: - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void closeSettings(); public slots: diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc new file mode 100644 index 0000000000..f73149cdf2 --- /dev/null +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -0,0 +1,75 @@ +#include "selfdrive/ui/qt/offroad/experimental_mode.h" + +#include +#include +#include +#include + +#include "selfdrive/ui/ui.h" + +ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { + chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + + // go to toggles and expand experimental mode description + connect(this, &QPushButton::clicked, [=]() { emit openSettings(2, "ExperimentalMode"); }); + + setFixedHeight(125); + QHBoxLayout *main_layout = new QHBoxLayout; + main_layout->setContentsMargins(horizontal_padding, 0, horizontal_padding, 0); + + mode_label = new QLabel; + mode_icon = new QLabel; + mode_icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + + main_layout->addWidget(mode_label, 1, Qt::AlignLeft); + main_layout->addWidget(mode_icon, 0, Qt::AlignRight); + + setLayout(main_layout); + + setStyleSheet(R"( + QPushButton { + border: none; + } + + QLabel { + font-size: 45px; + font-weight: 300; + text-align: left; + font-family: JetBrainsMono; + color: #000000; + } + )"); +} + +void ExperimentalModeButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::Antialiasing); + + QPainterPath path; + path.addRoundedRect(rect(), 10, 10); + + // gradient + bool pressed = isDown(); + QLinearGradient gradient(rect().left(), 0, rect().right(), 0); + if (experimental_mode) { + gradient.setColorAt(0, QColor(255, 155, 63, pressed ? 0xcc : 0xff)); + gradient.setColorAt(1, QColor(219, 56, 34, pressed ? 0xcc : 0xff)); + } else { + gradient.setColorAt(0, QColor(20, 255, 171, pressed ? 0xcc : 0xff)); + gradient.setColorAt(1, QColor(35, 149, 255, pressed ? 0xcc : 0xff)); + } + p.fillPath(path, gradient); + + // vertical line + p.setPen(QPen(QColor(0, 0, 0, 0x4d), 3, Qt::SolidLine)); + int line_x = rect().right() - img_width - (2 * horizontal_padding); + p.drawLine(line_x, rect().bottom(), line_x, rect().top()); +} + +void ExperimentalModeButton::showEvent(QShowEvent *event) { + experimental_mode = params.getBool("ExperimentalMode"); + mode_icon->setPixmap(experimental_mode ? experimental_pixmap : chill_pixmap); + mode_label->setText(experimental_mode ? tr("EXPERIMENTAL MODE ON") : tr("CHILL MODE ON")); +} diff --git a/selfdrive/ui/qt/offroad/experimental_mode.h b/selfdrive/ui/qt/offroad/experimental_mode.h new file mode 100644 index 0000000000..bfb7638bbe --- /dev/null +++ b/selfdrive/ui/qt/offroad/experimental_mode.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "common/params.h" + +class ExperimentalModeButton : public QPushButton { + Q_OBJECT + +public: + explicit ExperimentalModeButton(QWidget* parent = 0); + +signals: + void openSettings(int index = 0, const QString &toggle = ""); + +private: + void showEvent(QShowEvent *event) override; + + Params params; + bool experimental_mode; + int img_width = 100; + int horizontal_padding = 30; + QPixmap experimental_pixmap; + QPixmap chill_pixmap; + QLabel *mode_label; + QLabel *mode_icon; + +protected: + void paintEvent(QPaintEvent *event) override; +}; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 85b09dc183..01cb0ea720 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -39,7 +39,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "ExperimentalMode", tr("Experimental Mode"), "", - "../assets/offroad/icon_road.png", + "../assets/img_experimental_white.svg", }, { "ExperimentalLongitudinalEnabled", @@ -100,6 +100,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } // Toggles with confirmation dialogs + toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); toggles["ExperimentalMode"]->setConfirmation(true, true); toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); @@ -108,6 +109,10 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }); } +void TogglesPanel::expandToggleDescription(const QString ¶m) { + toggles[param.toStdString()]->showDescription(); +} + void TogglesPanel::showEvent(QShowEvent *event) { updateToggles(); } @@ -299,8 +304,15 @@ void DevicePanel::poweroff() { } void SettingsWindow::showEvent(QShowEvent *event) { - panel_widget->setCurrentIndex(0); - nav_btns->buttons()[0]->setChecked(true); + setCurrentPanel(0); +} + +void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { + panel_widget->setCurrentIndex(index); + nav_btns->buttons()[index]->setChecked(true); + if (!param.isEmpty()) { + emit expandToggleDescription(param); + } } SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { @@ -341,10 +353,13 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); + TogglesPanel *toggles = new TogglesPanel(this); + QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); + QList> panels = { {tr("Device"), device}, {tr("Network"), new Networking(this)}, - {tr("Toggles"), new TogglesPanel(this)}, + {tr("Toggles"), toggles}, {tr("Software"), new SoftwarePanel(this)}, }; diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 4177f28cf4..c63be4e138 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -17,6 +17,7 @@ class SettingsWindow : public QFrame { public: explicit SettingsWindow(QWidget *parent = 0); + void setCurrentPanel(int index, const QString ¶m = ""); protected: void showEvent(QShowEvent *event) override; @@ -25,6 +26,7 @@ signals: void closeSettings(); void reviewTrainingGuide(); void showDriverView(); + void expandToggleDescription(const QString ¶m); private: QPushButton *sidebar_alert_widget; @@ -56,6 +58,9 @@ public: explicit TogglesPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; +public slots: + void expandToggleDescription(const QString ¶m); + private: Params params; std::map toggles; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index fcf29181e7..50f891dd56 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -174,6 +174,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par pm = std::make_unique>({"uiDebug"}); engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); + experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size - 5, img_size - 5}); dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size}); } @@ -378,8 +379,9 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { // engage-ability icon if (engageable) { + SubMaster &sm = *(uiState()->sm); drawIcon(p, rect().right() - radius / 2 - bdr_s * 2, radius / 2 + int(bdr_s * 1.5), - engage_img, bg_colors[status], 1.0); + sm["controlsState"].getControlsState().getExperimentalMode() ? experimental_img : engage_img, blackColor(166), 1.0); } // dm icon @@ -409,7 +411,7 @@ void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QB p.setBrush(bg); p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius); p.setOpacity(opacity); - p.drawPixmap(x - img_size / 2, y - img_size / 2, img); + p.drawPixmap(x - img.size().width() / 2, y - img.size().height() / 2, img); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 7edca6b3d5..9e18355970 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -51,6 +51,7 @@ private: void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); QPixmap engage_img; + QPixmap experimental_img; QPixmap dm_img; const int radius = 192; const int img_size = (radius / 2) * 1.5; diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 53ad7467ac..fb96e1d540 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -20,7 +20,7 @@ public: explicit Sidebar(QWidget* parent = 0); signals: - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void valueChanged(); public slots: diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 04ce15ef23..198b1edbf6 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -53,6 +53,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { QFontDatabase::addApplicationFont("../assets/fonts/Inter-Regular.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-SemiBold.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-Thin.ttf"); + QFontDatabase::addApplicationFont("../assets/fonts/JetBrainsMono-Medium.ttf"); // no outline to prevent the focus rectangle setStyleSheet(R"( @@ -64,8 +65,9 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_NoSystemBackground); } -void MainWindow::openSettings() { +void MainWindow::openSettings(int index, const QString ¶m) { main_layout->setCurrentWidget(settingsWindow); + settingsWindow->setCurrentPanel(index, param); } void MainWindow::closeSettings() { diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 0bd328aa8a..71fc466c20 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -15,7 +15,7 @@ public: private: bool eventFilter(QObject *obj, QEvent *event) override; - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void closeSettings(); Device device; diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 21fdc48fef..963eeadd81 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -281,6 +281,17 @@ カメラを起動しています
+ + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 57c688ffe6..9defc2c36f 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -281,6 +281,17 @@ 카메라 시작중 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 2d7195e0d2..a1c966da45 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -281,6 +281,17 @@ câmera iniciando + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index fb9b6dd6f5..e77b3ef63d 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -281,6 +281,17 @@ 正在启动相机 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 86d5482355..f667fdc978 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -281,6 +281,17 @@ 開啟相機中 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog From 1899d439f4aaf8f218ad7d40d8d70bec0d6f151a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 23:11:53 -0800 Subject: [PATCH 27/29] Multilang: refactor experimental description (#26518) * refactor e2e description * forgot to update --- selfdrive/ui/qt/offroad/settings.cc | 23 +++++++++++------------ selfdrive/ui/translations/main_ja.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_ko.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_pt-BR.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_zh-CHS.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_zh-CHT.ts | 22 +++++++++++++++++++--- 6 files changed, 106 insertions(+), 27 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 01cb0ea720..330f636e2b 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -120,18 +120,17 @@ void TogglesPanel::showEvent(QShowEvent *event) { void TogglesPanel::updateToggles() { auto e2e_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; - const QString e2e_description = tr("\ - openpilot defaults to driving in chill mode.\ - Experimental mode enables alpha-level features that aren't ready for chill mode. \ - Experimental features are listed below: \ -
\ -

🌮 End-to-End Longitudinal Control 🌮

\ - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. \ - Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. \ -
\ -

New Driving Visualization

\ - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.\ - "); + const QString e2e_description = QString("%1
" + "

%2


" + "%3
" + "

%4


" + "%5") + .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) + .arg(tr("🌮 End-to-End Longitudinal Control 🌮")) + .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.")) + .arg(tr("New Driving Visualization")) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.");) auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 963eeadd81..2e01ce0dd1 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1018,15 +1018,31 @@ location set
- openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.
diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 9defc2c36f..8ce618edff 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1018,15 +1018,31 @@ location set - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index a1c966da45..2dc1538017 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1022,15 +1022,31 @@ trabalho definido - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index e77b3ef63d..fe569e1fb0 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1016,15 +1016,31 @@ location set - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index f667fdc978..3ec67f3961 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1018,15 +1018,31 @@ location set - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. From d8bc69c788c581485ac9e65e8f6d06e4c368935c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 23:52:47 -0800 Subject: [PATCH 28/29] Multilang: add missing translations (#26519) * add back missing japanese translations that haven't been changed * other languages * fix --- selfdrive/ui/qt/offroad/settings.cc | 2 +- selfdrive/ui/translations/main_ja.ts | 4 ++-- selfdrive/ui/translations/main_ko.ts | 4 ++-- selfdrive/ui/translations/main_pt-BR.ts | 4 ++-- selfdrive/ui/translations/main_zh-CHS.ts | 4 ++-- selfdrive/ui/translations/main_zh-CHT.ts | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 330f636e2b..bde8628dc4 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -130,7 +130,7 @@ void TogglesPanel::updateToggles() { .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.")) .arg(tr("New Driving Visualization")) - .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.");) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 2e01ce0dd1..ebf40cc5c5 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1027,11 +1027,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 エンドツーエンドアクセル制御 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 8ce618edff..98d46149dd 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1027,11 +1027,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험 모드의 특징은 아래에 나열되어 있습니다 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 E2E 롱컨트롤 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 2dc1538017..4a698947f1 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1031,11 +1031,11 @@ trabalho definido openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 Controle Longitudinal de Ponta a Ponta 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index fe569e1fb0..dc94fbc953 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1025,11 +1025,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 端到端(End-to-End) 纵向控制 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 3ec67f3961..46fb5578a3 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1027,11 +1027,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot 默認以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: 🌮 End-to-End Longitudinal Control 🌮 - + 🌮端到端縱向控制🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. From fe2368c9eb14ee49eb59af6d892becdb4d449443 Mon Sep 17 00:00:00 2001 From: Oxygen Date: Wed, 16 Nov 2022 19:11:45 +0800 Subject: [PATCH 29/29] Updated main_zh-CHS.ts (#26524) --- selfdrive/ui/translations/main_zh-CHS.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index dc94fbc953..31202e45f2 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 试验模式运行 CHILL MODE ON - + 轻松模式运行 @@ -1013,15 +1013,15 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + 针对此车辆,openpilot默认使用车辆自带的ACC,而非openpilot的纵向控制。启用此选项将切换到openpilot纵向控制。当使用试验性的openpilot纵向控制时,建议同时启用试验模式。 Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 Enable experimental longitudinal control to allow experimental mode. - + 启用试验性的纵向控制,以便允许使用试验模式。 openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: @@ -1033,15 +1033,15 @@ location set Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - + 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 New Driving Visualization - + 新驾驶视角 The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + 当低速行驶时,驾驶视角将切换到前向广角摄像头,便于更完整地显示转向路径。右上角将显示试验模式图标。