#include #include #include "frogpilot/ui/qt/offroad/vehicle_settings.h" QStringList getCarNames(const QString &carMake, QMap &carModels) { static QMap makeMap = { {"acura", "honda"}, {"audi", "volkswagen"}, {"buick", "gm"}, {"cadillac", "gm"}, {"chevrolet", "gm"}, {"chrysler", "chrysler"}, {"cupra", "volkswagen"}, {"dodge", "chrysler"}, {"ford", "ford"}, {"genesis", "hyundai"}, {"gmc", "gm"}, {"holden", "gm"}, {"honda", "honda"}, {"hyundai", "hyundai"}, {"jeep", "chrysler"}, {"kia", "hyundai"}, {"lexus", "toyota"}, {"lincoln", "ford"}, {"man", "volkswagen"}, {"mazda", "mazda"}, {"nissan", "nissan"}, {"ram", "chrysler"}, {"seat", "volkswagen"}, {"škoda", "volkswagen"}, {"subaru", "subaru"}, {"tesla", "tesla"}, {"toyota", "toyota"}, {"volkswagen", "volkswagen"} }; QStringList carNameList; QFile valuesFile(QString("../car/%1/values.py").arg(makeMap.value(carMake, carMake))); if (!valuesFile.open(QIODevice::ReadOnly | QIODevice::Text)) { return carNameList; } QString fileContent = QTextStream(&valuesFile).readAll(); valuesFile.close(); fileContent.remove(QRegularExpression("#[^\n]*")); fileContent.remove(QRegularExpression("footnotes=\\[[^\\]]*\\],\\s*")); static QRegularExpression carNameRegex("CarDocs\\(\\s*\"([^\"]+)\"[^)]*\\)"); static QRegularExpression platformRegex("((\\w+)\\s*=\\s*\\w+\\s*\\(\\s*\\[([\\s\\S]*?)\\]\\s*,)"); static QRegularExpression validNameRegex("^[A-Za-z0-9 \u0160.()-]+$"); QRegularExpressionMatchIterator platformMatches = platformRegex.globalMatch(fileContent); while (platformMatches.hasNext()) { QRegularExpressionMatch platformMatch = platformMatches.next(); QString platformName = platformMatch.captured(2); QString platformSection = platformMatch.captured(3); QRegularExpressionMatchIterator carNameMatches = carNameRegex.globalMatch(platformSection); while (carNameMatches.hasNext()) { QString carName = carNameMatches.next().captured(1); if (carName.contains(validNameRegex) && carName.count(" ") >= 1) { QString firstWord = carName.section(" ", 0, 0); if (firstWord.compare(carMake, Qt::CaseInsensitive) == 0) { carModels[carName] = platformName; carNameList.append(carName); } } } } carNameList.sort(); return carNameList; } FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent) : FrogPilotListWidget(parent), parent(parent) { QJsonObject shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object(); QString className = this->metaObject()->className(); if (!shownDescriptions.value(className).toBool(false)) { forceOpenDescriptions = true; shownDescriptions.insert(className, true); params.put("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString()); } QStackedLayout *vehiclesLayout = new QStackedLayout(); addItem(vehiclesLayout); FrogPilotListWidget *settingsList = new FrogPilotListWidget(this); ScrollView *vehiclesPanel = new ScrollView(settingsList, this); vehiclesLayout->addWidget(vehiclesPanel); QStringList makes = { "Acura", "Audi", "Buick", "Cadillac", "Chevrolet", "Chrysler", "CUPRA", "Dodge", "Ford", "Genesis", "GMC", "Holden", "Honda", "Hyundai", "Jeep", "Kia", "Lexus", "Lincoln", "MAN", "Mazda", "Nissan", "Ram", "SEAT", "Škoda", "Subaru", "Tesla", "Toyota", "Volkswagen" }; ButtonControl *selectMakeButton = new ButtonControl(tr("Car Make"), tr("SELECT")); QObject::connect(selectMakeButton, &ButtonControl::clicked, [makes, selectMakeButton, this]() { QString makeSelection = MultiOptionDialog::getSelection(tr("Choose your car make"), makes, "", this); if (!makeSelection.isEmpty()) { params.put("CarMake", makeSelection.toStdString()); selectMakeButton->setValue(makeSelection); } }); settingsList->addItem(selectMakeButton); ButtonControl *selectModelButton = new ButtonControl(tr("Car Model"), tr("SELECT")); QObject::connect(selectModelButton, &ButtonControl::clicked, [selectModelButton, this]() { QString modelSelection = MultiOptionDialog::getSelection(tr("Choose your car model"), getCarNames(QString::fromStdString(params.get("CarMake")).toLower(), carModels), "", this); if (!modelSelection.isEmpty()) { params.put("CarModel", carModels.value(modelSelection).toStdString()); params.put("CarModelName", modelSelection.toStdString()); selectModelButton->setValue(modelSelection); } }); settingsList->addItem(selectModelButton); forceFingerprint = new ParamControl("ForceFingerprint", tr("Disable Automatic Fingerprint Detection"), tr("Force the selected fingerprint and prevent it from ever changing."), ""); settingsList->addItem(forceFingerprint); disableOpenpilotLong = new ParamControl("DisableOpenpilotLongitudinal", tr("Disable openpilot Longitudinal Control"), tr("Disable openpilot longitudinal and use the car's stock ACC instead."), ""); QObject::connect(disableOpenpilotLong, &ToggleControl::toggleFlipped, [parent, this](bool state) { if (state) { if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely disable openpilot longitudinal control?"), this)) { if (started) { if (FrogPilotConfirmationDialog::toggleReboot(this)) { Hardware::reboot(); } } } else { params.putBool("DisableOpenpilotLongitudinal", false); disableOpenpilotLong->refresh(); } } parent->updateVariables(); updateToggles(); }); settingsList->addItem(disableOpenpilotLong); FrogPilotListWidget *gmList = new FrogPilotListWidget(this); FrogPilotListWidget *hkgList = new FrogPilotListWidget(this); FrogPilotListWidget *hondaList = new FrogPilotListWidget(this); FrogPilotListWidget *subaruList = new FrogPilotListWidget(this); FrogPilotListWidget *toyotaList = new FrogPilotListWidget(this); FrogPilotListWidget *vehicleInfoList = new FrogPilotListWidget(this); ScrollView *gmPanel = new ScrollView(gmList, this); ScrollView *hkgPanel = new ScrollView(hkgList, this); ScrollView *hondaPanel = new ScrollView(hondaList, this); ScrollView *subaruPanel = new ScrollView(subaruList, this); ScrollView *toyotaPanel = new ScrollView(toyotaList, this); ScrollView *vehicleInfoPanel = new ScrollView(vehicleInfoList, this); vehiclesLayout->addWidget(gmPanel); vehiclesLayout->addWidget(hkgPanel); vehiclesLayout->addWidget(hondaPanel); vehiclesLayout->addWidget(subaruPanel); vehiclesLayout->addWidget(toyotaPanel); vehiclesLayout->addWidget(vehicleInfoPanel); std::vector> vehicleToggles { {"GMToggles", tr("General Motors Settings"), tr("FrogPilot features for General Motors vehicles."), ""}, {"ExperimentalGMTune", tr("FrogsGoMoo's Experimental Tune"), tr("Experimental GM tune by FrogsGoMoo that attempts to smoothen stopping and takeoff control. Use at your own risk!"), ""}, {"LongPitch", tr("Smooth Pedal Response on Hills"), tr("Smoothen acceleration and braking when driving downhill/uphill."), ""}, {"VoltSNG", tr("Stop-and-Go Hack"), tr("Force stop-and-go on the 2017 Chevy Volt."), ""}, {"HKGToggles", tr("Hyundai/Kia/Genesis Settings"), tr("FrogPilot features for Genesis, Hyundai, and Kia vehicles."), ""}, {"NewLongAPI", tr("comma's New Longitudinal API"), tr("comma's new gas and brake control system that improves acceleration and braking but may cause issues on some Genesis/Hyundai/Kia vehicles."), ""}, {"TacoTuneHacks", tr("\"Taco Bell Run\" Torque Hack"), tr("The steering torque hack from comma's 2022 \"Taco Bell Run\". Designed to increase steering torque at low speeds for left and right turns."), ""}, {"HondaToggles", tr("Acura/Honda Settings"), tr("FrogPilot features for Acura and Honda vehicles."), ""}, {"HondaAltTune", tr("Gentle Following"), tr("Reduces jerky acceleration and braking when following a lead vehicle. Ideal for stop-and-go traffic."), ""}, {"HondaMaxBrake", tr("Increased Braking Force"), tr("Increases the maximum braking force for improved stopping performance."), ""}, {"HondaLowSpeedPedal", tr("Responsive Pedal at Low Speeds"), tr("Improves acceleration from a standstill for a more responsive throttle feel in city driving."), ""}, {"SubaruToggles", tr("Subaru Settings"), tr("FrogPilot features for Subaru vehicles."), ""}, {"SubaruSNG", tr("Stop and Go"), tr("Stop and go for supported Subaru vehicles."), ""}, {"ToyotaToggles", tr("Toyota/Lexus Settings"), tr("FrogPilot features for Lexus and Toyota vehicles."), ""}, {"ToyotaDoors", tr("Automatically Lock/Unlock Doors"), tr("Automatically lock/unlock doors when shifting in and out of drive."), ""}, {"ClusterOffset", tr("Dashboard Speed Offset"), tr("The speed offset openpilot uses to match the speed on the dashboard display."), ""}, {"FrogsGoMoosTweak", tr("FrogsGoMoo's Personal Tweaks"), tr("Personal tweaks by FrogsGoMoo for quicker acceleration and smoother braking."), ""}, {"LockDoorsTimer", tr("Lock Doors On Ignition Off After"), tr("Automatically lock the doors on ignition off when no one is detected in the front seats."), ""}, {"SNGHack", tr("Stop-and-Go Hack"), tr("Force stop-and-go on Lexus/Toyota vehicles without stock stop-and-go functionality."), ""}, {"VehicleInfo", tr("Vehicle Info"), tr("Information about your vehicle in regards to openpilot support and functionality."), ""}, {"HardwareDetected", tr("3rd Party Hardware Detected"), tr("Detected 3rd party hardware."), ""}, {"BlindSpotSupport", tr("Blind Spot Support"), tr("Does openpilot use the vehicle's blind spot data?"), ""}, {"PedalSupport", tr("comma Pedal Support"), tr("Does your vehicle support the \"comma pedal\"?"), ""}, {"OpenpilotLongitudinal", tr("openpilot Longitudinal Support"), tr("Can openpilot control the vehicle's acceleration and braking?"), ""}, {"RadarSupport", tr("Radar Support"), tr("Does openpilot use the vehicle's radar data alongside the device's camera for tracking lead vehicles?"), ""}, {"SDSUSupport", tr("SDSU Support"), tr("Does your vehicle support \"SDSUs\"?"), ""}, {"SNGSupport", tr("Stop-and-Go Support"), tr("Does your vehicle support stop-and-go driving?"), ""} }; for (const auto &[param, title, desc, icon] : vehicleToggles) { AbstractControl *vehicleToggle; if (param == "GMToggles") { ButtonControl *gmButton = new ButtonControl(title, tr("MANAGE"), desc); QObject::connect(gmButton, &ButtonControl::clicked, [vehiclesLayout, gmPanel, this]() { openDescriptions(forceOpenDescriptions, toggles); vehiclesLayout->setCurrentWidget(gmPanel); }); vehicleToggle = gmButton; } else if (param == "HKGToggles") { ButtonControl *hkgButton = new ButtonControl(title, tr("MANAGE"), desc); QObject::connect(hkgButton, &ButtonControl::clicked, [vehiclesLayout, hkgPanel, this]() { openDescriptions(forceOpenDescriptions, toggles); vehiclesLayout->setCurrentWidget(hkgPanel); }); vehicleToggle = hkgButton; } else if (param == "HondaToggles") { ButtonControl *hondaButton = new ButtonControl(title, tr("MANAGE"), desc); QObject::connect(hondaButton, &ButtonControl::clicked, [vehiclesLayout, hondaPanel, this]() { openDescriptions(forceOpenDescriptions, toggles); vehiclesLayout->setCurrentWidget(hondaPanel); }); vehicleToggle = hondaButton; } else if (param == "SubaruToggles") { ButtonControl *subaruButton = new ButtonControl(title, tr("MANAGE"), desc); QObject::connect(subaruButton, &ButtonControl::clicked, [vehiclesLayout, subaruPanel, this]() { openDescriptions(forceOpenDescriptions, toggles); vehiclesLayout->setCurrentWidget(subaruPanel); }); vehicleToggle = subaruButton; } else if (param == "ToyotaToggles") { ButtonControl *toyotaButton = new ButtonControl(title, tr("MANAGE"), desc); QObject::connect(toyotaButton, &ButtonControl::clicked, [vehiclesLayout, toyotaPanel, this]() { openDescriptions(forceOpenDescriptions, toggles); vehiclesLayout->setCurrentWidget(toyotaPanel); }); vehicleToggle = toyotaButton; } else if (param == "ToyotaDoors") { std::vector lockToggles{"LockDoors", "UnlockDoors"}; std::vector lockToggleNames{tr("Lock"), tr("Unlock")}; vehicleToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, lockToggles, lockToggleNames); } else if (param == "LockDoorsTimer") { std::map autoLockLabels; for (int i = 0; i <= 300; ++i) { autoLockLabels[i] = i == 0 ? tr("Never") : QString::number(i) + tr(" seconds"); } vehicleToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 300, QString(), autoLockLabels, 5); } else if (param == "ClusterOffset") { std::vector clusterOffsetButton{"Reset"}; FrogPilotParamValueButtonControl *clusterOffsetToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 1.000, 1.050, "x", std::map(), 0.001, false, {}, clusterOffsetButton, false, false); QObject::connect(clusterOffsetToggle, &FrogPilotParamValueButtonControl::buttonClicked, [clusterOffsetToggle, this]() { params.putFloat("ClusterOffset", params_default.getFloat("ClusterOffset")); clusterOffsetToggle->refresh(); }); vehicleToggle = clusterOffsetToggle; } else if (param == "VehicleInfo") { ButtonControl *VehicleInfoButton = new ButtonControl(title, tr("VIEW"), desc); QObject::connect(VehicleInfoButton, &ButtonControl::clicked, [vehiclesLayout, vehicleInfoPanel, this]() { openDescriptions(forceOpenDescriptions, toggles); vehiclesLayout->setCurrentWidget(vehicleInfoPanel); }); vehicleToggle = VehicleInfoButton; } else if (vehicleInfoKeys.contains(param)) { vehicleToggle = new LabelControl(title, "", desc); } else { vehicleToggle = new ParamControl(param, title, desc, icon); } toggles[param] = vehicleToggle; if (gmKeys.contains(param)) { gmList->addItem(vehicleToggle); } else if (hkgKeys.contains(param)) { hkgList->addItem(vehicleToggle); } else if (hondaKeys.contains(param)) { hondaList->addItem(vehicleToggle); } else if (subaruKeys.contains(param)) { subaruList->addItem(vehicleToggle); } else if (toyotaKeys.contains(param)) { toyotaList->addItem(vehicleToggle); } else if (vehicleInfoKeys.contains(param)) { vehicleInfoList->addItem(vehicleToggle); } else { settingsList->addItem(vehicleToggle); parentKeys.insert(param); } if (ButtonControl *buttonControl = qobject_cast(vehicleToggle)) { QObject::connect(buttonControl, &ButtonControl::clicked, this, &FrogPilotVehiclesPanel::openSubPanel); } QObject::connect(vehicleToggle, &AbstractControl::hideDescriptionEvent, [this]() { update(); }); QObject::connect(vehicleToggle, &AbstractControl::showDescriptionEvent, [this]() { update(); }); } static_cast(toggles["LockDoorsTimer"])->setWarning("Warning: openpilot can't detect if keys are still inside the car, so ensure you have a spare key to prevent accidental lockouts!"); QSet rebootKeys = {"HondaAltTune", "NewLongAPI", "TacoTuneHacks"}; for (const QString &key : rebootKeys) { QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) { if (started) { if (key == "HondaAltTune" || key == "TacoTuneHacks" && state) { if (FrogPilotConfirmationDialog::toggleReboot(this)) { Hardware::reboot(); } } else if (key != "TacoTuneHacks") { if (FrogPilotConfirmationDialog::toggleReboot(this)) { Hardware::reboot(); } } } }); } openDescriptions(forceOpenDescriptions, toggles); QObject::connect(uiState(), &UIState::offroadTransition, [selectMakeButton, selectModelButton, this]() { std::thread([selectMakeButton, selectModelButton, this]() { selectMakeButton->setValue(QString::fromStdString(params.get("CarMake", true))); selectModelButton->setValue(QString::fromStdString(params.get(params.get("CarModelName").empty() ? "CarModel" : "CarModelName"))); }).detach(); }); QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [vehiclesLayout, vehiclesPanel, this] { if (forceOpenDescriptions) { openDescriptions(forceOpenDescriptions, toggles); disableOpenpilotLong->showDescription(); forceFingerprint->showDescription(); } vehiclesLayout->setCurrentWidget(vehiclesPanel); }); QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotVehiclesPanel::updateState); } void FrogPilotVehiclesPanel::showEvent(QShowEvent *event) { if (forceOpenDescriptions) { disableOpenpilotLong->showDescription(); forceFingerprint->showDescription(); } frogpilotToggleLevels = parent->frogpilotToggleLevels; QStringList detected; if (parent->hasPedal) detected << "comma Pedal"; if (parent->hasSDSU) detected << "SDSU"; if (parent->hasZSS) detected << "ZSS"; static_cast(toggles["HardwareDetected"])->setText(detected.isEmpty() ? tr("None") : detected.join(", ")); static_cast(toggles["BlindSpotSupport"])->setText(parent->hasBSM ? tr("Yes") : tr("No")); static_cast(toggles["OpenpilotLongitudinal"])->setText(parent->hasOpenpilotLongitudinal ? tr("Yes") : tr("No")); static_cast(toggles["PedalSupport"])->setText(parent->canUsePedal ? tr("Yes") : tr("No")); static_cast(toggles["RadarSupport"])->setText(parent->hasRadar ? tr("Yes") : tr("No")); static_cast(toggles["SDSUSupport"])->setText(parent->canUseSDSU ? tr("Yes") : tr("No")); static_cast(toggles["SNGSupport"])->setText(parent->hasSNG ? tr("Yes") : tr("No")); updateToggles(); } void FrogPilotVehiclesPanel::updateState(const UIState &s) { if (!isVisible()) { return; } started = s.scene.started; } void FrogPilotVehiclesPanel::updateToggles() { for (auto &[key, toggle] : toggles) { if (parentKeys.contains(key)) { toggle->setVisible(false); } } for (auto &[key, toggle] : toggles) { if (parentKeys.contains(key)) { continue; } bool setVisible = parent->tuningLevel >= frogpilotToggleLevels[key].toDouble(); if (gmKeys.contains(key)) { setVisible &= parent->isGM; } else if (hkgKeys.contains(key)) { setVisible &= parent->isHKG; } else if (hondaKeys.contains(key)) { setVisible &= parent->isHonda; } else if (subaruKeys.contains(key)) { setVisible &= parent->isSubaru; } else if (toyotaKeys.contains(key)) { setVisible &= parent->isToyota; } else if (vehicleInfoKeys.contains(key)) { setVisible = true; } if (longitudinalKeys.contains(key)) { setVisible &= parent->hasOpenpilotLongitudinal; } if (key == "HondaAltTune") { setVisible &= parent->isHondaNidec; } else if (key == "HondaLowSpeedPedal") { setVisible &= parent->hasPedal; } else if (key == "HondaMaxBrake") { setVisible &= parent->isHondaNidec; } else if (key == "LockDoorsTimer") { setVisible &= !parent->isC3; } else if (key == "SNGHack") { setVisible &= !parent->hasPedal && !parent->hasSNG; } else if (key == "SubaruSNG") { setVisible &= parent->hasSNG; } else if (key == "TacoTuneHacks") { setVisible &= parent->isHKGCanFd; } else if (key == "VoltSNG") { setVisible &= parent->isVolt && !parent->hasSNG; } toggle->setVisible(setVisible); if (setVisible) { if (gmKeys.contains(key)) { toggles["GMToggles"]->setVisible(true); } else if (hkgKeys.contains(key)) { toggles["HKGToggles"]->setVisible(true); } else if (hondaKeys.contains(key)) { toggles["HondaToggles"]->setVisible(true); } else if (subaruKeys.contains(key)) { toggles["SubaruToggles"]->setVisible(true); } else if (toyotaKeys.contains(key)) { toggles["ToyotaToggles"]->setVisible(true); } else if (vehicleInfoKeys.contains(key)) { toggles["VehicleInfo"]->setVisible(true); } } } disableOpenpilotLong->setVisible((parent->hasOpenpilotLongitudinal || parent->openpilotLongitudinalControlDisabled) && !parent->hasExperimentalOpenpilotLongitudinal && parent->tuningLevel >= frogpilotToggleLevels["DisableOpenpilotLongitudinal"].toBool()); forceFingerprint->setVisible(parent->tuningLevel >= frogpilotToggleLevels["ForceFingerprint"].toBool()); openDescriptions(forceOpenDescriptions, toggles); update(); }