From ea1aad5ed1423f3b88647d799b8d808199408328 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 27 Apr 2024 13:43:16 -0500 Subject: [PATCH] wip --- selfdrive/ui/.gitignore | 6 +- selfdrive/ui/SConscript | 53 +- selfdrive/ui/__init__.py | 0 selfdrive/ui/installer/continue_openpilot.sh | 4 + selfdrive/ui/installer/installer.cc | 4 +- selfdrive/ui/installer/installer.h | 0 selfdrive/ui/main.cc | 0 selfdrive/ui/mui.cc | 50 + selfdrive/ui/qt/api.cc | 0 selfdrive/ui/qt/api.h | 0 selfdrive/ui/qt/body.cc | 163 +- selfdrive/ui/qt/body.good | 60 - selfdrive/ui/qt/body.h | 30 +- selfdrive/ui/qt/body.h.org | 38 - selfdrive/ui/qt/body.org | 161 -- selfdrive/ui/qt/body.webbrowser.test | 52 - selfdrive/ui/qt/home.cc | 53 +- selfdrive/ui/qt/home.h | 8 +- selfdrive/ui/qt/maps/map.cc | 83 +- selfdrive/ui/qt/maps/map.h | 17 +- selfdrive/ui/qt/maps/map_eta.cc | 0 selfdrive/ui/qt/maps/map_eta.h | 0 selfdrive/ui/qt/maps/map_helpers.cc | 37 +- selfdrive/ui/qt/maps/map_helpers.h | 17 +- selfdrive/ui/qt/maps/map_instructions.cc | 0 selfdrive/ui/qt/maps/map_instructions.h | 0 selfdrive/ui/qt/maps/map_panel.cc | 7 +- selfdrive/ui/qt/maps/map_panel.h | 5 +- selfdrive/ui/qt/maps/map_settings.cc | 16 +- selfdrive/ui/qt/maps/map_settings.h | 5 +- selfdrive/ui/qt/network/networking.cc | 12 +- selfdrive/ui/qt/network/networking.h | 0 selfdrive/ui/qt/network/networkmanager.h | 0 selfdrive/ui/qt/network/wifi_manager.cc | 3 +- selfdrive/ui/qt/network/wifi_manager.h | 2 +- selfdrive/ui/qt/offroad/driverview.cc | 0 selfdrive/ui/qt/offroad/driverview.h | 0 selfdrive/ui/qt/offroad/experimental_mode.cc | 0 selfdrive/ui/qt/offroad/experimental_mode.h | 0 selfdrive/ui/qt/offroad/onboarding.cc | 14 +- selfdrive/ui/qt/offroad/onboarding.h | 0 selfdrive/ui/qt/offroad/settings.cc | 385 ++++- selfdrive/ui/qt/offroad/settings.h | 27 +- selfdrive/ui/qt/offroad/software_settings.cc | 152 +- selfdrive/ui/qt/offroad/text_view.qml | 0 selfdrive/ui/qt/onroad.cc | 1511 ++++++++++------- selfdrive/ui/qt/onroad.h | 132 +- selfdrive/ui/qt/python_helpers.py | 20 + selfdrive/ui/qt/qt_window.cc | 0 selfdrive/ui/qt/qt_window.h | 0 selfdrive/ui/qt/request_repeater.cc | 0 selfdrive/ui/qt/request_repeater.h | 0 selfdrive/ui/qt/setup/reset.cc | 9 +- selfdrive/ui/qt/setup/reset.h | 1 - selfdrive/ui/qt/setup/setup.cc | 122 +- selfdrive/ui/qt/setup/setup.h | 1 + selfdrive/ui/qt/setup/updater.cc | 31 +- selfdrive/ui/qt/setup/updater.h | 3 - selfdrive/ui/qt/sidebar.cc | 125 +- selfdrive/ui/qt/sidebar.h | 21 +- selfdrive/ui/qt/spinner.cc | 4 +- selfdrive/ui/qt/spinner.h | 0 selfdrive/ui/qt/text.cc | 48 +- selfdrive/ui/qt/util.cc | 8 +- selfdrive/ui/qt/util.h | 0 selfdrive/ui/qt/widgets/cameraview.cc | 3 +- selfdrive/ui/qt/widgets/cameraview.h | 0 selfdrive/ui/qt/widgets/controls.cc | 0 selfdrive/ui/qt/widgets/controls.h | 29 +- selfdrive/ui/qt/widgets/drive_stats.cc | 40 +- selfdrive/ui/qt/widgets/drive_stats.h | 6 +- selfdrive/ui/qt/widgets/input.cc | 0 selfdrive/ui/qt/widgets/input.h | 0 selfdrive/ui/qt/widgets/keyboard.cc | 0 selfdrive/ui/qt/widgets/keyboard.h | 0 selfdrive/ui/qt/widgets/offroad_alerts.cc | 10 +- selfdrive/ui/qt/widgets/offroad_alerts.h | 0 selfdrive/ui/qt/widgets/prime.cc | 0 selfdrive/ui/qt/widgets/prime.h | 0 selfdrive/ui/qt/widgets/scrollview.cc | 0 selfdrive/ui/qt/widgets/scrollview.h | 1 + selfdrive/ui/qt/widgets/ssh_keys.cc | 0 selfdrive/ui/qt/widgets/ssh_keys.h | 0 selfdrive/ui/qt/widgets/toggle.cc | 2 +- selfdrive/ui/qt/widgets/toggle.h | 0 selfdrive/ui/qt/widgets/wifi.cc | 31 +- selfdrive/ui/qt/widgets/wifi.h | 4 + selfdrive/ui/qt/window.cc | 37 +- selfdrive/ui/qt/window.h | 3 +- selfdrive/ui/soundd.py | 114 +- selfdrive/ui/spinner | 6 +- selfdrive/ui/tests/.gitignore | 6 + selfdrive/ui/tests/__init__.py | 0 selfdrive/ui/tests/body.py | 22 + .../ui/tests/create_test_translations.sh | 18 + selfdrive/ui/tests/cycle_offroad_alerts.py | 36 + selfdrive/ui/tests/playsound.cc | 30 + selfdrive/ui/tests/test_runner.cc | 25 + selfdrive/ui/tests/test_soundd.py | 41 + selfdrive/ui/tests/test_translations.cc | 48 + selfdrive/ui/tests/test_translations.py | 6 +- selfdrive/ui/tests/test_ui/run.py | 198 +++ selfdrive/ui/tests/test_ui/template.html | 34 + selfdrive/ui/tests/ui_snapshot.cc | 66 + selfdrive/ui/tests/ui_snapshot.h | 5 + selfdrive/ui/text | 6 +- selfdrive/ui/translations/README.md | 71 + selfdrive/ui/translations/auto_translate.py | 138 ++ selfdrive/ui/translations/create_badges.py | 62 + selfdrive/ui/translations/languages.json | 0 selfdrive/ui/translations/main_ar.ts | 32 +- selfdrive/ui/translations/main_de.ts | 30 +- selfdrive/ui/translations/main_en.ts | 0 selfdrive/ui/translations/main_fr.ts | 42 +- selfdrive/ui/translations/main_ja.ts | 30 +- selfdrive/ui/translations/main_ko.ts | 42 +- selfdrive/ui/translations/main_nl.ts | 0 selfdrive/ui/translations/main_pl.ts | 0 selfdrive/ui/translations/main_pt-BR.ts | 42 +- selfdrive/ui/translations/main_th.ts | 32 +- selfdrive/ui/translations/main_tr.ts | 28 +- selfdrive/ui/translations/main_zh-CHS.ts | 42 +- selfdrive/ui/translations/main_zh-CHT.ts | 42 +- selfdrive/ui/ui.cc | 318 ++-- selfdrive/ui/ui.h | 94 +- selfdrive/ui/ui.py | 77 + selfdrive/ui/update_translations.py | 2 +- selfdrive/ui/watch3.cc | 0 128 files changed, 3533 insertions(+), 1918 deletions(-) mode change 100755 => 100644 selfdrive/ui/.gitignore mode change 100755 => 100644 selfdrive/ui/SConscript create mode 100644 selfdrive/ui/__init__.py create mode 100644 selfdrive/ui/installer/continue_openpilot.sh mode change 100755 => 100644 selfdrive/ui/installer/installer.cc mode change 100755 => 100644 selfdrive/ui/installer/installer.h mode change 100755 => 100644 selfdrive/ui/main.cc create mode 100644 selfdrive/ui/mui.cc mode change 100755 => 100644 selfdrive/ui/qt/api.cc mode change 100755 => 100644 selfdrive/ui/qt/api.h mode change 100755 => 100644 selfdrive/ui/qt/body.cc delete mode 100644 selfdrive/ui/qt/body.good mode change 100755 => 100644 selfdrive/ui/qt/body.h delete mode 100644 selfdrive/ui/qt/body.h.org delete mode 100644 selfdrive/ui/qt/body.org delete mode 100644 selfdrive/ui/qt/body.webbrowser.test mode change 100755 => 100644 selfdrive/ui/qt/home.cc mode change 100755 => 100644 selfdrive/ui/qt/home.h mode change 100755 => 100644 selfdrive/ui/qt/maps/map.cc mode change 100755 => 100644 selfdrive/ui/qt/maps/map.h mode change 100755 => 100644 selfdrive/ui/qt/maps/map_eta.cc mode change 100755 => 100644 selfdrive/ui/qt/maps/map_eta.h mode change 100755 => 100644 selfdrive/ui/qt/maps/map_helpers.cc mode change 100755 => 100644 selfdrive/ui/qt/maps/map_helpers.h mode change 100755 => 100644 selfdrive/ui/qt/maps/map_instructions.cc mode change 100755 => 100644 selfdrive/ui/qt/maps/map_instructions.h mode change 100755 => 100644 selfdrive/ui/qt/maps/map_panel.cc mode change 100755 => 100644 selfdrive/ui/qt/maps/map_panel.h mode change 100755 => 100644 selfdrive/ui/qt/maps/map_settings.cc mode change 100755 => 100644 selfdrive/ui/qt/maps/map_settings.h mode change 100755 => 100644 selfdrive/ui/qt/network/networking.cc mode change 100755 => 100644 selfdrive/ui/qt/network/networking.h mode change 100755 => 100644 selfdrive/ui/qt/network/networkmanager.h mode change 100755 => 100644 selfdrive/ui/qt/network/wifi_manager.cc mode change 100755 => 100644 selfdrive/ui/qt/network/wifi_manager.h mode change 100755 => 100644 selfdrive/ui/qt/offroad/driverview.cc mode change 100755 => 100644 selfdrive/ui/qt/offroad/driverview.h mode change 100755 => 100644 selfdrive/ui/qt/offroad/experimental_mode.cc mode change 100755 => 100644 selfdrive/ui/qt/offroad/experimental_mode.h mode change 100755 => 100644 selfdrive/ui/qt/offroad/onboarding.cc mode change 100755 => 100644 selfdrive/ui/qt/offroad/onboarding.h mode change 100755 => 100644 selfdrive/ui/qt/offroad/settings.cc mode change 100755 => 100644 selfdrive/ui/qt/offroad/settings.h mode change 100755 => 100644 selfdrive/ui/qt/offroad/software_settings.cc mode change 100755 => 100644 selfdrive/ui/qt/offroad/text_view.qml mode change 100755 => 100644 selfdrive/ui/qt/onroad.cc mode change 100755 => 100644 selfdrive/ui/qt/onroad.h create mode 100644 selfdrive/ui/qt/python_helpers.py mode change 100755 => 100644 selfdrive/ui/qt/qt_window.cc mode change 100755 => 100644 selfdrive/ui/qt/qt_window.h mode change 100755 => 100644 selfdrive/ui/qt/request_repeater.cc mode change 100755 => 100644 selfdrive/ui/qt/request_repeater.h mode change 100755 => 100644 selfdrive/ui/qt/setup/reset.cc mode change 100755 => 100644 selfdrive/ui/qt/setup/reset.h mode change 100755 => 100644 selfdrive/ui/qt/setup/setup.cc mode change 100755 => 100644 selfdrive/ui/qt/setup/setup.h mode change 100755 => 100644 selfdrive/ui/qt/setup/updater.cc mode change 100755 => 100644 selfdrive/ui/qt/setup/updater.h mode change 100755 => 100644 selfdrive/ui/qt/sidebar.cc mode change 100755 => 100644 selfdrive/ui/qt/sidebar.h mode change 100755 => 100644 selfdrive/ui/qt/spinner.cc mode change 100755 => 100644 selfdrive/ui/qt/spinner.h mode change 100755 => 100644 selfdrive/ui/qt/text.cc mode change 100755 => 100644 selfdrive/ui/qt/util.cc mode change 100755 => 100644 selfdrive/ui/qt/util.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/cameraview.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/cameraview.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/controls.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/controls.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/drive_stats.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/drive_stats.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/input.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/input.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/keyboard.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/keyboard.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/offroad_alerts.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/offroad_alerts.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/prime.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/prime.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/scrollview.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/scrollview.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/ssh_keys.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/ssh_keys.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/toggle.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/toggle.h mode change 100755 => 100644 selfdrive/ui/qt/widgets/wifi.cc mode change 100755 => 100644 selfdrive/ui/qt/widgets/wifi.h mode change 100755 => 100644 selfdrive/ui/qt/window.cc mode change 100755 => 100644 selfdrive/ui/qt/window.h mode change 100755 => 100644 selfdrive/ui/soundd.py create mode 100644 selfdrive/ui/tests/.gitignore create mode 100644 selfdrive/ui/tests/__init__.py create mode 100644 selfdrive/ui/tests/body.py create mode 100644 selfdrive/ui/tests/create_test_translations.sh create mode 100644 selfdrive/ui/tests/cycle_offroad_alerts.py create mode 100644 selfdrive/ui/tests/playsound.cc create mode 100644 selfdrive/ui/tests/test_runner.cc create mode 100644 selfdrive/ui/tests/test_soundd.py create mode 100644 selfdrive/ui/tests/test_translations.cc create mode 100644 selfdrive/ui/tests/test_ui/run.py create mode 100644 selfdrive/ui/tests/test_ui/template.html create mode 100644 selfdrive/ui/tests/ui_snapshot.cc create mode 100644 selfdrive/ui/tests/ui_snapshot.h create mode 100644 selfdrive/ui/translations/README.md create mode 100644 selfdrive/ui/translations/auto_translate.py create mode 100644 selfdrive/ui/translations/create_badges.py mode change 100755 => 100644 selfdrive/ui/translations/languages.json mode change 100755 => 100644 selfdrive/ui/translations/main_ar.ts mode change 100755 => 100644 selfdrive/ui/translations/main_de.ts mode change 100755 => 100644 selfdrive/ui/translations/main_en.ts mode change 100755 => 100644 selfdrive/ui/translations/main_fr.ts mode change 100755 => 100644 selfdrive/ui/translations/main_ja.ts mode change 100755 => 100644 selfdrive/ui/translations/main_ko.ts mode change 100755 => 100644 selfdrive/ui/translations/main_nl.ts mode change 100755 => 100644 selfdrive/ui/translations/main_pl.ts mode change 100755 => 100644 selfdrive/ui/translations/main_pt-BR.ts mode change 100755 => 100644 selfdrive/ui/translations/main_th.ts mode change 100755 => 100644 selfdrive/ui/translations/main_tr.ts mode change 100755 => 100644 selfdrive/ui/translations/main_zh-CHS.ts mode change 100755 => 100644 selfdrive/ui/translations/main_zh-CHT.ts mode change 100755 => 100644 selfdrive/ui/ui.cc mode change 100755 => 100644 selfdrive/ui/ui.h create mode 100644 selfdrive/ui/ui.py mode change 100755 => 100644 selfdrive/ui/watch3.cc diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore old mode 100755 new mode 100644 index 99f9097..f972481 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -3,11 +3,13 @@ moc_* translations/main_test_en.* +_text +_spinner + ui +mui watch3 installer/installers/* -qt/text -qt/spinner qt/setup/setup qt/setup/reset qt/setup/wifi diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript old mode 100755 new mode 100644 index 8a90134..1e6f8f6 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -11,29 +11,26 @@ if arch == 'larch64': maps = arch in ['larch64', 'aarch64', 'x86_64'] -if maps and arch != 'larch64': - rpath = [Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath] - qt_env["RPATH"] += rpath - if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] qt_env['FRAMEWORKS'] += ['OpenCL'] +# FIXME: remove this once we're on 5.15 (24.04) +qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] + qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/widgets/wifi.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc", - "../frogpilot/ui/frogpilot_functions.cc", "../frogpilot/navigation/ui/navigation_settings.cc", - "../frogpilot/ui/control_settings.cc", "../frogpilot/ui/vehicle_settings.cc", - "../frogpilot/ui/visual_settings.cc", - "../oscarpilot/settings/settings.cc", "../oscarpilot/settings/basic.cc", - ] + "../frogpilot/ui/qt/widgets/frogpilot_controls.cc", "../frogpilot/navigation/ui/navigation_settings.cc", + "../frogpilot/ui/qt/offroad/control_settings.cc", "../frogpilot/ui/qt/offroad/vehicle_settings.cc", + "../frogpilot/ui/qt/offroad/visual_settings.cc"] qt_env['CPPDEFINES'] = [] if maps: - base_libs += ['qmapboxgl'] + base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", "qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"] qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] @@ -48,21 +45,6 @@ qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "../frogpilot/screenrecorder/omx_encoder.cc", "../frogpilot/screenrecorder/screenrecorder.cc"] -def is_running_on_wsl2(): - try: - with open('/proc/version', 'r') as f: - return 'WSL2' in f.read() - except FileNotFoundError: - return False - -if is_running_on_wsl2(): - qt_env.Append(CXXFLAGS=['-DWSL2']) - base_libs.remove('OmxCore') - qt_libs.remove('OmxCore') - qt_src.remove("../frogpilot/screenrecorder/screenrecorder.cc") - qt_src.remove("../frogpilot/screenrecorder/omx_encoder.cc") - print("Building for WSL2. Removing Screen Recorder") - # build translation files with open(File("translations/languages.json").abspath) as f: languages = json.loads(f.read()) @@ -94,8 +76,8 @@ asset_obj = qt_env.Object("assets", assets) qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) # spinner and text window -qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs) -qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) +qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs) +qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) # build main UI qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) @@ -115,28 +97,25 @@ if GetOption('extras') and arch != "Darwin": # build updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) + # build mui + qt_env.Program("mui", ["mui.cc"], LIBS=qt_libs) + # build installers senv = qt_env.Clone() senv['LINKFLAGS'].append('-Wl,-strip-debug') release = "release3" - dashcam = "dashcam3" installers = [ ("openpilot", release), ("openpilot_test", f"{release}-staging"), ("openpilot_nightly", "nightly"), ("openpilot_internal", "master"), - ("dashcam", dashcam), - ("dashcam_test", f"{dashcam}-staging"), ] - cont = {} - for brand in ("openpilot", "dashcam"): - cont[brand] = senv.Command(f"installer/continue_{brand}.o", f"installer/continue_{brand}.sh", - "ld -r -b binary -o $TARGET $SOURCE") + cont = senv.Command(f"installer/continue_openpilot.o", f"installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") for name, branch in installers: - brand = "dashcam" if "dashcam" in branch else "openpilot" - d = {'BRANCH': f"'\"{branch}\"'", 'BRAND': f"'\"{brand}\"'"} + d = {'BRANCH': f"'\"{branch}\"'"} if "internal" in name: d['INTERNAL'] = "1" @@ -145,7 +124,7 @@ if GetOption('extras') and arch != "Darwin": r.raise_for_status() d['SSH_KEYS'] = f'\\"{r.text.strip()}\\"' obj = senv.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = senv.Program(f"installer/installers/installer_{name}", [obj, cont[brand]], LIBS=qt_libs) + f = senv.Program(f"installer/installers/installer_{name}", [obj, cont], LIBS=qt_libs) # keep installers small assert f[0].get_size() < 350*1e3 diff --git a/selfdrive/ui/__init__.py b/selfdrive/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/selfdrive/ui/installer/continue_openpilot.sh b/selfdrive/ui/installer/continue_openpilot.sh new file mode 100644 index 0000000..3da6731 --- /dev/null +++ b/selfdrive/ui/installer/continue_openpilot.sh @@ -0,0 +1,4 @@ +#!/usr/bin/bash + +cd /data/openpilot +exec ./launch_openpilot.sh diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc old mode 100755 new mode 100644 index 179ce60..d43ed37 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -33,8 +33,8 @@ const QString CACHE_PATH = "/data/openpilot.cache"; #define INSTALL_PATH "/data/openpilot" #define TMP_INSTALL_PATH "/data/tmppilot" -extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_start"); -extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_end"); +extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start"); +extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); bool time_valid() { time_t rawtime; diff --git a/selfdrive/ui/installer/installer.h b/selfdrive/ui/installer/installer.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc new file mode 100644 index 0000000..98029ee --- /dev/null +++ b/selfdrive/ui/mui.cc @@ -0,0 +1,50 @@ +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/qt_window.h" + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QWidget w; + setMainWindow(&w); + + w.setStyleSheet("background-color: black;"); + + // our beautiful UI + QVBoxLayout *layout = new QVBoxLayout(&w); + QLabel *label = new QLabel("〇"); + layout->addWidget(label, 0, Qt::AlignCenter); + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, [=]() { + static SubMaster sm({"deviceState", "controlsState"}); + + bool onroad_prev = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + sm.update(0); + + bool onroad = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + + if (onroad) { + label->setText("〇"); + auto cs = sm["controlsState"].getControlsState(); + UIStatus status = cs.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; + label->setStyleSheet(QString("color: %1; font-size: 250px;").arg(bg_colors[status].name())); + } else { + label->setText("offroad"); + label->setStyleSheet("color: grey; font-size: 40px;"); + } + + if ((onroad != onroad_prev) || sm.frame < 2) { + Hardware::set_brightness(50); + Hardware::set_display_power(onroad); + } + }); + timer.start(50); + + return a.exec(); +} diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/body.cc b/selfdrive/ui/qt/body.cc old mode 100755 new mode 100644 index 563acc0..e01adbe --- a/selfdrive/ui/qt/body.cc +++ b/selfdrive/ui/qt/body.cc @@ -3,54 +3,159 @@ #include #include -#include +#include #include -#include -#include -#include -#include -#include - #include "common/params.h" #include "common/timing.h" -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/util.h" +RecordButton::RecordButton(QWidget *parent) : QPushButton(parent) { + setCheckable(true); + setChecked(false); + setFixedSize(148, 148); -BodyWindow::BodyWindow(QWidget *parent) : QWidget(parent) { - QGridLayout *layout = new QGridLayout(this); - layout->setSpacing(0); - layout->setMargin(200); + QObject::connect(this, &QPushButton::toggled, [=]() { + setEnabled(false); + }); +} - setAttribute(Qt::WA_OpaquePaintEvent); +void RecordButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); - setStyleSheet(R"( - BodyWindow { - background-color: blue; - } - )"); + QPoint center(width() / 2, height() / 2); + + QColor bg(isChecked() ? "#FFFFFF" : "#737373"); + QColor accent(isChecked() ? "#FF0000" : "#FFFFFF"); + if (!isEnabled()) { + bg = QColor("#404040"); + accent = QColor("#FFFFFF"); + } + + if (isDown()) { + accent.setAlphaF(0.7); + } + + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(center, 74, 74); + + p.setPen(QPen(accent, 6)); + p.setBrush(Qt::NoBrush); + p.drawEllipse(center, 42, 42); + + p.setPen(Qt::NoPen); + p.setBrush(accent); + p.drawEllipse(center, 22, 22); +} + + +BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QWidget(parent) { + QStackedLayout *layout = new QStackedLayout(this); + layout->setStackingMode(QStackedLayout::StackAll); + + QWidget *w = new QWidget; + QVBoxLayout *vlayout = new QVBoxLayout(w); + vlayout->setMargin(45); + layout->addWidget(w); + + // face + face = new QLabel(); + face->setAlignment(Qt::AlignCenter); + layout->addWidget(face); + awake = new QMovie("../assets/body/awake.gif", {}, this); + awake->setCacheMode(QMovie::CacheAll); + sleep = new QMovie("../assets/body/sleep.gif", {}, this); + sleep->setCacheMode(QMovie::CacheAll); + + // record button + btn = new RecordButton(this); + vlayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight); + QObject::connect(btn, &QPushButton::clicked, [=](bool checked) { + btn->setEnabled(false); + Params().putBool("DisableLogging", !checked); + last_button = nanos_since_boot(); + }); + w->raise(); QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState); } void BodyWindow::paintEvent(QPaintEvent *event) { - QPainter painter(this); + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); - QPixmap comma_img = loadPixmap("../assets/oscarpilot_ready.png"); + p.fillRect(rect(), QColor(0, 0, 0)); - // Calculate the top-left position to center the image in the window. - int x = (this->width() - comma_img.width()) / 2; - int y = (this->height() - comma_img.height()) / 2; + // battery outline + detail + p.translate(width() - 136, 16); + const QColor gray = QColor("#737373"); + p.setBrush(Qt::NoBrush); + p.setPen(QPen(gray, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + p.drawRoundedRect(2, 2, 78, 36, 8, 8); - // Draw the pixmap at the calculated position. - painter.drawPixmap(x, y, comma_img); -} + p.setPen(Qt::NoPen); + p.setBrush(gray); + p.drawRoundedRect(84, 12, 6, 16, 4, 4); + p.drawRect(84, 12, 3, 16); + // battery level + double fuel = std::clamp(fuel_filter.x(), 0.2f, 1.0f); + const int m = 5; // manual margin since we can't do an inner border + p.setPen(Qt::NoPen); + p.setBrush(fuel > 0.25 ? QColor("#32D74B") : QColor("#FF453A")); + p.drawRoundedRect(2 + m, 2 + m, (78 - 2*m)*fuel, 36 - 2*m, 4, 4); -void BodyWindow::updateState(const UIState &s) { + // charging status + if (charging) { + p.setPen(Qt::NoPen); + p.setBrush(Qt::white); + const QPolygonF charger({ + QPointF(12.31, 0), + QPointF(12.31, 16.92), + QPointF(18.46, 16.92), + QPointF(6.15, 40), + QPointF(6.15, 23.08), + QPointF(0, 23.08), + }); + p.drawPolygon(charger.translated(98, 0)); + } } void BodyWindow::offroadTransition(bool offroad) { + btn->setChecked(true); + btn->setEnabled(true); + fuel_filter.reset(1.0); +} + +void BodyWindow::updateState(const UIState &s) { + if (!isVisible()) { + return; + } + + const SubMaster &sm = *(s.sm); + auto cs = sm["carState"].getCarState(); + + charging = cs.getCharging(); + fuel_filter.update(cs.getFuelGauge()); + + // TODO: use carState.standstill when that's fixed + const bool standstill = std::abs(cs.getVEgo()) < 0.01; + QMovie *m = standstill ? sleep : awake; + if (m != face->movie()) { + face->setMovie(m); + face->movie()->start(); + } + + // update record button state + if (sm.updated("managerState") && (sm.rcv_time("managerState") - last_button)*1e-9 > 0.5) { + for (auto proc : sm["managerState"].getManagerState().getProcesses()) { + if (proc.getName() == "loggerd") { + btn->setEnabled(true); + btn->setChecked(proc.getRunning()); + } + } + } + + update(); } diff --git a/selfdrive/ui/qt/body.good b/selfdrive/ui/qt/body.good deleted file mode 100644 index 5ac284e..0000000 --- a/selfdrive/ui/qt/body.good +++ /dev/null @@ -1,60 +0,0 @@ -#include "selfdrive/ui/qt/body.h" - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include "common/params.h" -#include "common/timing.h" - -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/util.h" - -void LogoWidget::paintEvent(QPaintEvent *event) { - QPainter painter(this); -} - -BodyWindow::BodyWindow(QWidget *parent) : QWidget(parent) { - QGridLayout *layout = new QGridLayout(this); - layout->setSpacing(0); - layout->setMargin(200); - - setAttribute(Qt::WA_OpaquePaintEvent); - - setStyleSheet(R"( - BodyWindow { - background-color: blue; - } - )"); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState); -} - -void BodyWindow::paintEvent(QPaintEvent *event) { - QPainter painter(this); - - QPixmap comma_img = loadPixmap("../assets/oscarpilot_ready.png"); - - // Calculate the top-left position to center the image in the window. - int x = (this->width() - comma_img.width()) / 2; - int y = (this->height() - comma_img.height()) / 2; - - // Draw the pixmap at the calculated position. - painter.drawPixmap(x, y, comma_img); -} - - -void BodyWindow::updateState(const UIState &s) { -} - -void BodyWindow::offroadTransition(bool offroad) { -} diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h old mode 100755 new mode 100644 index 922a50e..567a54d --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -3,21 +3,35 @@ #include #include #include -#include -#include -#include -#include -#include - + #include "common/util.h" #include "selfdrive/ui/ui.h" -class BodyWindow : public QWidget { +class RecordButton : public QPushButton { Q_OBJECT + public: - BodyWindow(QWidget* parent = 0); + RecordButton(QWidget* parent = 0); + private: void paintEvent(QPaintEvent*) override; +}; + +class BodyWindow : public QWidget { + Q_OBJECT + +public: + BodyWindow(QWidget* parent = 0); + +private: + bool charging = false; + uint64_t last_button = 0; + FirstOrderFilter fuel_filter; + QLabel *face; + QMovie *awake, *sleep; + RecordButton *btn; + void paintEvent(QPaintEvent*) override; + private slots: void updateState(const UIState &s); void offroadTransition(bool onroad); diff --git a/selfdrive/ui/qt/body.h.org b/selfdrive/ui/qt/body.h.org deleted file mode 100644 index 20df3c1..0000000 --- a/selfdrive/ui/qt/body.h.org +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "common/util.h" -#include "selfdrive/ui/ui.h" - -class RecordButton : public QPushButton { - Q_OBJECT - -public: - RecordButton(QWidget* parent = 0); - -private: - void paintEvent(QPaintEvent*) override; -}; - -class BodyWindow : public QWidget { - Q_OBJECT - -public: - BodyWindow(QWidget* parent = 0); - -private: - bool charging = false; - uint64_t last_button = 0; - FirstOrderFilter fuel_filter; - QLabel *face; - QMovie *awake, *sleep; - RecordButton *btn; - void paintEvent(QPaintEvent*) override; - -private slots: - void updateState(const UIState &s); - void offroadTransition(bool onroad); -}; \ No newline at end of file diff --git a/selfdrive/ui/qt/body.org b/selfdrive/ui/qt/body.org deleted file mode 100644 index 304ef6e..0000000 --- a/selfdrive/ui/qt/body.org +++ /dev/null @@ -1,161 +0,0 @@ -#include "selfdrive/ui/qt/body.h" - -#include -#include - -#include -#include - -#include "common/params.h" -#include "common/timing.h" - -RecordButton::RecordButton(QWidget *parent) : QPushButton(parent) { - setCheckable(true); - setChecked(false); - setFixedSize(148, 148); - - QObject::connect(this, &QPushButton::toggled, [=]() { - setEnabled(false); - }); -} - -void RecordButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - QPoint center(width() / 2, height() / 2); - - QColor bg(isChecked() ? "#FFFFFF" : "#737373"); - QColor accent(isChecked() ? "#FF0000" : "#FFFFFF"); - if (!isEnabled()) { - bg = QColor("#404040"); - accent = QColor("#FFFFFF"); - } - - if (isDown()) { - accent.setAlphaF(0.7); - } - - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(center, 74, 74); - - p.setPen(QPen(accent, 6)); - p.setBrush(Qt::NoBrush); - p.drawEllipse(center, 42, 42); - - p.setPen(Qt::NoPen); - p.setBrush(accent); - p.drawEllipse(center, 22, 22); -} - - -BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QWidget(parent) { - QStackedLayout *layout = new QStackedLayout(this); - layout->setStackingMode(QStackedLayout::StackAll); - - QWidget *w = new QWidget; - QVBoxLayout *vlayout = new QVBoxLayout(w); - vlayout->setMargin(45); - layout->addWidget(w); - - // face - face = new QLabel(); - face->setAlignment(Qt::AlignCenter); - layout->addWidget(face); - awake = new QMovie("../assets/body/awake.gif", {}, this); - awake->setCacheMode(QMovie::CacheAll); - sleep = new QMovie("../assets/body/sleep.gif", {}, this); - sleep->setCacheMode(QMovie::CacheAll); - - // record button - btn = new RecordButton(this); - vlayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight); - QObject::connect(btn, &QPushButton::clicked, [=](bool checked) { - btn->setEnabled(false); - Params().putBool("DisableLogging", !checked); - last_button = nanos_since_boot(); - }); - w->raise(); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState); -} - -void BodyWindow::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - p.fillRect(rect(), QColor(0, 0, 0)); - - // battery outline + detail - p.translate(width() - 136, 16); - const QColor gray = QColor("#737373"); - p.setBrush(Qt::NoBrush); - p.setPen(QPen(gray, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - p.drawRoundedRect(2, 2, 78, 36, 8, 8); - - p.setPen(Qt::NoPen); - p.setBrush(gray); - p.drawRoundedRect(84, 12, 6, 16, 4, 4); - p.drawRect(84, 12, 3, 16); - - // battery level - double fuel = std::clamp(fuel_filter.x(), 0.2f, 1.0f); - const int m = 5; // manual margin since we can't do an inner border - p.setPen(Qt::NoPen); - p.setBrush(fuel > 0.25 ? QColor("#32D74B") : QColor("#FF453A")); - p.drawRoundedRect(2 + m, 2 + m, (78 - 2*m)*fuel, 36 - 2*m, 4, 4); - - // charging status - if (charging) { - p.setPen(Qt::NoPen); - p.setBrush(Qt::white); - const QPolygonF charger({ - QPointF(12.31, 0), - QPointF(12.31, 16.92), - QPointF(18.46, 16.92), - QPointF(6.15, 40), - QPointF(6.15, 23.08), - QPointF(0, 23.08), - }); - p.drawPolygon(charger.translated(98, 0)); - } -} - -void BodyWindow::offroadTransition(bool offroad) { - btn->setChecked(true); - btn->setEnabled(true); - fuel_filter.reset(1.0); -} - -void BodyWindow::updateState(const UIState &s) { - if (!isVisible()) { - return; - } - - const SubMaster &sm = *(s.sm); - auto cs = sm["carState"].getCarState(); - - charging = cs.getCharging(); - fuel_filter.update(cs.getFuelGauge()); - - // TODO: use carState.standstill when that's fixed - const bool standstill = std::abs(cs.getVEgo()) < 0.01; - QMovie *m = standstill ? sleep : awake; - if (m != face->movie()) { - face->setMovie(m); - face->movie()->start(); - } - - // update record button state - if (sm.updated("managerState") && (sm.rcv_time("managerState") - last_button)*1e-9 > 0.5) { - for (auto proc : sm["managerState"].getManagerState().getProcesses()) { - if (proc.getName() == "loggerd") { - btn->setEnabled(true); - btn->setChecked(proc.getRunning()); - } - } - } - - update(); -} diff --git a/selfdrive/ui/qt/body.webbrowser.test b/selfdrive/ui/qt/body.webbrowser.test deleted file mode 100644 index 88a8c5d..0000000 --- a/selfdrive/ui/qt/body.webbrowser.test +++ /dev/null @@ -1,52 +0,0 @@ -#include "selfdrive/ui/qt/body.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include // Include the QWebEngineView header - -#include "common/params.h" -#include "common/timing.h" -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/util.h" - -void LogoWidget::paintEvent(QPaintEvent *event) { - QPainter painter(this); -} - -BodyWindow::BodyWindow(QWidget *parent) : QWidget(parent) { - // Create a QWebEngineView - QWebEngineView *view = new QWebEngineView(this); - view->setUrl(QUrl("http://www.fark.com/")); // Set the URL to fark.com - - // Filler - - QGridLayout *layout = new QGridLayout(this); - layout->setSpacing(0); - layout->setMargin(0); // Set margin to 0 to fill the entire window - layout->addWidget(view, 0, 0); // Add the view to the layout - - setAttribute(Qt::WA_OpaquePaintEvent); - - setStyleSheet(R"( - BodyWindow { - background-color: blue; - } - )"); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState); -} - -void BodyWindow::updateState(const UIState &s) { -} - -void BodyWindow::offroadTransition(bool offroad) { -} diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc old mode 100755 new mode 100644 index 574f055..9c7c89a --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -45,7 +45,7 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { }); slayout->addWidget(driver_view); setAttribute(Qt::WA_NoSystemBackground); - // QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState); + QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState); QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition); QObject::connect(uiState(), &UIState::offroadTransition, sidebar, &Sidebar::offroadTransition); } @@ -66,37 +66,45 @@ void HomeWindow::updateState(const UIState &s) { body->setEnabled(true); slayout->setCurrentWidget(body); } + + if (s.scene.started) { + showDriverView(s.scene.driver_camera_timer >= 10, true); + } } void HomeWindow::offroadTransition(bool offroad) { body->setEnabled(false); - sidebar->setVisible(false); + sidebar->setVisible(offroad); if (offroad) { - slayout->setCurrentWidget(body); + slayout->setCurrentWidget(home); } else { slayout->setCurrentWidget(onroad); + uiState()->scene.map_open = onroad->isMapVisible(); } } -void HomeWindow::showDriverView(bool show) { +void HomeWindow::showDriverView(bool show, bool started) { if (show) { emit closeSettings(); slayout->setCurrentWidget(driver_view); + sidebar->setVisible(show == false); } else { - slayout->setCurrentWidget(body); + if (started) { + slayout->setCurrentWidget(onroad); + sidebar->setVisible(params.getBool("Sidebar")); + } else { + slayout->setCurrentWidget(home); + sidebar->setVisible(show == false); + } } - sidebar->setVisible(false); } void HomeWindow::mousePressEvent(QMouseEvent* e) { - if (body->isVisible()) { - showSidebar(true); - slayout->setCurrentWidget(home); - } else { // Handle sidebar collapsing - if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) { - sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible()); - } + if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) { + sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible()); + uiState()->scene.map_open = onroad->isMapVisible(); + params.putBool("Sidebar", sidebar->isVisible()); } } @@ -165,9 +173,9 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { left_widget->addWidget(new DriveStats); left_widget->setStyleSheet("border-radius: 10px;"); - left_widget->setCurrentIndex(params.getBool("DriveStats") ? 2 : uiState()->hasPrime() ? 0 : 1); + left_widget->setCurrentIndex(2); connect(uiState(), &UIState::primeChanged, [=](bool prime) { - left_widget->setCurrentIndex(prime ? 0 : 1); + left_widget->setCurrentIndex(2); }); home_layout->addWidget(left_widget, 1); @@ -222,17 +230,6 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { font-size: 55px; } )"); - - // Set the model name - std::map MODEL_NAME { - {0, "New Delhi"}, - {1, "Blue Diamond V1"}, - {2, "Blue Diamond V2"}, - {3, "Farmville"}, - {4, "New Lemon Pie"}, - }; - - modelName = MODEL_NAME[params.getInt("Model")]; } void OffroadHome::showEvent(QShowEvent *event) { @@ -245,8 +242,10 @@ void OffroadHome::hideEvent(QHideEvent *event) { } void OffroadHome::refresh() { + QString model = QString::fromStdString(params.get("ModelName")); + date->setText(QLocale(uiState()->language.mid(5)).toString(QDateTime::currentDateTime(), "dddd, MMMM d")); - version->setText(getBrand() + " v" + getVersion().left(14).trimmed() + " - " + modelName); + version->setText(getBrand() + " v" + getVersion().left(14).trimmed() + " - " + model); bool updateAvailable = update_widget->refresh(); int alerts = alerts_widget->refresh(); diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h old mode 100755 new mode 100644 index 780b8c2..5ea4a16 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -33,7 +33,6 @@ private: Params params; QTimer* timer; - ElidedLabel* date; ElidedLabel* version; QStackedLayout* center_layout; UpdateAlert *update_widget; @@ -42,7 +41,7 @@ private: QPushButton* update_notif; // FrogPilot variables - QString modelName; + ElidedLabel* date; }; class HomeWindow : public QWidget { @@ -57,7 +56,7 @@ signals: public slots: void offroadTransition(bool offroad); - void showDriverView(bool show); + void showDriverView(bool show, bool started=false); void showSidebar(bool show); void showMapPanel(bool show); @@ -73,6 +72,9 @@ private: DriverViewWindow *driver_view; QStackedLayout *slayout; + // FrogPilot variables + Params params; + private slots: void updateState(const UIState &s); }; diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc old mode 100755 new mode 100644 index aff2e4b..11d91b5 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -18,7 +18,7 @@ const float MAX_PITCH = 50; const float MIN_PITCH = 0; const float MAP_SCALE = 2; -MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) { +MapWindow::MapWindow(const QMapLibre::Settings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) { QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); map_overlay = new QWidget (this); @@ -57,10 +57,10 @@ void MapWindow::initLayers() { if (!m_map->layerExists("modelPathLayer")) { qDebug() << "Initializing modelPathLayer"; QVariantMap modelPath; - modelPath["id"] = "modelPathLayer"; + //modelPath["id"] = "modelPathLayer"; modelPath["type"] = "line"; modelPath["source"] = "modelPathSource"; - m_map->addLayer(modelPath); + m_map->addLayer("modelPathLayer", modelPath); m_map->setPaintProperty("modelPathLayer", "line-color", QColor("red")); m_map->setPaintProperty("modelPathLayer", "line-width", 5.0); m_map->setLayoutProperty("modelPathLayer", "line-cap", "round"); @@ -68,10 +68,9 @@ void MapWindow::initLayers() { if (!m_map->layerExists("navLayer")) { qDebug() << "Initializing navLayer"; QVariantMap nav; - nav["id"] = "navLayer"; nav["type"] = "line"; nav["source"] = "navSource"; - m_map->addLayer(nav, "road-intersection"); + m_map->addLayer("navLayer", nav, "road-intersection"); QVariantMap transition; transition["duration"] = 400; // ms @@ -84,10 +83,9 @@ void MapWindow::initLayers() { qDebug() << "Initializing pinLayer"; m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg")); QVariantMap pin; - pin["id"] = "pinLayer"; pin["type"] = "symbol"; pin["source"] = "pinSource"; - m_map->addLayer(pin); + m_map->addLayer("pinLayer", pin); m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport"); m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker"); m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true); @@ -100,10 +98,9 @@ void MapWindow::initLayers() { m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg")); QVariantMap carPos; - carPos["id"] = "carPosLayer"; carPos["type"] = "symbol"; carPos["source"] = "carPosSource"; - m_map->addLayer(carPos); + m_map->addLayer("carPosLayer", carPos); m_map->setLayoutProperty("carPosLayer", "icon-pitch-alignment", "map"); m_map->setLayoutProperty("carPosLayer", "icon-image", "label-arrow"); m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5); @@ -112,7 +109,6 @@ void MapWindow::initLayers() { // TODO: remove, symbol-sort-key does not seem to matter outside of each layer m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0); } - if (!m_map->layerExists("buildingsLayer")) { qDebug() << "Initializing buildingsLayer"; QVariantMap buildings; @@ -121,7 +117,7 @@ void MapWindow::initLayers() { buildings["source-layer"] = "building"; buildings["type"] = "fill-extrusion"; buildings["minzoom"] = 15; - m_map->addLayer(buildings); + m_map->addLayer("buildingsLayer", buildings); m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"})); QVariantList fillExtrusionHight = { @@ -141,7 +137,7 @@ void MapWindow::initLayers() { }; QVariantList fillExtrusionOpacity = { - "interpolate", + "interpolate", QVariantList{"linear"}, QVariantList{"zoom"}, 15, 0, @@ -168,8 +164,7 @@ void MapWindow::updateState(const UIState &s) { if (sm.updated("modelV2")) { // set path color on change, and show map on rising edge of navigate on openpilot bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabled() && - (sm["controlsState"].getControlsState().getEnabled() || sm["frogpilotCarControl"].getFrogpilotCarControl().getAlwaysOnLateral()) && - (!params.get("NavDestination").empty() || params.getInt("PrimeType") != 0); + (sm["controlsState"].getControlsState().getEnabled() || sm["frogpilotCarControl"].getFrogpilotCarControl().getAlwaysOnLateral()); if (nav_enabled != uiState()->scene.navigate_on_openpilot) { if (loaded_once) { m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled)); @@ -194,12 +189,24 @@ void MapWindow::updateState(const UIState &s) { locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough); if (locationd_valid) { - last_position = QMapbox::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); + last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); last_bearing = RAD2DEG(locationd_orientation.getValue()[2]); velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0])); } } + // Credit to jakethesnake420 + if (loaded_once && (sm.rcv_frame("uiPlan") != model_rcv_frame)) { + auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman(); + auto model_path = model_to_collection(locationd_location.getCalibratedOrientationECEF(), locationd_location.getPositionECEF(), sm["uiPlan"].getUiPlan().getPosition()); + QMapLibre::Feature model_path_feature(QMapLibre::Feature::LineStringType, model_path, {}, {}); + QVariantMap modelV2Path; + modelV2Path["type"] = "geojson"; + modelV2Path["data"] = QVariant::fromValue(model_path_feature); + m_map->updateSource("modelPathSource", modelV2Path); + model_rcv_frame = sm.rcv_frame("uiPlan"); + } + if (sm.updated("navRoute") && sm["navRoute"].getNavRoute().getCoordinates().size()) { auto nav_dest = coordinate_from_param("NavDestination"); bool allow_open = std::exchange(last_valid_nav_dest, nav_dest) != nav_dest && @@ -231,10 +238,10 @@ void MapWindow::updateState(const UIState &s) { if (locationd_valid) { // Update current location marker auto point = coordinate_to_collection(*last_position); - QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {}); + QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {}); QVariantMap carPosSource; carPosSource["type"] = "geojson"; - carPosSource["data"] = QVariant::fromValue(feature1); + carPosSource["data"] = QVariant::fromValue(feature1); m_map->updateSource("carPosSource", carPosSource); // Map bearing isn't updated when interacting, keep location marker up to date @@ -275,16 +282,40 @@ void MapWindow::updateState(const UIState &s) { qWarning() << "Updating navLayer with new route"; auto route = sm["navRoute"].getNavRoute(); auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates()); - QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {}); QVariantMap navSource; navSource["type"] = "geojson"; - navSource["data"] = QVariant::fromValue(feature); + navSource["data"] = QVariant::fromValue(feature); m_map->updateSource("navSource", navSource); m_map->setLayoutProperty("navLayer", "visibility", "visible"); route_rcv_frame = sm.rcv_frame("navRoute"); updateDestinationMarker(); } + + // Map Styling - Credit goes to OPKR! + int map_style = uiState()->scene.map_style; + + if (map_style != previous_map_style) { + std::unordered_map styleUrls = { + {0, "mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj"}, // Stock openpilot + {1, "mapbox://styles/mapbox/streets-v11"}, // Mapbox Streets + {2, "mapbox://styles/mapbox/outdoors-v11"}, // Mapbox Outdoors + {3, "mapbox://styles/mapbox/light-v10"}, // Mapbox Light + {4, "mapbox://styles/mapbox/dark-v10"}, // Mapbox Dark + {5, "mapbox://styles/mapbox/satellite-v9"}, // Mapbox Satellite + {6, "mapbox://styles/mapbox/satellite-streets-v11"}, // Mapbox Satellite Streets + {7, "mapbox://styles/mapbox/navigation-day-v1"}, // Mapbox Navigation Day + {8, "mapbox://styles/mapbox/navigation-night-v1"}, // Mapbox Navigation Night + {9, "mapbox://styles/mapbox/traffic-night-v2"}, // Mapbox Traffic Night + {10, "mapbox://styles/mike854/clt0hm8mw01ok01p4blkr27jp"}, // mike854's (Satellite hybrid) + }; + + std::unordered_map::iterator it = styleUrls.find(map_style); + m_map->setStyleUrl(QString::fromStdString(it->second)); + } + + previous_map_style = map_style; } void MapWindow::setError(const QString &err_str) { @@ -301,24 +332,24 @@ void MapWindow::resizeGL(int w, int h) { } void MapWindow::initializeGL() { - m_map.reset(new QMapboxGL(this, m_settings, size(), 1)); + m_map.reset(new QMapLibre::Map(this, m_settings, size(), 1)); if (last_position) { m_map->setCoordinateZoom(*last_position, MAX_ZOOM); } else { - m_map->setCoordinateZoom(QMapbox::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM); + m_map->setCoordinateZoom(QMapLibre::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM); } m_map->setMargins({0, 350, 0, 50}); m_map->setPitch(MIN_PITCH); m_map->setStyleUrl("mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj"); - QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { + QObject::connect(m_map.data(), &QMapLibre::Map::mapChanged, [=](QMapLibre::Map::MapChange change) { // set global animation duration to 0 ms so visibility changes are instant - if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingStyle) { + if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingStyle) { m_map->setTransitionOptions(0, 0); } - if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) { + if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingMap) { loaded_once = true; } }); @@ -426,10 +457,10 @@ void MapWindow::updateDestinationMarker() { auto nav_dest = coordinate_from_param("NavDestination"); if (nav_dest.has_value()) { auto point = coordinate_to_collection(*nav_dest); - QMapbox::Feature feature(QMapbox::Feature::PointType, point, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::PointType, point, {}, {}); QVariantMap pinSource; pinSource["type"] = "geojson"; - pinSource["data"] = QVariant::fromValue(feature); + pinSource["data"] = QVariant::fromValue(feature); m_map->updateSource("pinSource", pinSource); m_map->setPaintProperty("pinLayer", "visibility", "visible"); } else { diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h old mode 100755 new mode 100644 index 243ab96..fe10a20 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -6,7 +6,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -27,7 +28,7 @@ class MapWindow : public QOpenGLWidget { Q_OBJECT public: - MapWindow(const QMapboxGLSettings &); + MapWindow(const QMapLibre::Settings &); ~MapWindow(); private: @@ -35,8 +36,8 @@ private: void paintGL() final; void resizeGL(int w, int h) override; - QMapboxGLSettings m_settings; - QScopedPointer m_map; + QMapLibre::Settings m_settings; + QScopedPointer m_map; void initLayers(); @@ -56,8 +57,8 @@ private: int interaction_counter = 0; // Position - std::optional last_valid_nav_dest; - std::optional last_position; + std::optional last_valid_nav_dest; + std::optional last_position; std::optional last_bearing; FirstOrderFilter velocity_filter; bool locationd_valid = false; @@ -80,6 +81,10 @@ private: // FrogPilot variables Params params; + int previous_map_style; + + uint64_t model_rcv_frame = 0; + private slots: void updateState(const UIState &s); diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/maps/map_eta.h b/selfdrive/ui/qt/maps/map_eta.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc old mode 100755 new mode 100644 index 022355e..3907ff7 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -16,24 +16,25 @@ QString get_mapbox_token() { return MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; } -QMapboxGLSettings get_mapbox_settings() { - QMapboxGLSettings settings; +QMapLibre::Settings get_mapbox_settings() { + QMapLibre::Settings settings; + settings.setProviderTemplate(QMapLibre::Settings::ProviderTemplate::MapboxProvider); if (!Hardware::PC()) { settings.setCacheDatabasePath(MAPS_CACHE_PATH); settings.setCacheDatabaseMaximumSize(100 * 1024 * 1024); } settings.setApiBaseUrl(MAPS_HOST); - settings.setAccessToken(get_mapbox_token()); + settings.setApiKey(get_mapbox_token()); return settings; } -QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) { +QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in) { return QGeoCoordinate(in.first, in.second); } -QMapbox::CoordinatesCollections model_to_collection( +QMapLibre::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, const cereal::XYZTData::Reader &line){ @@ -42,7 +43,7 @@ QMapbox::CoordinatesCollections model_to_collection( Eigen::Vector3d orient(calibratedOrientationECEF.getValue()[0], calibratedOrientationECEF.getValue()[1], calibratedOrientationECEF.getValue()[2]); Eigen::Matrix3d ecef_from_local = euler2rot(orient); - QMapbox::Coordinates coordinates; + QMapLibre::Coordinates coordinates; auto x = line.getX(); auto y = line.getY(); auto z = line.getZ(); @@ -52,28 +53,28 @@ QMapbox::CoordinatesCollections model_to_collection( coordinates.push_back({point_geodetic.lat, point_geodetic.lon}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c) { - QMapbox::Coordinates coordinates{c}; - return {QMapbox::CoordinatesCollection{coordinates}}; +QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c) { + QMapLibre::Coordinates coordinates{c}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader& coordinate_list) { - QMapbox::Coordinates coordinates; +QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader& coordinate_list) { + QMapLibre::Coordinates coordinates; for (auto const &c : coordinate_list) { coordinates.push_back({c.getLatitude(), c.getLongitude()}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list) { - QMapbox::Coordinates coordinates; +QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list) { + QMapLibre::Coordinates coordinates; for (auto &c : coordinate_list) { coordinates.push_back({c.latitude(), c.longitude()}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } QList polyline_to_coordinate_list(const QString &polylineString) { @@ -118,7 +119,7 @@ QList polyline_to_coordinate_list(const QString &polylineString) return path; } -std::optional coordinate_from_param(const std::string ¶m) { +std::optional coordinate_from_param(const std::string ¶m) { QString json_str = QString::fromStdString(Params().get(param)); if (json_str.isEmpty()) return {}; @@ -127,7 +128,7 @@ std::optional coordinate_from_param(const std::string ¶ QJsonObject json = doc.object(); if (json["latitude"].isDouble() && json["longitude"].isDouble()) { - QMapbox::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble()); + QMapLibre::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble()); return coord; } else { return {}; diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h old mode 100755 new mode 100644 index 04e7686..d1ac768 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -3,8 +3,9 @@ #include #include #include +#include +#include #include -#include #include #include "common/params.h" @@ -18,15 +19,15 @@ const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "ht const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db"; QString get_mapbox_token(); -QMapboxGLSettings get_mapbox_settings(); -QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in); -QMapbox::CoordinatesCollections model_to_collection( +QMapLibre::Settings get_mapbox_settings(); +QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in); +QMapLibre::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, const cereal::XYZTData::Reader &line); -QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c); -QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); -QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list); +QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c); +QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); +QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list); QList polyline_to_coordinate_list(const QString &polylineString); -std::optional coordinate_from_param(const std::string ¶m); +std::optional coordinate_from_param(const std::string ¶m); std::pair map_format_distance(float d, bool is_metric); diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/maps/map_instructions.h b/selfdrive/ui/qt/maps/map_instructions.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc old mode 100755 new mode 100644 index 7913314..c4cc20e --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -8,7 +8,7 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" -MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : QFrame(parent) { +MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); content_stack->setContentsMargins(0, 0, 0, 0); @@ -41,8 +41,3 @@ void MapPanel::toggleMapSettings() { emit mapPanelRequested(); show(); } - -void MapPanel::setVisible(bool visible) { - QFrame::setVisible(visible); - uiState()->scene.map_open = visible; -} diff --git a/selfdrive/ui/qt/maps/map_panel.h b/selfdrive/ui/qt/maps/map_panel.h old mode 100755 new mode 100644 index 1b0ba42..190bb63 --- a/selfdrive/ui/qt/maps/map_panel.h +++ b/selfdrive/ui/qt/maps/map_panel.h @@ -1,15 +1,14 @@ #pragma once #include -#include +#include #include class MapPanel : public QFrame { Q_OBJECT public: - explicit MapPanel(const QMapboxGLSettings &settings, QWidget *parent = nullptr); - void setVisible(bool visible); + explicit MapPanel(const QMapLibre::Settings &settings, QWidget *parent = nullptr); signals: void mapPanelRequested(); diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc old mode 100755 new mode 100644 index 69a1406..d6fc724 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -62,13 +62,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { title->setStyleSheet("color: #FFFFFF; font-size: 54px; font-weight: 600;"); heading->addWidget(title); - // NOO without Prime IP extraction - if (notPrime) { - ipAddress = QString("%1:8082").arg(wifi->getIp4Address()); - subtitle = new QLabel(tr("Manage at %1").arg(ipAddress), this); - } else { - subtitle = new QLabel(tr("Manage at connect.comma.ai"), this); - } + subtitle = new QLabel(tr("Manage at connect.comma.ai"), this); subtitle->setStyleSheet("color: #A0A0A0; font-size: 40px; font-weight: 300;"); heading->addWidget(subtitle); } @@ -99,6 +93,8 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { setStyleSheet("MapSettings { background-color: #333333; }"); QObject::connect(NavManager::instance(), &NavManager::updated, this, &MapSettings::refresh); + + wifi = new WifiManager(this); } void MapSettings::showEvent(QShowEvent *event) { @@ -145,9 +141,9 @@ void MapSettings::refresh() { setUpdatesEnabled(true); - // NOO without Prime IP update - if (notPrime) { - ipAddress = QString("%1:8082").arg(wifi->getIp4Address()); + // Use IP for NOO without Prime + if (!uiState()->hasPrime()) { + QString ipAddress = QString("%1:8082").arg(wifi->getIp4Address()); subtitle->setText(tr("Manage at %1").arg(ipAddress)); } } diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h old mode 100755 new mode 100644 index 6a87a4d..1a96336 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -66,10 +66,9 @@ private: std::vector widgets; // FrogPilot variables - bool notPrime = Params().getInt("PrimeType") == 0; QLabel *subtitle; - QString ipAddress; - WifiManager *wifi = new WifiManager(this); + + WifiManager *wifi; signals: void closeSettings(); diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc old mode 100755 new mode 100644 index a14e74b..3f1a777 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -82,11 +82,11 @@ void Networking::connectToNetwork(const Network n) { if (wifi->isKnownConnection(n.ssid)) { wifi->activateWifiConnection(n.ssid); } else if (n.security_type == SecurityType::OPEN) { - wifi->connect(n); + wifi->connect(n, false); } else if (n.security_type == SecurityType::WPA) { QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); if (!pass.isEmpty()) { - wifi->connect(n, pass); + wifi->connect(n, false, pass); } } } @@ -96,7 +96,7 @@ void Networking::wrongPassword(const QString &ssid) { const Network &n = wifi->seenNetworks.value(ssid); QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); if (!pass.isEmpty()) { - wifi->connect(n, pass); + wifi->connect(n, false, pass); } } } @@ -131,7 +131,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(tetheringToggle); QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); if (params.getBool("TetheringEnabled")) { - tetheringToggle->setVisualOn(); + tetheringToggle->refresh(); uiState()->scene.tethering_enabled = true; } @@ -196,9 +196,9 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid hidden_network.ssid = ssid.toUtf8(); if (!pass.isEmpty()) { hidden_network.security_type = SecurityType::WPA; - wifi->connect(hidden_network, pass); + wifi->connect(hidden_network, true, pass); } else { - wifi->connect(hidden_network); + wifi->connect(hidden_network, true); } emit requestWifiScreen(); } diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/network/networkmanager.h b/selfdrive/ui/qt/network/networkmanager.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc old mode 100755 new mode 100644 index ebb5cb8..1117263 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -166,7 +166,7 @@ SecurityType WifiManager::getSecurityType(const QVariantMap &properties) { } } -void WifiManager::connect(const Network &n, const QString &password, const QString &username) { +void WifiManager::connect(const Network &n, const bool is_hidden, const QString &password, const QString &username) { setCurrentConnecting(n.ssid); forgetConnection(n.ssid); // Clear all connections that may already exist to the network we are connecting Connection connection; @@ -176,6 +176,7 @@ void WifiManager::connect(const Network &n, const QString &password, const QStri connection["connection"]["autoconnect-retries"] = 0; connection["802-11-wireless"]["ssid"] = n.ssid; + connection["802-11-wireless"]["hidden"] = is_hidden; connection["802-11-wireless"]["mode"] = "infrastructure"; if (n.security_type == SecurityType::WPA) { diff --git a/selfdrive/ui/qt/network/wifi_manager.h b/selfdrive/ui/qt/network/wifi_manager.h old mode 100755 new mode 100644 index 51d1175..39305e5 --- a/selfdrive/ui/qt/network/wifi_manager.h +++ b/selfdrive/ui/qt/network/wifi_manager.h @@ -52,7 +52,7 @@ public: std::optional activateWifiConnection(const QString &ssid); NetworkType currentNetworkType(); void updateGsmSettings(bool roaming, QString apn, bool metered); - void connect(const Network &ssid, const QString &password = {}, const QString &username = {}); + void connect(const Network &ssid, const bool is_hidden = false, const QString &password = {}, const QString &username = {}); // Tethering functions void setTetheringEnabled(bool enabled); diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/offroad/experimental_mode.h b/selfdrive/ui/qt/offroad/experimental_mode.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc old mode 100755 new mode 100644 index b52a6a3..b121905 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -183,8 +183,8 @@ void DeclinePage::showEvent(QShowEvent *event) { void OnboardingWindow::updateActiveScreen() { if (!accepted_terms) { setCurrentIndex(0); - // } else if (!training_done && !params.getBool("Passive")) { - // setCurrentIndex(1); + } else if (!training_done) { + setCurrentIndex(1); } else { emit onboardingDone(); } @@ -199,7 +199,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { TermsPage* terms = new TermsPage(this); addWidget(terms); connect(terms, &TermsPage::acceptedTerms, [=]() { - Params().put("HasAcceptedTerms", current_terms_version); + params.put("HasAcceptedTerms", current_terms_version); accepted_terms = true; updateActiveScreen(); }); @@ -209,7 +209,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { addWidget(tr); connect(tr, &TrainingGuide::completedTraining, [=]() { training_done = true; - Params().put("CompletedTrainingVersion", current_training_version); + params.put("CompletedTrainingVersion", current_training_version); updateActiveScreen(); }); @@ -230,11 +230,5 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { background-color: #4F4F4F; } )"); - - // # Oscar sez - Params().put("HasAcceptedTerms", current_terms_version); - Params().put("CompletedTrainingVersion", current_training_version); - accepted_terms = true; - emit onboardingDone(); updateActiveScreen(); } diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc old mode 100755 new mode 100644 index b5b6927..6bab1ae --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -25,9 +27,9 @@ #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/frogpilot/navigation/ui/navigation_settings.h" -#include "selfdrive/frogpilot/ui/control_settings.h" -#include "selfdrive/frogpilot/ui/vehicle_settings.h" -#include "selfdrive/frogpilot/ui/visual_settings.h" +#include "selfdrive/frogpilot/ui/qt/offroad/control_settings.h" +#include "selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h" +#include "selfdrive/frogpilot/ui/qt/offroad/visual_settings.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon @@ -97,9 +99,14 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")}; long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " - "In relaxed mode openpilot will stay further away from lead cars."), + "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + "your steering wheel distance button."), "../assets/offroad/icon_speed_limit.png", longi_button_texts); + + // set up uiState update for personality setting + QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState); + for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControl(param, title, desc, icon, this); @@ -110,7 +117,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { toggles[param.toStdString()] = toggle; // insert longitudinal personality after NDOG toggle - if (param == "DisengageOnAccelerator" && !params.getInt("AdjustablePersonalities")) { + if (param == "DisengageOnAccelerator") { addItem(long_personality_setting); } } @@ -129,6 +136,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }); } +void TogglesPanel::updateState(const UIState &s) { + const SubMaster &sm = *(s.sm); + + if (sm.updated("controlsState")) { + auto personality = sm["controlsState"].getControlsState().getPersonality(); + if (personality != s.scene.personality && s.scene.started && isVisible()) { + long_personality_setting->setCheckedButton(static_cast(personality)); + } + uiState()->scene.personality = personality; + } +} + void TogglesPanel::expandToggleDescription(const QString ¶m) { toggles[param.toStdString()]->showDescription(); } @@ -138,6 +157,13 @@ void TogglesPanel::showEvent(QShowEvent *event) { } void TogglesPanel::updateToggles() { + auto disengage_on_accelerator_toggle = toggles["DisengageOnAccelerator"]; + disengage_on_accelerator_toggle->setVisible(!params.getBool("AlwaysOnLateral")); + auto driver_camera_toggle = toggles["RecordFront"]; + driver_camera_toggle->setVisible(!(params.getBool("DeviceManagement") && params.getBool("NoLogging") && params.getBool("NoUploads"))); + auto nav_settings_left_toggle = toggles["NavSettingLeftSide"]; + nav_settings_left_toggle->setVisible(!params.getBool("FullMap")); + auto experimental_mode_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; const QString e2e_description = QString("%1
" @@ -173,7 +199,12 @@ void TogglesPanel::updateToggles() { op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); if (hasLongitudinalControl(CP)) { // normal description and toggle - experimental_mode_toggle->setEnabled(!params.getBool("ConditionalExperimental")); + bool conditional_experimental = params.getBool("ConditionalExperimental"); + if (conditional_experimental) { + params.putBool("ExperimentalMode", true); + experimental_mode_toggle->refresh(); + } + experimental_mode_toggle->setEnabled(!conditional_experimental); experimental_mode_toggle->setDescription(e2e_description); long_personality_setting->setEnabled(true); } else { @@ -225,15 +256,13 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { }); addItem(resetCalibBtn); - if (!params.getBool("Passive")) { - auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); - connect(retrainingBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { - emit reviewTrainingGuide(); - } - }); - addItem(retrainingBtn); - } + auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); + connect(retrainingBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { + emit reviewTrainingGuide(); + } + }); + addItem(retrainingBtn); if (Hardware::TICI()) { auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), ""); @@ -258,44 +287,251 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { addItem(translateBtn); // Delete driving footage button - auto deleteFootageBtn = new ButtonControl(tr("Delete Driving Data"), tr("DELETE"), tr("This button provides a swift and secure way to permanently delete all " + auto deleteDrivingDataBtn = new ButtonControl(tr("Delete Driving Data"), tr("DELETE"), tr("This button provides a swift and secure way to permanently delete all " "stored driving footage and data from your device. Ideal for maintaining privacy or freeing up space.") ); - connect(deleteFootageBtn, &ButtonControl::clicked, [this]() { + connect(deleteDrivingDataBtn, &ButtonControl::clicked, [=]() { if (!ConfirmationDialog::confirm(tr("Are you sure you want to permanently delete all of your driving footage and data?"), tr("Delete"), this)) return; std::thread([&] { + deleteDrivingDataBtn->setValue(tr("Deleting footage...")); std::system("rm -rf /data/media/0/realdata"); + deleteDrivingDataBtn->setValue(tr("Deleted!")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + deleteDrivingDataBtn->setValue(""); }).detach(); }); - addItem(deleteFootageBtn); + addItem(deleteDrivingDataBtn); // Panda flashing button - auto flashPandaBtn = new ButtonControl(tr("Flash Panda"), tr("FLASH"), "Use this button to troubleshoot and update the Panda device's firmware."); - connect(flashPandaBtn, &ButtonControl::clicked, [this]() { - if (!ConfirmationDialog::confirm(tr("Are you sure you want to flash the Panda?"), tr("Flash"), this)) return; - QProcess process; - // Get Panda type - SubMaster &sm = *(uiState()->sm); - auto pandaStates = sm["pandaStates"].getPandaStates(); - // Choose recovery script based on Panda type - if (pandaStates.size() != 0) { - auto pandaType = pandaStates[0].getPandaType(); - bool isRedPanda = (pandaType == cereal::PandaState::PandaType::RED_PANDA || - pandaType == cereal::PandaState::PandaType::RED_PANDA_V2); - QString recoveryScript = isRedPanda ? "./recover.sh" : "./recover.py"; - // Run recovery script and flash Panda - process.setWorkingDirectory("/data/openpilot/panda/board"); - process.start("/bin/sh", QStringList{"-c", recoveryScript}); - process.waitForFinished(); + auto flashPandaBtn = new ButtonControl(tr("Flash Panda"), tr("FLASH"), tr("Use this button to troubleshoot and update the Panda device's firmware.")); + connect(flashPandaBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to flash the Panda?"), tr("Flash"), this)) { + std::thread([=]() { + flashPandaBtn->setValue(tr("Flashing...")); + + QProcess process; + + process.setWorkingDirectory("/data/openpilot/panda/board"); + process.start("/bin/sh", QStringList{"-c", "./recover.py"}); + process.waitForFinished(); + process.start("/bin/sh", QStringList{"-c", "./flash.py"}); + process.waitForFinished(); + + process.setWorkingDirectory("/data/openpilot/panda/tests"); + process.start("/bin/sh", QStringList{"-c", "python reflash_internal_panda.py"}); + process.waitForFinished(); + + Hardware::soft_reboot(); + }).detach(); } - // Run the killall script as a redundancy - process.setWorkingDirectory("/data/openpilot/panda"); - process.start("/bin/sh", QStringList{"-c", "pkill -f boardd; PYTHONPATH=.. python -c \"from panda import Panda; Panda().flash()\""}); - process.waitForFinished(); - Hardware::reboot(); }); addItem(flashPandaBtn); + // Reset toggle button + auto resetTogglesBtn = new ButtonControl(tr("Reset Toggle Settings"), tr("RESET"), tr("Reset your toggle settings back to default.")); + connect(resetTogglesBtn, &ButtonControl::clicked, [=]() { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to completely reset your toggle settings? This is irreversible!"), tr("Reset"), this)) return; + std::thread([&] { + resetTogglesBtn->setValue(tr("Resetting toggles...")); + + std::system("find /data/params -type f ! -name 'FrogPilotDrives' ! -name 'FrogPilotMinutes' ! -name 'FrogPilotKilometers' -exec rm {} +"); + std::system("find /persist/params -type f ! -name 'FrogPilotDrives' ! -name 'FrogPilotMinutes' ! -name 'FrogPilotKilometers' -exec rm {} +"); + + Hardware::soft_reboot(); + }).detach(); + }); + addItem(resetTogglesBtn); + + // Backup FrogPilot + std::vector frogpilotBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")}; + FrogPilotButtonsControl *frogpilotBackup = new FrogPilotButtonsControl(tr("FrogPilot Backups"), tr("Backup, delete, or restore your FrogPilot backups."), "", frogpilotBackupOptions); + + connect(frogpilotBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir backupDir("/data/backups"); + + if (id == 0) { + QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1); + if (!nameSelection.isEmpty()) { + std::thread([=]() { + frogpilotBackup->setValue(tr("Backing up...")); + + std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString(); + + std::ostringstream commandStream; + commandStream << "mkdir -p " << std::quoted(fullBackupPath) + << " && rsync -av /data/openpilot/ " << std::quoted(fullBackupPath + "/"); + std::string command = commandStream.str(); + + int result = std::system(command.c_str()); + if (result == 0) { + std::cout << "Backup successful to " << fullBackupPath << std::endl; + frogpilotBackup->setValue(tr("Success!")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + } else { + std::cerr << "Backup failed with error code: " << result << std::endl; + frogpilotBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + } + }).detach(); + } + } else if (id == 1) { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return; + std::thread([=]() { + frogpilotBackup->setValue(tr("Deleting...")); + QDir dirToDelete(backupDir.absoluteFilePath(selection)); + if (dirToDelete.removeRecursively()) { + frogpilotBackup->setValue(tr("Deleted!")); + } else { + frogpilotBackup->setValue(tr("Failed...")); + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + }).detach(); + } + } else { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this version of FrogPilot?"), tr("Restore"), this)) return; + std::thread([=]() { + frogpilotBackup->setValue(tr("Restoring...")); + + std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString(); + std::string targetPath = "/data/safe_staging/finalized"; + std::string consistentFilePath = targetPath + "/.overlay_consistent"; + + std::ostringstream commandStream; + commandStream << "rsync -av --delete --exclude='.overlay_consistent' " << std::quoted(sourcePath + "/") << " " << std::quoted(targetPath + "/"); + std::string command = commandStream.str(); + int result = std::system(command.c_str()); + + if (result == 0) { + std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl; + std::ofstream consistentFile(consistentFilePath); + if (consistentFile) { + consistentFile.close(); + std::cout << ".overlay_consistent file created successfully." << std::endl; + } else { + std::cerr << "Failed to create .overlay_consistent file." << std::endl; + frogpilotBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + } + Hardware::soft_reboot(); + } else { + std::cerr << "Restore failed with error code: " << result << std::endl; + } + }).detach(); + } + } + }); + addItem(frogpilotBackup); + + // Backup toggles + std::vector toggleBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")}; + FrogPilotButtonsControl *toggleBackup = new FrogPilotButtonsControl(tr("Toggle Backups"), tr("Backup, delete, or restore your toggle backups."), "", toggleBackupOptions); + + connect(toggleBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir backupDir("/data/toggle_backups"); + + if (id == 0) { + QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1); + if (!nameSelection.isEmpty()) { + std::thread([=]() { + toggleBackup->setValue(tr("Backing up...")); + + std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString() + "/"; + + std::ostringstream commandStream; + commandStream << "mkdir -p " << std::quoted(fullBackupPath) + << " && rsync -av /data/params/d/ " << std::quoted(fullBackupPath); + std::string command = commandStream.str(); + + int result = std::system(command.c_str()); + if (result == 0) { + std::cout << "Backup successful to " << fullBackupPath << std::endl; + toggleBackup->setValue(tr("Success!")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + } else { + std::cerr << "Backup failed with error code: " << result << std::endl; + toggleBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + } + }).detach(); + } + } else if (id == 1) { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return; + std::thread([=]() { + toggleBackup->setValue(tr("Deleting...")); + QDir dirToDelete(backupDir.absoluteFilePath(selection)); + if (dirToDelete.removeRecursively()) { + toggleBackup->setValue(tr("Deleted!")); + } else { + toggleBackup->setValue(tr("Failed...")); + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + }).detach(); + } + } else { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this toggle backup?"), tr("Restore"), this)) return; + std::thread([=]() { + toggleBackup->setValue(tr("Restoring...")); + + std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString() + "/"; + std::string targetPath = "/data/params/d/"; + + std::ostringstream commandStream; + commandStream << "rsync -av --delete " << std::quoted(sourcePath) << " " << std::quoted(targetPath); + std::string command = commandStream.str(); + + int result = std::system(command.c_str()); + + if (result == 0) { + std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl; + toggleBackup->setValue(tr("Success!")); + paramsMemory.putBool("FrogPilotTogglesUpdated", true); + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + paramsMemory.putBool("FrogPilotTogglesUpdated", false); + } else { + std::cerr << "Restore failed with error code: " << result << std::endl; + toggleBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + } + }).detach(); + } + } + }); + addItem(toggleBackup); + + auto lockDoorsButton = new ButtonControl(tr("Lock Doors"), tr("LOCK"), tr("Use this button to lock the doors on Toyota/Lexus vehicles.")); + connect(lockDoorsButton, &ButtonControl::clicked, [this]() { + QProcess *process = new QProcess(this); + QString scriptPath = "/data/openpilot/frogpilot/controls/lib/lock_doors.py"; + QStringList arguments{"--lock"}; + process->start("python3", QStringList() << scriptPath << arguments); + }); + addItem(lockDoorsButton); + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { btn->setEnabled(offroad); @@ -311,6 +547,11 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { power_layout->addWidget(reboot_btn); QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); + QPushButton *softreboot_btn = new QPushButton(tr("Soft Reboot")); + softreboot_btn->setObjectName("softreboot_btn"); + power_layout->addWidget(softreboot_btn); + QObject::connect(softreboot_btn, &QPushButton::clicked, this, &DevicePanel::softreboot); + QPushButton *poweroff_btn = new QPushButton(tr("Power Off")); poweroff_btn->setObjectName("poweroff_btn"); power_layout->addWidget(poweroff_btn); @@ -321,6 +562,8 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { } setStyleSheet(R"( + #softreboot_btn { height: 120px; border-radius: 15px; background-color: #e2e22c; } + #softreboot_btn:pressed { background-color: #ffe224; } #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } #reboot_btn:pressed { background-color: #4a4a4a; } #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } @@ -366,6 +609,18 @@ void DevicePanel::reboot() { } } +void DevicePanel::softreboot() { + if (!uiState()->engaged()) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to soft reboot?"), tr("Soft Reboot"), this)) { + if (!uiState()->engaged()) { + params.putBool("DoSoftReboot", true); + } + } + } else { + ConfirmationDialog::alert(tr("Disengage to Soft Reboot"), this); + } +} + void DevicePanel::poweroff() { if (!uiState()->engaged()) { if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) { @@ -379,6 +634,15 @@ void DevicePanel::poweroff() { } } +void SettingsWindow::hideEvent(QHideEvent *event) { + closeParentToggle(); + + parentToggleOpen = false; + subParentToggleOpen = false; + + previousScrollPosition = 0; +} + void SettingsWindow::showEvent(QShowEvent *event) { setCurrentPanel(0); } @@ -396,33 +660,34 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { // setup two main layouts sidebar_widget = new QWidget; QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); - sidebar_layout->setMargin(0); panel_widget = new QStackedWidget(); // close button QPushButton *close_btn = new QPushButton(tr("← Back")); close_btn->setStyleSheet(R"( QPushButton { - font-size: 50px; - padding-bottom: 0px; - border 1px grey solid; + color: white; border-radius: 25px; - background-color: #292929; + background: #292929; + font-size: 50px; font-weight: 500; } QPushButton:pressed { - background-color: #3B3B3B; + color: #ADADAD; } )"); close_btn->setFixedSize(300, 125); sidebar_layout->addSpacing(10); sidebar_layout->addWidget(close_btn, 0, Qt::AlignRight); QObject::connect(close_btn, &QPushButton::clicked, [this]() { - if (frogPilotTogglesOpen) { - frogPilotTogglesOpen = false; - this->closeParentToggle(); + if (subParentToggleOpen) { + closeSubParentToggle(); + subParentToggleOpen = false; + } else if (parentToggleOpen) { + closeParentToggle(); + parentToggleOpen = false; } else { - this->closeSettings(); + closeSettings(); } }); @@ -435,20 +700,19 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); QObject::connect(toggles, &TogglesPanel::updateMetric, this, &SettingsWindow::updateMetric); - // FrogPilotControlsPanel *frogpilotControls = new FrogPilotControlsPanel(this); - // QObject::connect(frogpilotControls, &FrogPilotControlsPanel::closeParentToggle, this, [this]() {frogPilotTogglesOpen = false;}); - // QObject::connect(frogpilotControls, &FrogPilotControlsPanel::openParentToggle, this, [this]() {frogPilotTogglesOpen = true;}); + FrogPilotControlsPanel *frogpilotControls = new FrogPilotControlsPanel(this); + QObject::connect(frogpilotControls, &FrogPilotControlsPanel::openSubParentToggle, this, [this]() {subParentToggleOpen = true;}); + QObject::connect(frogpilotControls, &FrogPilotControlsPanel::openParentToggle, this, [this]() {parentToggleOpen = true;}); FrogPilotVisualsPanel *frogpilotVisuals = new FrogPilotVisualsPanel(this); - QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::closeParentToggle, this, [this]() {frogPilotTogglesOpen = false;}); - QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::openParentToggle, this, [this]() {frogPilotTogglesOpen = true;}); + QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::openParentToggle, this, [this]() {parentToggleOpen = true;}); QList> panels = { {tr("Device"), device}, {tr("Network"), new Networking(this)}, {tr("Toggles"), toggles}, {tr("Software"), new SoftwarePanel(this)}, - // {tr("Controls"), frogpilotControls}, + {tr("Controls"), frogpilotControls}, {tr("Navigation"), new FrogPilotNavigationPanel(this)}, {tr("Vehicles"), new FrogPilotVehiclesPanel(this)}, {tr("Visuals"), frogpilotVisuals}, @@ -484,21 +748,24 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { ScrollView *panel_frame = new ScrollView(panel, this); panel_widget->addWidget(panel_frame); - if (name == tr("Controls") || name == tr("Visuals")) { + if (name == tr("Controls")) { QScrollBar *scrollbar = panel_frame->verticalScrollBar(); QObject::connect(scrollbar, &QScrollBar::valueChanged, this, [this](int value) { - if (!frogPilotTogglesOpen) { + if (!parentToggleOpen) { previousScrollPosition = value; } }); QObject::connect(scrollbar, &QScrollBar::rangeChanged, this, [this, panel_frame]() { - panel_frame->restorePosition(previousScrollPosition); + if (!parentToggleOpen) { + panel_frame->restorePosition(previousScrollPosition); + } }); } QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { + closeParentToggle(); previousScrollPosition = 0; btn->setChecked(true); panel_widget->setCurrentWidget(w); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h old mode 100755 new mode 100644 index 0a028a2..1b47ed5 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -11,6 +11,7 @@ #include +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" @@ -25,6 +26,9 @@ public: protected: void showEvent(QShowEvent *event) override; + // FrogPilot widgets + void hideEvent(QHideEvent *event) override; + signals: void closeSettings(); void reviewTrainingGuide(); @@ -33,7 +37,9 @@ signals: // FrogPilot signals void closeParentToggle(); + void closeSubParentToggle(); void updateMetric(); + private: QPushButton *sidebar_alert_widget; QWidget *sidebar_widget; @@ -41,7 +47,9 @@ private: QStackedWidget *panel_widget; // FrogPilot variables - bool frogPilotTogglesOpen; + bool parentToggleOpen; + bool subParentToggleOpen; + int previousScrollPosition; }; @@ -56,10 +64,14 @@ signals: private slots: void poweroff(); void reboot(); + void softreboot(); void updateCalibDescription(); private: Params params; + + // FrogPilot variables + Params paramsMemory{"/dev/shm/params"}; }; class TogglesPanel : public ListWidget { @@ -75,6 +87,9 @@ signals: public slots: void expandToggleDescription(const QString ¶m); +private slots: + void updateState(const UIState &s); + private: Params params; std::map toggles; @@ -97,7 +112,6 @@ private: QLabel *onroadLbl; LabelControl *versionLbl; - ButtonControl *errorLogBtn; ButtonControl *installBtn; ButtonControl *downloadBtn; ButtonControl *targetBranchBtn; @@ -106,11 +120,6 @@ private: ParamWatcher *fs_watch; // FrogPilot variables - void automaticUpdate(); - - ButtonControl *updateTime; - - int deviceShutdown; - int schedule; - int time; + Params paramsMemory{"/dev/shm/params"}; + UIScene &scene; }; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc old mode 100755 new mode 100644 index bfe1803..f146491 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -2,8 +2,6 @@ #include #include -#include -#include #include #include @@ -18,13 +16,14 @@ #include "selfdrive/ui/qt/widgets/input.h" #include "system/hardware/hw.h" +#include "selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.h" void SoftwarePanel::checkForUpdates() { - std::system("pkill -SIGUSR1 -f selfdrive.updated"); + std::system("pkill -SIGUSR1 -f selfdrive.updated.updated"); } -SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { - onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off.")); +SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent), scene(uiState()->scene) { + onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off or in park.")); onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;"); addItem(onroadLbl); @@ -32,40 +31,17 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { versionLbl = new LabelControl(tr("Current Version"), ""); addItem(versionLbl); - // update scheduler - std::vector scheduleOptions{tr("Manually"), tr("Daily"), tr("Weekly")}; - ButtonParamControl *preferredSchedule = new ButtonParamControl("UpdateSchedule", tr("Update Scheduler"), - tr("Choose the update frequency for FrogPilot's automatic updates.\n\n" - "This feature will handle the download, installation, and device reboot for a seamless 'Set and Forget' experience.\n\n" - "Weekly updates start at midnight every Sunday."), - "", - scheduleOptions); - schedule = params.getInt("UpdateSchedule"); - addItem(preferredSchedule); - - updateTime = new ButtonControl(tr("Update Time"), tr("SELECT")); - QStringList hours; - for (int h = 0; h < 24; h++) { - int displayHour = (h % 12 == 0) ? 12 : h % 12; - QString meridiem = (h < 12) ? "AM" : "PM"; - hours << QString("%1:00 %2").arg(displayHour).arg(meridiem) - << QString("%1:30 %2").arg(displayHour).arg(meridiem); - } - QObject::connect(updateTime, &ButtonControl::clicked, [=]() { - int currentHourIndex = params.getInt("UpdateTime"); - QString currentHourLabel = hours[currentHourIndex]; - - QString selection = MultiOptionDialog::getSelection(tr("Select a time to automatically update"), hours, currentHourLabel, this); - if (!selection.isEmpty()) { - int selectedHourIndex = hours.indexOf(selection); - params.putInt("UpdateTime", selectedHourIndex); - updateTime->setValue(selection); - } + // automatic updates toggle + ParamControl *automaticUpdatesToggle = new ParamControl("AutomaticUpdates", tr("Automatically Update FrogPilot"), + tr("FrogPilot will automatically update itself and it's assets when you're offroad and connected to Wi-Fi."), ""); + connect(automaticUpdatesToggle, &ToggleControl::toggleFlipped, [this]() { + std::thread([this]() { + paramsMemory.putBool("FrogPilotTogglesUpdated", true); + std::this_thread::sleep_for(std::chrono::seconds(1)); + paramsMemory.putBool("FrogPilotTogglesUpdated", false); + }).detach(); }); - time = params.getInt("UpdateTime"); - deviceShutdown = params.getInt("DeviceShutdown") * 3600; - updateTime->setValue(hours[time]); - addItem(updateTime); + addItem(automaticUpdatesToggle); // download update btn downloadBtn = new ButtonControl(tr("Download"), tr("CHECK")); @@ -74,8 +50,9 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { if (downloadBtn->text() == tr("CHECK")) { checkForUpdates(); } else { - std::system("pkill -SIGHUP -f selfdrive.updated"); + std::system("pkill -SIGHUP -f selfdrive.updated.updated"); } + paramsMemory.putBool("ManualUpdateInitiated", true); }); addItem(downloadBtn); @@ -92,6 +69,11 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { connect(targetBranchBtn, &ButtonControl::clicked, [=]() { auto current = params.get("GitBranch"); QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(","); + if (!Params("/persist/params").getBool("FrogsGoMoo")) { + branches.removeAll("FrogPilot-Development"); + branches.removeAll("FrogPilot-New"); + branches.removeAll("MAKE-PRS-HERE"); + } for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) { auto i = branches.indexOf(b); if (i >= 0) { @@ -122,7 +104,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { addItem(uninstallBtn); // error log button - errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), "View the error log for debugging purposes when openpilot crashes."); + auto errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), tr("View the error log for openpilot crashes.")); connect(errorLogBtn, &ButtonControl::clicked, [=]() { std::string txt = util::read_file("/data/community/crashes/error.txt"); ConfirmationDialog::rich(QString::fromStdString(txt), this); @@ -131,8 +113,6 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { fs_watch = new ParamWatcher(this); QObject::connect(fs_watch, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { - schedule = params.getInt("UpdateSchedule"); - time = params.getInt("UpdateTime"); updateLabels(); }); @@ -141,8 +121,6 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { updateLabels(); }); - QObject::connect(uiState(), &UIState::uiUpdate, this, &SoftwarePanel::automaticUpdate); - updateLabels(); } @@ -160,16 +138,15 @@ void SoftwarePanel::updateLabels() { fs_watch->addParam("UpdaterState"); fs_watch->addParam("UpdateAvailable"); - fs_watch->addParam("UpdateSchedule"); - fs_watch->addParam("UpdateTime"); - if (!isVisible()) { return; } - // updater only runs offroad - onroadLbl->setVisible(is_onroad); - downloadBtn->setVisible(!is_onroad); + // updater only runs offroad or when parked + bool parked = scene.parked; + + onroadLbl->setVisible(is_onroad && !parked); + downloadBtn->setVisible(!is_onroad || parked); // download update QString updater_state = QString::fromStdString(params.get("UpdaterState")); @@ -201,82 +178,9 @@ void SoftwarePanel::updateLabels() { versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription"))); versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes"))); - installBtn->setVisible(!is_onroad && params.getBool("UpdateAvailable")); + installBtn->setVisible((!is_onroad || parked) && params.getBool("UpdateAvailable")); installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription"))); installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes"))); - updateTime->setVisible(params.getInt("UpdateSchedule")); - update(); } - -void SoftwarePanel::automaticUpdate() { - static int timer = 0; - static std::chrono::system_clock::time_point lastOffroadTime; - - if (!is_onroad) { - timer = (timer == 0) ? 0 : std::chrono::duration_cast(std::chrono::system_clock::now() - lastOffroadTime).count(); - lastOffroadTime = std::chrono::system_clock::now(); - } else { - timer = 0; - } - - bool isWifiConnected = (*uiState()->sm)["deviceState"].getDeviceState().getNetworkType() == cereal::DeviceState::NetworkType::WIFI; - if (schedule == 0 || is_onroad || !isWifiConnected || isVisible()) return; - - static bool isDownloadCompleted = false; - if (isDownloadCompleted && params.getBool("UpdateAvailable")) { - params.putBool(timer > deviceShutdown ? "DoShutdown" : "DoReboot", true); - return; - } - - int updateHour = time / 2; - int updateMinute = (time % 2) * 30; - - if (updateHour >= 1 && updateHour <= 11 && time >= 24) { - updateHour += 12; - } else if (updateHour == 12 && time < 24) { - updateHour = 0; - } - - std::time_t currentTimeT = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - std::tm now = *std::localtime(¤tTimeT); - - static bool updateCheckedToday = false; - static int lastCheckedDay = now.tm_yday; - if (lastCheckedDay != now.tm_yday) { - updateCheckedToday = false; - lastCheckedDay = now.tm_yday; - } - - if (now.tm_hour != updateHour || now.tm_min < updateMinute) return; - - std::string lastUpdateStr = params.get("Updated"); - std::tm lastUpdate = {}; - std::istringstream iss(lastUpdateStr); - - if (iss >> std::get_time(&lastUpdate, "%Y-%m-%d %H:%M:%S")) { - lastUpdate.tm_year -= 1900; - lastUpdate.tm_mon -= 1; - } - std::time_t lastUpdateTimeT = std::mktime(&lastUpdate); - - if (lastUpdate.tm_yday == now.tm_yday) { - return; - } - - if (!isDownloadCompleted) { - std::chrono::hours durationSinceLastUpdate = std::chrono::duration_cast(std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(lastUpdateTimeT)); - int daysSinceLastUpdate = durationSinceLastUpdate.count() / 24; - - if ((schedule == 1 && daysSinceLastUpdate >= 1) || (schedule == 2 && (now.tm_yday / 7) != (std::localtime(&lastUpdateTimeT)->tm_yday / 7))) { - if (downloadBtn->text() == tr("CHECK") && !updateCheckedToday) { - checkForUpdates(); - updateCheckedToday = true; - } else { - std::system("pkill -SIGHUP -f selfdrive.updated"); - isDownloadCompleted = true; - } - } - } -} diff --git a/selfdrive/ui/qt/offroad/text_view.qml b/selfdrive/ui/qt/offroad/text_view.qml old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc old mode 100755 new mode 100644 index d53218d..626b624 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -18,31 +18,32 @@ #include "selfdrive/ui/qt/maps/map_panel.h" #endif -static void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity) { +static void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity, const int angle = 0) { p.setRenderHint(QPainter::Antialiasing); p.setOpacity(1.0); // bg dictates opacity of ellipse p.setPen(Qt::NoPen); p.setBrush(bg); p.drawEllipse(center, btn_size / 2, btn_size / 2); + p.save(); + p.translate(center); + p.rotate(-angle); p.setOpacity(opacity); - p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img); + p.drawPixmap(-QPoint(img.width() / 2, img.height() / 2), img); p.setOpacity(1.0); + p.restore(); } -// static void drawIconRotate(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity, const int angle) { -// p.setRenderHint(QPainter::Antialiasing); -// p.setOpacity(1.0); // bg dictates opacity of ellipse -// p.setPen(Qt::NoPen); -// p.setBrush(bg); -// p.drawEllipse(center, btn_size / 2, btn_size / 2); -// p.save(); -// p.translate(center); -// p.rotate(-angle); -// p.setOpacity(opacity); -// p.drawPixmap(-QPoint(img.width() / 2, img.height() / 2), img); -// p.setOpacity(1.0); -// p.restore(); -// } +static void drawIconGif(QPainter &p, const QPoint ¢er, const QMovie &img, const QBrush &bg, float opacity) { + p.setRenderHint(QPainter::Antialiasing); + p.setOpacity(1.0); // bg dictates opacity of ellipse + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(center.x() - btn_size / 2, center.y() - btn_size / 2, btn_size, btn_size); + p.setOpacity(opacity); + QPixmap currentFrame = img.currentPixmap(); + p.drawPixmap(center - QPoint(currentFrame.width() / 2, currentFrame.height() / 2), currentFrame); + p.setOpacity(1.0); +} OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent), scene(uiState()->scene) { QVBoxLayout *main_layout = new QVBoxLayout(this); @@ -99,7 +100,7 @@ void OnroadWindow::updateState(const UIState &s) { Alert alert = Alert::get(*(s.sm), s.scene.started_frame); alerts->updateAlert(alert); - if (s.scene.map_on_left) { + if (s.scene.map_on_left || scene.full_map) { split->setDirection(QBoxLayout::LeftToRight); } else { split->setDirection(QBoxLayout::RightToLeft); @@ -115,59 +116,74 @@ void OnroadWindow::updateState(const UIState &s) { } void OnroadWindow::mousePressEvent(QMouseEvent* e) { - Params params = Params(); - Params paramsMemory = Params("/dev/shm/params"); - // FrogPilot clickable widgets bool widgetClicked = false; // Change cruise control increments button QRect maxSpeedRect(7, 25, 225, 225); - bool isMaxSpeedClicked = maxSpeedRect.contains(e->pos()); + bool isMaxSpeedClicked = maxSpeedRect.contains(e->pos()) && scene.reverse_cruise_ui; // Hide speed button - QRect speedRect(rect().center().x() - 175, 50, 350, 350); - bool isSpeedClicked = speedRect.contains(e->pos()); + QRect hideSpeedRect(rect().center().x() - 175, 50, 350, 350); + bool isSpeedClicked = hideSpeedRect.contains(e->pos()) && scene.hide_speed_ui; + + // Speed limit confirmation buttons + QSize size = this->size(); + QRect leftRect(0, 0, size.width() / 2, size.height()); + QRect rightRect = leftRect.translated(size.width() / 2, 0); + + bool isLeftSideClicked = leftRect.contains(e->pos()) && scene.speed_limit_changed; + bool isRightSideClicked = rightRect.contains(e->pos()) && scene.speed_limit_changed; // Speed limit offset button - const QRect speedLimitRect(7, 250, 225, 225); - const bool isSpeedLimitClicked = speedLimitRect.contains(e->pos()); + QRect speedLimitRect(7, 250, 225, 225); + bool isSpeedLimitClicked = speedLimitRect.contains(e->pos()) && scene.show_slc_offset_ui; if (isMaxSpeedClicked || isSpeedClicked || isSpeedLimitClicked) { - // Check if the click was within the max speed area if (isMaxSpeedClicked) { - bool currentReverseCruise = !params.getBool("ReverseCruise"); - params.putBoolNonBlocking("ReverseCruise", currentReverseCruise); - if (!params.getBool("QOLControls")) { - params.putBoolNonBlocking("QOLControls", true); - } - // Check if the click was within the speed text area + std::thread([this]() { + bool currentReverseCruise = scene.reverse_cruise; + + uiState()->scene.reverse_cruise = !currentReverseCruise; + params.putBoolNonBlocking("ReverseCruise", !currentReverseCruise); + + paramsMemory.putBool("FrogPilotTogglesUpdated", true); + std::this_thread::sleep_for(std::chrono::seconds(1)); + paramsMemory.putBool("FrogPilotTogglesUpdated", false); + }).detach(); + } else if (isSpeedClicked) { - bool currentHideSpeed = !params.getBool("HideSpeed"); - params.putBoolNonBlocking("HideSpeed", currentHideSpeed); - if (!params.getBool("QOLVisuals")) { - params.putBoolNonBlocking("QOLVisuals", true); - } - } else { - bool currentShowSLCOffset = !params.getBool("ShowSLCOffset"); - params.putBoolNonBlocking("ShowSLCOffset", currentShowSLCOffset); - if (!params.getBool("QOLVisuals")) { - params.putBoolNonBlocking("QOLVisuals", true); - } + bool currentHideSpeed = scene.hide_speed; + + uiState()->scene.hide_speed = !currentHideSpeed; + params.putBoolNonBlocking("HideSpeed", !currentHideSpeed); + } else if (isSpeedLimitClicked) { + bool currentShowSLCOffset = scene.show_slc_offset; + + scene.show_slc_offset = !currentShowSLCOffset; + params.putBoolNonBlocking("ShowSLCOffset", !currentShowSLCOffset); } + + widgetClicked = true; + } else if (isLeftSideClicked || isRightSideClicked) { + bool slcConfirmed = isLeftSideClicked && !scene.right_hand_drive || isRightSideClicked && scene.right_hand_drive; + paramsMemory.putBoolNonBlocking("SLCConfirmed", slcConfirmed); + paramsMemory.putBoolNonBlocking("SLCConfirmedPressed", true); + widgetClicked = true; - paramsMemory.putBoolNonBlocking("FrogPilotTogglesUpdated", true); // If the click wasn't for anything specific, change the value of "ExperimentalMode" } else if (scene.experimental_mode_via_screen && e->pos() != timeoutPoint) { if (clickTimer.isActive()) { clickTimer.stop(); + if (scene.conditional_experimental) { - int override_value = (scene.conditional_status >= 1 && scene.conditional_status <= 4) ? 0 : scene.conditional_status >= 5 ? 3 : 4; + int override_value = (scene.conditional_status >= 1 && scene.conditional_status <= 6) ? 0 : scene.conditional_status >= 7 ? 5 : 6; paramsMemory.putIntNonBlocking("CEStatus", override_value); } else { bool experimentalMode = params.getBool("ExperimentalMode"); params.putBoolNonBlocking("ExperimentalMode", !experimentalMode); } + } else { clickTimer.start(500); } @@ -179,8 +195,11 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { // Switch between map and sidebar when using navigate on openpilot bool sidebarVisible = geometry().x() > 0; bool show_map = uiState()->scene.navigate_on_openpilot ? sidebarVisible : !sidebarVisible; - if (!scene.experimental_mode_via_screen || map->isVisible()) { - map->setVisible(show_map && !map->isVisible()); + map->setVisible(show_map && !map->isVisible()); + if (scene.big_map) { + map->setFixedWidth(width()); + } else { + map->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); } } #endif @@ -202,11 +221,7 @@ void OnroadWindow::offroadTransition(bool offroad) { QObject::connect(nvg->map_settings_btn_bottom, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); nvg->map_settings_btn->setEnabled(true); - if (scene.full_map) { - m->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - } else { - m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); - } + m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); split->insertWidget(0, m); // hidden by default, made visible when navRoute is published @@ -230,64 +245,90 @@ void OnroadWindow::primeChanged(bool prime) { } void OnroadWindow::paintEvent(QPaintEvent *event) { - Params paramsMemory = Params("/dev/shm/params"); - QPainter p(this); p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); - // Draw FPS on screen - if (scene.show_fps) { - constexpr double minAllowedFPS = 0.1; - constexpr double maxAllowedFPS = 99.9; - constexpr qint64 oneMinuteInMilliseconds = 60000; - static double avgFPS; - static double maxFPS; - static double minFPS; - static double totalFPS; - static qint64 frameCount; + if (scene.fps_counter) { + qint64 currentMillis = QDateTime::currentMSecsSinceEpoch(); + auto fpsQueue = std::queue>(); - // Store the last reset time - static qint64 lastResetTime = QDateTime::currentMSecsSinceEpoch(); - const qint64 currentMillis = QDateTime::currentMSecsSinceEpoch(); + static double avgFPS = 0.0; + static double maxFPS = 0.0; + static double minFPS = 99.9; - // Reset the counter if it's been 60 seconds - if (currentMillis - lastResetTime >= oneMinuteInMilliseconds) { - avgFPS = 0; - frameCount = 0; - minFPS = maxAllowedFPS; - maxFPS = minAllowedFPS; - totalFPS = 0; - lastResetTime = currentMillis; - } - - // Update the FPS variables - fps = qBound(minAllowedFPS, fps, maxAllowedFPS); minFPS = qMin(minFPS, fps); maxFPS = qMax(maxFPS, fps); - frameCount++; - totalFPS += fps; - avgFPS = totalFPS / frameCount; - update(); - // Text declarations - p.setFont(InterFont(30, QFont::DemiBold)); + fpsQueue.push({currentMillis, fps}); + + while (!fpsQueue.empty() && currentMillis - fpsQueue.front().first > 60000) { + fpsQueue.pop(); + } + + if (!fpsQueue.empty()) { + double totalFPS = 0; + for (auto tempQueue = fpsQueue; !tempQueue.empty(); tempQueue.pop()) { + totalFPS += tempQueue.front().second; + } + avgFPS = totalFPS / fpsQueue.size(); + } + + QString fpsDisplayString = QString("FPS: %1 (%2) | Min: %3 | Max: %4 | Avg: %5") + .arg(qRound(fps)) + .arg(paramsMemory.getInt("CameraFPS")) + .arg(qRound(minFPS)) + .arg(qRound(maxFPS)) + .arg(qRound(avgFPS)); + + p.setFont(InterFont(28, QFont::DemiBold)); p.setRenderHint(QPainter::TextAntialiasing); p.setPen(Qt::white); - // Construct the FPS display string - QString fpsDisplayString = QString("FPS: %1 (%2) | Min: %3 | Max: %4 | Avg: %5") - .arg(fps, 0, 'f', 2) - .arg(paramsMemory.getInt("CameraFPS")) - .arg(minFPS, 0, 'f', 2) - .arg(maxFPS, 0, 'f', 2) - .arg(avgFPS, 0, 'f', 2); + QRect currentRect = rect(); + int textWidth = p.fontMetrics().horizontalAdvance(fpsDisplayString); + int xPos = (currentRect.width() - textWidth) / 2; + int yPos = currentRect.bottom() - 5; - // Calculate text positioning - const QRect currentRect = rect(); - const int textWidth = p.fontMetrics().horizontalAdvance(fpsDisplayString); - const int xPos = (currentRect.width() - textWidth) / 2; - const int yPos = currentRect.bottom() - 5; p.drawText(xPos, yPos, fpsDisplayString); + + update(); + } + + QString logicsDisplayString = QString(); + if (scene.show_jerk) { + logicsDisplayString += QString("Acceleration Jerk: %1 (%2%3) | Speed Jerk: %4 (%5%6) | ") + .arg(scene.acceleration_jerk, 0, 'f', 3) + .arg(scene.acceleration_jerk_difference > 0 ? "-" : "", 0) + .arg(abs(scene.acceleration_jerk_difference), 0, 'f', 3) + .arg(scene.ego_jerk, 0, 'f', 3) + .arg(scene.ego_jerk_difference > 0 ? "-" : "", 0) + .arg(abs(scene.ego_jerk_difference), 0, 'f', 3); + } + if (scene.show_tuning) { + if (!scene.live_valid) { + logicsDisplayString += "Friction: Calculating... | Lateral Acceleration: Calculating..."; + } else { + logicsDisplayString += QString("Friction: %1 | Lateral Acceleration: %2") + .arg(scene.friction, 0, 'f', 3) + .arg(scene.lat_accel, 0, 'f', 3); + } + } + if (logicsDisplayString.endsWith(" | ")) { + logicsDisplayString.chop(3); + } + + if (!logicsDisplayString.isEmpty()) { + p.setFont(InterFont(28, QFont::DemiBold)); + p.setRenderHint(QPainter::TextAntialiasing); + p.setPen(Qt::white); + + QRect currentRect = rect(); + int logicsWidth = p.fontMetrics().horizontalAdvance(logicsDisplayString); + int logicsX = (currentRect.width() - logicsWidth) / 2; + int logicsY = currentRect.top() + 27; + + p.drawText(logicsX, logicsY, logicsDisplayString); + update(); } } @@ -302,9 +343,14 @@ void OnroadAlerts::updateAlert(const Alert &a) { } void OnroadAlerts::paintEvent(QPaintEvent *event) { - if (alert.size == cereal::ControlsState::AlertSize::NONE || scene.show_driver_camera) { + if (alert.size == cereal::ControlsState::AlertSize::NONE) { return; } + + if (scene.hide_alerts && alert.status == cereal::ControlsState::AlertStatus::NORMAL) { + return; + } + static std::map alert_heights = { {cereal::ControlsState::AlertSize::SMALL, 271}, {cereal::ControlsState::AlertSize::MID, 420}, @@ -314,7 +360,7 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { int margin = 40; int radius = 30; - int offset = scene.always_on_lateral || scene.conditional_experimental || scene.road_name_ui ? 25 : 0; + int offset = scene.show_aol_status_bar || scene.show_cem_status_bar || scene.road_name_ui ? 25 : 0; if (alert.size == cereal::ControlsState::AlertSize::FULL) { margin = 0; radius = 0; @@ -368,7 +414,6 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode); - // Custom steering wheel images wheelImages = { {0, loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size})}, {1, loadPixmap("../frogpilot/assets/wheel_images/lexus.png", {img_size, img_size})}, @@ -377,28 +422,30 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals {4, loadPixmap("../frogpilot/assets/wheel_images/rocket.png", {img_size, img_size})}, {5, loadPixmap("../frogpilot/assets/wheel_images/hyundai.png", {img_size, img_size})}, {6, loadPixmap("../frogpilot/assets/wheel_images/stalin.png", {img_size, img_size})}, - {7, loadPixmap("../frogpilot/assets/wheel_images/firefox.png", {img_size, img_size})} + {7, loadPixmap("../frogpilot/assets/random_events/images/firefox.png", {img_size, img_size})}, }; + + wheelImagesGif[1] = new QMovie("../frogpilot/assets/random_events/images/weeb_wheel.gif", QByteArray(), this); + wheelImagesGif[2] = new QMovie("../frogpilot/assets/random_events/images/tree_fiddy.gif", QByteArray(), this); + wheelImagesGif[3] = new QMovie("../frogpilot/assets/random_events/images/great_scott.gif", QByteArray(), this); } void ExperimentalButton::changeMode() { - Params paramsMemory = Params("/dev/shm/params"); - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = hasLongitudinalControl(cp) && (params.getBool("ExperimentalModeConfirmed") || scene.experimental_mode_via_screen); + bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); if (can_change) { if (scene.conditional_experimental) { - int override_value = (scene.conditional_status >= 1 && scene.conditional_status <= 4) ? 0 : scene.conditional_status >= 5 ? 3 : 4; + int override_value = (scene.conditional_status >= 1 && scene.conditional_status <= 4) ? 0 : scene.conditional_status >= 5 ? 5 : 6; paramsMemory.putIntNonBlocking("ConditionalStatus", override_value); } else { - params.putBoolNonBlocking("ExperimentalMode", !experimental_mode); + params.putBool("ExperimentalMode", !experimental_mode); } } } void ExperimentalButton::updateState(const UIState &s, bool leadInfo) { const auto cs = (*s.sm)["controlsState"].getControlsState(); - bool eng = cs.getEngageable() || cs.getEnabled(); + bool eng = cs.getEngageable() || cs.getEnabled() || scene.always_on_lateral_active; if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { engageable = eng; experimental_mode = cs.getExperimentalMode(); @@ -406,47 +453,73 @@ void ExperimentalButton::updateState(const UIState &s, bool leadInfo) { } // FrogPilot variables - firefoxRandomEventTriggered = scene.current_random_event == 1; + int randomEvent = scene.current_random_event; + rotatingWheel = scene.rotating_wheel; wheelIcon = scene.wheel_icon; + wheelIconGif = 0; y_offset = leadInfo ? 10 : 0; - if (firefoxRandomEventTriggered) { + if (randomEvent == 0 && gifLabel) { + delete gifLabel; + gifLabel = nullptr; + } else if (randomEvent == 1) { static int rotationDegree = 0; rotationDegree = (rotationDegree + 36) % 360; steeringAngleDeg = rotationDegree; wheelIcon = 7; update(); - // Update the icon so the steering wheel rotates in real time + + } else if (randomEvent == 2 || randomEvent == 3 || randomEvent == 4) { + if (!gifLabel) { + gifLabel = new QLabel(this); + QMovie *movie = wheelImagesGif[randomEvent - 1]; + if (movie) { + gifLabel->setMovie(movie); + gifLabel->setFixedSize(img_size, img_size); + gifLabel->move((width() - gifLabel->width()) / 2, (height() - gifLabel->height()) / 2 + y_offset); + gifLabel->movie()->start(); + } + } + gifLabel->show(); + wheelIconGif = randomEvent - 1; + update(); + } else if (rotatingWheel && steeringAngleDeg != scene.steering_angle_deg) { steeringAngleDeg = scene.steering_angle_deg; update(); + steeringAngleDeg = scene.steering_angle_deg; + } else if (!rotatingWheel) { + steeringAngleDeg = 0; } } void ExperimentalButton::paintEvent(QPaintEvent *event) { + if (wheelIcon < 0) { + return; + } + QPainter p(this); - // Steering wheel icon disabled + engage_img = wheelImages[wheelIcon]; + QPixmap img = wheelIcon != 0 ? engage_img : (experimental_mode ? experimental_img : engage_img); + QMovie *gif = wheelImagesGif[wheelIconGif]; - // // Custom steering wheel icon - // engage_img = wheelImages[wheelIcon]; - // QPixmap img = wheelIcon ? engage_img : (experimental_mode ? experimental_img : engage_img); + QColor background_color = wheelIcon != 0 && !isDown() && engageable ? + (scene.always_on_lateral_active ? QColor(10, 186, 181, 255) : + (scene.conditional_status == 1 || scene.conditional_status == 3 || scene.conditional_status == 5 ? QColor(255, 246, 0, 255) : + (experimental_mode ? QColor(218, 111, 37, 241) : + (scene.traffic_mode_active ? QColor(201, 34, 49, 255) : + (scene.navigate_on_openpilot ? QColor(49, 161, 238, 255) : QColor(0, 0, 0, 166)))))) : + QColor(0, 0, 0, 166); - // QColor background_color = wheelIcon && !isDown() && engageable ? - // (scene.always_on_lateral_active ? QColor(10, 186, 181, 255) : - // (scene.conditional_status == 1 ? QColor(255, 246, 0, 255) : - // (experimental_mode ? QColor(218, 111, 37, 241) : - // (scene.navigate_on_openpilot ? QColor(49, 161, 238, 255) : QColor(0, 0, 0, 166))))) : - // QColor(0, 0, 0, 166); - - // if (!scene.show_driver_camera) { - // if (rotatingWheel || firefoxRandomEventTriggered) { - // drawIconRotate(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, background_color, (isDown() || (!engageable && !scene.always_on_lateral_active)) ? 0.6 : 1.0, steeringAngleDeg); - // } else { - // drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, background_color, (isDown() || (!engageable && !scene.always_on_lateral_active)) ? 0.6 : 1.0); - // } - // } + if (!(scene.map_open && scene.big_map)) { + if (wheelIconGif != 0) { + drawIconGif(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), *gif, background_color, 1.0); + } else { + drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0, steeringAngleDeg); + } + } } @@ -474,15 +547,22 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par main_layout->setMargin(UI_BORDER_SIZE); main_layout->setSpacing(0); + QHBoxLayout *buttons_layout = new QHBoxLayout(); + buttons_layout->setSpacing(0); + // Neokii screen recorder - QHBoxLayout *top_right_layout = new QHBoxLayout(); + recorder_btn = new ScreenRecorder(this); + buttons_layout->addWidget(recorder_btn); + + experimental_btn = new ExperimentalButton(this); + buttons_layout->addWidget(experimental_btn); + + QVBoxLayout *top_right_layout = new QVBoxLayout(); top_right_layout->setSpacing(0); + top_right_layout->addLayout(buttons_layout); - // recorder_btn = new ScreenRecorder(this); - // top_right_layout->addWidget(recorder_btn); - - // experimental_btn = new ExperimentalButton(this); - // top_right_layout->addWidget(experimental_btn); + pedal_icons = new PedalIcons(this); + top_right_layout->addWidget(pedal_icons, 0, Qt::AlignRight); main_layout->addLayout(top_right_layout, 0); main_layout->setAlignment(top_right_layout, Qt::AlignTop | Qt::AlignRight); @@ -507,7 +587,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const auto nav_instruction = sm["navInstruction"].getNavInstruction(); // Handle older routes where vCruiseCluster is not set - float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); + float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); setSpeed = cs_alive ? v_cruise : SET_SPEED_NA; is_cruise_set = setSpeed > 0 && (int)setSpeed != SET_SPEED_NA; if (is_cruise_set && !s.scene.is_metric) { @@ -516,22 +596,22 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { // Handle older routes where vEgoCluster is not set v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0; - float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo(); + float v_ego = v_ego_cluster_seen && !scene.wheel_speed ? car_state.getVEgoCluster() : car_state.getVEgo(); speed = cs_alive ? std::max(0.0, v_ego) : 0.0; speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; auto speed_limit_sign = nav_instruction.getSpeedLimitSign(); - speedLimit = slcOverridden ? slcOverriddenSpeed : slcSpeedLimit ? slcSpeedLimit : nav_alive ? nav_instruction.getSpeedLimit() : 0.0; + speedLimit = slcOverridden ? scene.speed_limit_overridden_speed : speedLimitController ? scene.speed_limit : nav_alive ? nav_instruction.getSpeedLimit() : 0.0; speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - if (slcSpeedLimit && !slcOverridden) { + if (speedLimitController && !slcOverridden) { speedLimit = speedLimit - (showSLCOffset ? slcSpeedLimitOffset : 0); } - has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD) || (slcSpeedLimit && !useViennaSLCSign); - has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) || (slcSpeedLimit && useViennaSLCSign); + has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD) || (speedLimitController && !useViennaSLCSign); + has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) && !(speedLimitController && !useViennaSLCSign) || (speedLimitController && useViennaSLCSign); is_metric = s.scene.is_metric; speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); - hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals && (turnSignalLeft || turnSignalRight)) || showDriverCamera; + hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals != 0 && (turnSignalLeft || turnSignalRight) || bigMapOpen); status = s.status; // update engageability/experimental mode button @@ -546,9 +626,11 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { // hide map settings button for alerts and flip for right hand DM if (map_settings_btn->isEnabled()) { - map_settings_btn->setVisible(!hideBottomIcons && compass); - main_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | (compass ? Qt::AlignTop : Qt::AlignBottom)); + map_settings_btn->setVisible(!hideBottomIcons && compass && !scene.hide_map_icon); + main_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignTop); } + + updateFrogPilotWidgets(); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -561,114 +643,128 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { p.fillRect(0, 0, width(), UI_HEADER_HEIGHT, bg); QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–"; - QString speedLimitOffsetStr = (showSLCOffset) ? "+" + QString::number(std::nearbyint(slcSpeedLimitOffset)) : "–"; + QString speedLimitOffsetStr = slcSpeedLimitOffset == 0 ? "–" : QString::number(slcSpeedLimitOffset, 'f', 0).prepend(slcSpeedLimitOffset > 0 ? "+" : ""); QString speedStr = QString::number(std::nearbyint(speed)); QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed - cruiseAdjustment)) : "–"; - // Draw outer box + border to contain set speed and speed limit - const int sign_margin = 12; - const int us_sign_height = 186; - const int eu_sign_size = 176; + if (!scene.hide_max_speed) { + // Draw outer box + border to contain set speed and speed limit + const int sign_margin = 12; + const int us_sign_height = 186; + const int eu_sign_size = 176; - const QSize default_size = {172, 204}; - QSize set_speed_size = default_size; - if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; - if (has_us_speed_limit && speedLimitStr.size() >= 3) set_speed_size.rwidth() = 223; + const QSize default_size = {172, 204}; + QSize set_speed_size = default_size; + if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; + if (has_us_speed_limit && speedLimitStr.size() >= 3) set_speed_size.rwidth() = 223; - if (has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; - else if (has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; + if (has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; + else if (has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; - int top_radius = 32; - int bottom_radius = has_eu_speed_limit ? 100 : 32; + int top_radius = 32; + int bottom_radius = has_eu_speed_limit ? 100 : 32; - QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); - if (is_cruise_set && cruiseAdjustment) { - float transition = qBound(0.0f, 4.0f * (cruiseAdjustment / setSpeed), 1.0f); - QColor min = whiteColor(75); - QColor max = redColor(75); + QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); + if (is_cruise_set && cruiseAdjustment != 0) { + float transition = qBound(0.0f, 4.0f * (cruiseAdjustment / setSpeed), 1.0f); + QColor min = whiteColor(75); + QColor max = vtscControllingCurve ? redColor() : greenColor(); - p.setPen(QPen(QColor::fromRgbF( - min.redF() + transition * (max.redF() - min.redF()), - min.greenF() + transition * (max.greenF() - min.greenF()), - min.blueF() + transition * (max.blueF() - min.blueF()) - ), 6)); - } else if (reverseCruise) { - p.setPen(QPen(QColor(0, 150, 255), 6)); - } else { - p.setPen(QPen(whiteColor(75), 6)); - } - p.setBrush(blackColor(166)); - drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); - - // Draw MAX - QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); - QColor set_speed_color = whiteColor(); - if (is_cruise_set) { - if (status == STATUS_DISENGAGED) { - max_color = whiteColor(); - } else if (status == STATUS_OVERRIDE) { - max_color = QColor(0x91, 0x9b, 0x95, 0xff); - } else if (speedLimit > 0) { - auto interp_color = [=](QColor c1, QColor c2, QColor c3) { - return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; - }; - max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); - set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); - } - } else { - max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); - set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); - } - p.setFont(InterFont(40, QFont::DemiBold)); - p.setPen(max_color); - p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); - p.setFont(InterFont(90, QFont::Bold)); - p.setPen(set_speed_color); - p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); - - const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); - // US/Canada (MUTCD style) sign - if (has_us_speed_limit) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawRoundedRect(sign_rect, 24, 24); - p.setPen(QPen(blackColor(), 6)); - p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); - - p.save(); - p.setOpacity(slcOverridden ? 0.25 : 1.0); - if (showSLCOffset) { - p.setFont(InterFont(28, QFont::DemiBold)); - p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); - p.setFont(InterFont(70, QFont::Bold)); - p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr); - p.setFont(InterFont(50, QFont::DemiBold)); - p.drawText(sign_rect.adjusted(0, 120, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitOffsetStr); + p.setPen(QPen(QColor::fromRgbF( + min.redF() + transition * (max.redF() - min.redF()), + min.greenF() + transition * (max.greenF() - min.greenF()), + min.blueF() + transition * (max.blueF() - min.blueF()) + ), 10)); + } else if (scene.reverse_cruise) { + p.setPen(QPen(blueColor(), 6)); + } else if (trafficModeActive) { + p.setPen(QPen(redColor(), 10)); } else { - p.setFont(InterFont(28, QFont::DemiBold)); - p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); - p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); - p.setFont(InterFont(70, QFont::Bold)); - p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr); + p.setPen(QPen(whiteColor(75), 6)); } - p.restore(); - } + p.setBrush(blackColor(166)); + drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); - // EU (Vienna style) sign - if (has_eu_speed_limit) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawEllipse(sign_rect); - p.setPen(QPen(Qt::red, 20)); - p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); + // Draw MAX + QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); + QColor set_speed_color = whiteColor(); + if (is_cruise_set) { + if (status == STATUS_DISENGAGED) { + max_color = whiteColor(); + } else if (status == STATUS_OVERRIDE) { + max_color = QColor(0x91, 0x9b, 0x95, 0xff); + } else if (speedLimit > 0) { + auto interp_color = [=](QColor c1, QColor c2, QColor c3) { + return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; + }; + max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); + set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); + } + } else { + max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); + set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); + } + p.setFont(InterFont(40, QFont::DemiBold)); + p.setPen(max_color); + p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); + p.setFont(InterFont(90, QFont::Bold)); + p.setPen(set_speed_color); + p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); - p.setFont(InterFont((speedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold)); - p.setPen(blackColor()); - p.drawText(sign_rect, Qt::AlignCenter, speedLimitStr); + const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); + // US/Canada (MUTCD style) sign + if (has_us_speed_limit) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawRoundedRect(sign_rect, 24, 24); + p.setPen(QPen(blackColor(), 6)); + p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); + + p.save(); + p.setOpacity(slcOverridden ? 0.25 : 1.0); + if (speedLimitController && showSLCOffset && !slcOverridden) { + p.setFont(InterFont(28, QFont::DemiBold)); + p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); + p.setFont(InterFont(70, QFont::Bold)); + p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr); + p.setFont(InterFont(50, QFont::DemiBold)); + p.drawText(sign_rect.adjusted(0, 120, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitOffsetStr); + } else { + p.setFont(InterFont(28, QFont::DemiBold)); + p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); + p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); + p.setFont(InterFont(70, QFont::Bold)); + p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr); + } + p.restore(); + } + + // EU (Vienna style) sign + if (has_eu_speed_limit) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawEllipse(sign_rect); + p.setPen(QPen(Qt::red, 20)); + p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); + + p.save(); + p.setOpacity(slcOverridden ? 0.25 : 1.0); + p.setPen(blackColor()); + if (showSLCOffset) { + p.setFont(InterFont((speedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold)); + p.drawText(sign_rect.adjusted(0, -25, 0, 0), Qt::AlignCenter, speedLimitStr); + p.setFont(InterFont(40, QFont::DemiBold)); + p.drawText(sign_rect.adjusted(0, 100, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitOffsetStr); + } else { + p.setFont(InterFont((speedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold)); + p.drawText(sign_rect, Qt::AlignCenter, speedLimitStr); + } + p.restore(); + } } // current speed - if (!hideSpeed) { + if (!(scene.hide_speed || bigMapOpen)) { p.setFont(InterFont(176, QFont::Bold)); drawText(p, rect().center().x(), 210, speedStr); p.setFont(InterFont(66)); @@ -676,6 +772,9 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { } p.restore(); + + // Draw FrogPilot widgets + paintFrogPilotWidgets(p); } void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &text, int alpha) { @@ -722,8 +821,10 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { - if (customColors != 0) { - painter.setBrush(themeConfiguration[customColors].second.first); + if (currentHolidayTheme != 0) { + painter.setBrush(std::get<2>(holidayThemeConfiguration[currentHolidayTheme]).begin()->second); + } else if (customColors != 0) { + painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second); } else { painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); } @@ -732,8 +833,10 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // road edges for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { - if (customColors != 0) { - painter.setBrush(themeConfiguration[customColors].second.first); + if (currentHolidayTheme != 0) { + painter.setBrush(std::get<2>(holidayThemeConfiguration[currentHolidayTheme]).begin()->second); + } else if (customColors != 0) { + painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second); } else { painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); } @@ -742,7 +845,7 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint path QLinearGradient bg(0, height(), 0, 0); - if (sm["controlsState"].getControlsState().getExperimentalMode() || accelerationPath) { + if (experimentalMode || scene.acceleration_path) { // The first half of track_vertices are the points for the right side of the path // and the indices match the positions of accel from uiPlan const auto &acceleration_const = sm["uiPlan"].getUiPlan().getAccel(); @@ -761,12 +864,19 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // Flip so 0 is bottom of frame float lin_grad_point = (height() - scene.track_vertices[i].y()) / height(); - // If acceleration is between -0.2 and 0.2, resort to the theme color - if (std::abs(acceleration[i]) < 0.2 && (customColors != 0)) { - const auto &colorMap = themeConfiguration[customColors].second.second; + // If acceleration is between -0.25 and 0.25, resort to the theme color + if (std::abs(acceleration[i]) < 0.25 && (currentHolidayTheme != 0)) { + const auto &colorMap = std::get<2>(holidayThemeConfiguration[currentHolidayTheme]); for (const auto &[position, brush] : colorMap) { bg.setColorAt(position, brush.color()); } + + } else if (std::abs(acceleration[i]) < 0.25 && (customColors != 0)) { + const auto &colorMap = std::get<2>(themeConfiguration[customColors]); + for (const auto &[position, brush] : colorMap) { + bg.setColorAt(position, brush.color()); + } + } else { // speed up: 120, slow down: 0 float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); @@ -783,11 +893,18 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { } } - } else if (customColors != 0) { - const auto &colorMap = themeConfiguration[customColors].second.second; + } else if (currentHolidayTheme != 0) { + const auto &colorMap = std::get<2>(holidayThemeConfiguration[currentHolidayTheme]); for (const auto &[position, brush] : colorMap) { bg.setColorAt(position, brush.color()); } + + } else if (customColors != 0) { + const auto &colorMap = std::get<2>(themeConfiguration[customColors]); + for (const auto &[position, brush] : colorMap) { + bg.setColorAt(position, brush.color()); + } + } else { bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); @@ -797,18 +914,13 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.setBrush(bg); painter.drawPolygon(scene.track_vertices); - // create new path with track vertices and track edge vertices - QPainterPath path; - path.addPolygon(scene.track_vertices); - path.addPolygon(scene.track_edge_vertices); - - // paint path edges + // Paint path edges QLinearGradient pe(0, height(), 0, 0); - if (alwaysOnLateral) { + if (alwaysOnLateralActive) { pe.setColorAt(0.0, QColor::fromHslF(178 / 360., 0.90, 0.38, 1.0)); pe.setColorAt(0.5, QColor::fromHslF(178 / 360., 0.90, 0.38, 0.5)); pe.setColorAt(1.0, QColor::fromHslF(178 / 360., 0.90, 0.38, 0.1)); - } else if (conditionalStatus == 1 || conditionalStatus == 3) { + } else if (conditionalStatus == 1 || conditionalStatus == 3 || conditionalStatus == 5) { pe.setColorAt(0.0, QColor::fromHslF(58 / 360., 1.00, 0.50, 1.0)); pe.setColorAt(0.5, QColor::fromHslF(58 / 360., 1.00, 0.50, 0.5)); pe.setColorAt(1.0, QColor::fromHslF(58 / 360., 1.00, 0.50, 0.1)); @@ -816,12 +928,22 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { pe.setColorAt(0.0, QColor::fromHslF(25 / 360., 0.71, 0.50, 1.0)); pe.setColorAt(0.5, QColor::fromHslF(25 / 360., 0.71, 0.50, 0.5)); pe.setColorAt(1.0, QColor::fromHslF(25 / 360., 0.71, 0.50, 0.1)); + } else if (trafficModeActive) { + pe.setColorAt(0.0, QColor::fromHslF(355 / 360., 0.71, 0.46, 1.0)); + pe.setColorAt(0.5, QColor::fromHslF(355 / 360., 0.71, 0.46, 0.5)); + pe.setColorAt(1.0, QColor::fromHslF(355 / 360., 0.71, 0.46, 0.1)); } else if (scene.navigate_on_openpilot) { pe.setColorAt(0.0, QColor::fromHslF(205 / 360., 0.85, 0.56, 1.0)); pe.setColorAt(0.5, QColor::fromHslF(205 / 360., 0.85, 0.56, 0.5)); pe.setColorAt(1.0, QColor::fromHslF(205 / 360., 0.85, 0.56, 0.1)); + } else if (currentHolidayTheme != 0) { + const auto &colorMap = std::get<2>(holidayThemeConfiguration[currentHolidayTheme]); + for (const auto &[position, brush] : colorMap) { + QColor darkerColor = brush.color().darker(120); + pe.setColorAt(position, darkerColor); + } } else if (customColors != 0) { - const auto &colorMap = themeConfiguration[customColors].second.second; + const auto &colorMap = std::get<2>(themeConfiguration[customColors]); for (const auto &[position, brush] : colorMap) { QColor darkerColor = brush.color().darker(120); pe.setColorAt(position, darkerColor); @@ -832,64 +954,61 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { pe.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.00, 0.68, 0.1)); } + QPainterPath path; + path.addPolygon(scene.track_vertices); + path.addPolygon(scene.track_edge_vertices); + painter.setBrush(pe); painter.drawPath(path); - // paint blindspot path - QLinearGradient bs(0, height(), 0, 0); - if (blindSpotLeft || blindSpotRight) { + // Paint blindspot path + if (scene.blind_spot_path) { + QLinearGradient bs(0, height(), 0, 0); bs.setColorAt(0.0, QColor::fromHslF(0 / 360., 0.75, 0.50, 0.6)); bs.setColorAt(0.5, QColor::fromHslF(0 / 360., 0.75, 0.50, 0.4)); bs.setColorAt(1.0, QColor::fromHslF(0 / 360., 0.75, 0.50, 0.2)); + + painter.setBrush(bs); + if (blindSpotLeft) { + painter.drawPolygon(scene.track_adjacent_vertices[4]); + } + if (blindSpotRight) { + painter.drawPolygon(scene.track_adjacent_vertices[5]); + } } - painter.setBrush(bs); - if (blindSpotLeft) { - painter.drawPolygon(scene.track_adjacent_vertices[4]); - } - if (blindSpotRight) { - painter.drawPolygon(scene.track_adjacent_vertices[5]); - } + // Paint adjacent lane paths + if (scene.adjacent_path && (laneWidthLeft != 0 || laneWidthRight != 0)) { + QString unit_d = is_metric ? tr(" meters") : tr(" feet"); - // paint adjacent lane paths - if (adjacentPath && (laneWidthLeft != 0 || laneWidthRight != 0)) { - // Set up the units - double conversionFactor = is_metric ? 1.0 : 3.28084; - QString unit_d = is_metric ? " meters" : " feet"; + float minLaneWidth = laneDetectionWidth * 0.5; + float maxLaneWidth = laneDetectionWidth * 1.5; - // Declare the lane width thresholds - constexpr float minLaneWidth = 2.5f; - constexpr float maxLaneWidth = 3.5f; + auto paintLane = [=](QPainter &painter, const QPolygonF &lane, float laneWidth, bool blindspot) { + QLinearGradient al(0, height(), 0, 0); - // Set gradient colors based on laneWidth and blindspot - auto setGradientColors = [](QLinearGradient &gradient, float laneWidth, bool blindspot) { - // Make the path red for smaller paths or if there's a car in the blindspot and green for larger paths - double hue = (laneWidth < minLaneWidth || blindspot) ? 0.0 : - (laneWidth >= maxLaneWidth) ? 120.0 : - 120.0 * (laneWidth - minLaneWidth) / (maxLaneWidth - minLaneWidth); - auto hue_ratio = hue / 360.0; - gradient.setColorAt(0.0, QColor::fromHslF(hue_ratio, 0.75, 0.50, 0.6)); - gradient.setColorAt(0.5, QColor::fromHslF(hue_ratio, 0.75, 0.50, 0.4)); - gradient.setColorAt(1.0, QColor::fromHslF(hue_ratio, 0.75, 0.50, 0.2)); - }; + bool redPath = laneWidth < minLaneWidth || laneWidth > maxLaneWidth || blindspot; + float hue = redPath ? 0.0 : 120.0 * (laneWidth - minLaneWidth) / (maxLaneWidth - minLaneWidth); - // Paint the lanes - auto paintLane = [&](QPainter &painter, const QPolygonF &lane, const float laneWidth, const bool blindspot) { - QLinearGradient gradient(0, height(), 0, 0); - setGradientColors(gradient, laneWidth, blindspot); - painter.setFont(InterFont(30, QFont::DemiBold)); - painter.setBrush(gradient); - painter.setPen(Qt::transparent); + al.setColorAt(0.0, QColor::fromHslF(hue / 360.0, 0.75, 0.50, 0.6)); + al.setColorAt(0.5, QColor::fromHslF(hue / 360.0, 0.75, 0.50, 0.4)); + al.setColorAt(1.0, QColor::fromHslF(hue / 360.0, 0.75, 0.50, 0.2)); + + painter.setBrush(al); painter.drawPolygon(lane); + + painter.setFont(InterFont(30, QFont::DemiBold)); painter.setPen(Qt::white); QRectF boundingRect = lane.boundingRect(); - painter.drawText(boundingRect.center(), blindspot ? "Vehicle in blind spot" : - QString("%1%2").arg(laneWidth * conversionFactor, 0, 'f', 2).arg(unit_d)); + if (scene.adjacent_path_metrics) { + QString text = blindspot ? tr("Vehicle in blind spot") : + QString("%1%2").arg(laneWidth * distanceConversion, 0, 'f', 2).arg(unit_d); + painter.drawText(boundingRect, Qt::AlignCenter, text); + } painter.setPen(Qt::NoPen); }; - // Paint lanes paintLane(painter, scene.track_adjacent_vertices[4], laneWidthLeft, blindSpotLeft); paintLane(painter, scene.track_adjacent_vertices[5], laneWidthRight, blindSpotRight); } @@ -902,9 +1021,9 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) // base icon int offset = UI_BORDER_SIZE + btn_size / 2; - offset += alwaysOnLateral || conditionalExperimental || roadNameUI ? 25 : 0; int x = rightHandDM ? width() - offset : offset; - x += onroadAdjustableProfiles ? 250 : 0; + x += onroadDistanceButton ? 250 : 0; + offset += showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI ? 25 : 0; int y = height() - offset; float opacity = dmActive ? 0.65 : 0.2; drawIcon(painter, QPoint(x, y), dm_img, blackColor(70), opacity); @@ -938,13 +1057,13 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) painter.restore(); } -void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) { +void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd, const float v_ego) { painter.save(); - const float speedBuff = customColors ? 25. : 10.; // Make the center of the chevron appear sooner if a custom theme is active - const float leadBuff = customColors ? 100. : 40.; // Make the center of the chevron appear sooner if a custom theme is active - const float d_rel = lead_data.getDRel(); - const float v_rel = lead_data.getVRel(); + const float speedBuff = currentHolidayTheme != 0 || customColors != 0 ? 25. : 10.; // Make the center of the chevron appear sooner if a theme is active + const float leadBuff = currentHolidayTheme != 0 || customColors != 0 ? 100. : 40.; // Make the center of the chevron appear sooner if a theme is active + const float d_rel = lead_data.getX()[0]; + const float v_rel = lead_data.getV()[0] - v_ego; float fillAlpha = 0; if (d_rel < leadBuff) { @@ -968,48 +1087,27 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState // chevron QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; - if (customColors != 0) { - painter.setBrush(themeConfiguration[customColors].second.first); + if (currentHolidayTheme != 0) { + painter.setBrush(std::get<2>(holidayThemeConfiguration[currentHolidayTheme]).begin()->second); + } else if (customColors != 0) { + painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second); } else { painter.setBrush(redColor(fillAlpha)); } painter.drawPolygon(chevron, std::size(chevron)); - // Add lead info if (leadInfo) { - // Declare the variables - QString unit_d = "meters"; - QString unit_s = "km/h"; - float distance = d_rel; - float lead_speed = std::max(lead_data.getVLead(), 0.0f); // Ensure speed doesn't go under 0 m/s cause that's dumb + float lead_speed = std::max(v_rel + v_ego, 0.0f); - // Conversion factors and units - constexpr float toFeet = 3.28084f; - constexpr float toMph = 2.23694f; - constexpr float toKmph = 3.6f; - - // Metric speed conversion - if (is_metric || useSI) { - lead_speed *= toKmph; - } else { - // US imperial conversion - distance *= toFeet; - lead_speed *= toMph; - unit_d = "feet"; - unit_s = "mph"; - } - - // Form the text and center it below the chevron painter.setPen(Qt::white); painter.setFont(InterFont(35, QFont::Bold)); QString text = QString("%1 %2 | %3 %4") - .arg(distance, 0, 'f', 2, '0') - .arg(unit_d) - .arg(lead_speed, 0, 'f', 2, '0') - .arg(unit_s); + .arg(qRound(d_rel * distanceConversion)) + .arg(leadDistanceUnit) + .arg(qRound(lead_speed * speedConversion)) + .arg(leadSpeedUnit); - // Calculate the text starting position QFontMetrics metrics(painter.font()); int middle_x = (chevron[2].x() + chevron[0].x()) / 2; int textWidth = metrics.horizontalAdvance(text); @@ -1028,6 +1126,7 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); const double start_draw_t = millis_since_boot(); const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); + const float v_ego = sm["carState"].getCarState().getVEgo(); // draw camera frame { @@ -1048,20 +1147,19 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { // Wide or narrow cam dependent on speed bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); - if (has_wide_cam) { - float v_ego = sm["carState"].getCarState().getVEgo(); + if (has_wide_cam && cameraView == 0) { if ((v_ego < 10) || available_streams.size() == 1) { wide_cam_requested = true; } else if (v_ego > 15) { wide_cam_requested = false; } - wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode() || cameraView == 2; + wide_cam_requested = wide_cam_requested && experimentalMode; // for replay of old routes, never go to widecam wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; } - paramsMemory.putBoolNonBlocking("WideCamera", wide_cam_requested); - CameraWidget::setStreamType(showDriverCamera || cameraView == 3 ? VISION_STREAM_DRIVER : - wide_cam_requested && cameraView != 1 ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); + CameraWidget::setStreamType(cameraView == 1 ? VISION_STREAM_DRIVER : + cameraView == 3 || wide_cam_requested ? VISION_STREAM_WIDE_ROAD : + VISION_STREAM_ROAD); s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; if (s->scene.calibration_valid) { @@ -1079,26 +1177,26 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); - if (s->scene.world_objects_visible && !showDriverCamera) { + if (s->scene.world_objects_visible) { update_model(s, model, sm["uiPlan"].getUiPlan()); drawLaneLines(painter, s); - if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { - auto radar_state = sm["radarState"].getRadarState(); - update_leads(s, radar_state, model.getPosition()); - auto lead_one = radar_state.getLeadOne(); - auto lead_two = radar_state.getLeadTwo(); - if (lead_one.getStatus()) { - drawLead(painter, lead_one, s->scene.lead_vertices[0]); - } - if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { - drawLead(painter, lead_two, s->scene.lead_vertices[1]); + if (s->scene.longitudinal_control && sm.rcv_frame("modelV2") > s->scene.started_frame) { + update_leads(s, model); + float prev_drel = -1; + for (int i = 0; i < model.getLeadsV3().size() && i < 2; i++) { + const auto &lead = model.getLeadsV3()[i]; + auto lead_drel = lead.getX()[0]; + if (lead.getProb() > 0.5 && (prev_drel < 0 || std::abs(lead_drel - prev_drel) > 3.0)) { + drawLead(painter, lead, s->scene.lead_vertices[i], v_ego); + } + prev_drel = lead_drel; } } } // DMoji - if (!hideBottomIcons && (sm.rcv_frame("driverStateV2") > s->scene.started_frame) && !muteDM) { + if (!hideBottomIcons && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) { update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM); drawDriverState(painter, s); } @@ -1118,27 +1216,21 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { auto m = msg.initEvent().initUiDebug(); m.setDrawTimeMillis(cur_draw_t - start_draw_t); pm->send("uiDebug", msg); - - // Update FrogPilot widgets - updateFrogPilotWidgets(painter); } void AnnotatedCameraWidget::showEvent(QShowEvent *event) { CameraWidget::showEvent(event); - std::thread updateFrogPilotParams(ui_update_params, uiState()); - updateFrogPilotParams.detach(); + ui_update_params(uiState()); prev_draw_t = millis_since_boot(); } // FrogPilot widgets void AnnotatedCameraWidget::initializeFrogPilotWidgets() { - Params params = Params(); - bottom_layout = new QHBoxLayout(); - personality_btn = new PersonalityButton(this); - bottom_layout->addWidget(personality_btn); + distance_btn = new DistanceButton(this); + bottom_layout->addWidget(distance_btn); QSpacerItem *spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); bottom_layout->addItem(spacer); @@ -1151,20 +1243,54 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() { main_layout->addLayout(bottom_layout); - // Custom themes configuration themeConfiguration = { - {1, {QString("frog_theme"), {QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, - {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, - {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}}, - {2, {QString("tesla_theme"), {QColor(0, 72, 255, 255), {{0.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.1))}}}}}, - {3, {QString("stalin_theme"), {QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}}} + {1, {"frog_theme", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, + {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, + {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, + {2, {"tesla_theme", QColor(0, 72, 255, 255), {{0.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.9))}, + {0.5, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.5))}, + {1.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.1))}}}}, + {3, {"stalin_theme", QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))}, + {0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, + {1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}}, + }; + + holidayThemeConfiguration = { + {1, {"april_fools", QColor(255, 165, 0, 255), {{0.0, QBrush(QColor::fromHslF(39 / 360., 1.0, 0.5, 0.9))}, + {0.5, QBrush(QColor::fromHslF(39 / 360., 1.0, 0.5, 0.5))}, + {1.0, QBrush(QColor::fromHslF(39 / 360., 1.0, 0.5, 0.1))}}}}, + {2, {"christmas", QColor(0, 72, 255, 255), {{0.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.9))}, + {0.5, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.5))}, + {1.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.1))}}}}, + {3, {"cinco_de_mayo", QColor(0, 104, 71, 255), {{0.0, QBrush(QColor::fromHslF(161 / 360., 1.0, 0.2, 0.9))}, + {0.5, QBrush(QColor::fromHslF(161 / 360., 1.0, 0.2, 0.5))}, + {1.0, QBrush(QColor::fromHslF(161 / 360., 1.0, 0.2, 0.1))}}}}, + {4, {"easter", QColor(200, 150, 200, 255), {{0.0, QBrush(QColor::fromHslF(300 / 360., 0.31, 0.69, 0.9))}, + {0.5, QBrush(QColor::fromHslF(300 / 360., 0.31, 0.69, 0.5))}, + {1.0, QBrush(QColor::fromHslF(300 / 360., 0.31, 0.69, 0.1))}}}}, + {5, {"fourth_of_july", QColor(0, 72, 255, 255), {{0.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.9))}, + {0.5, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.5))}, + {1.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.1))}}}}, + {6, {"halloween", QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))}, + {0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, + {1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}}, + {7, {"new_years_day", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, + {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, + {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, + {8, {"st_patricks_day", QColor(0, 128, 0, 255), {{0.0, QBrush(QColor::fromHslF(120 / 360., 1.0, 0.25, 0.9))}, + {0.5, QBrush(QColor::fromHslF(120 / 360., 1.0, 0.25, 0.5))}, + {1.0, QBrush(QColor::fromHslF(120 / 360., 1.0, 0.25, 0.1))}}}}, + {9, {"thanksgiving", QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))}, + {0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, + {1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}}, + {10, {"valentines_day", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, + {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, + {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, + {11, {"world_frog_day", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, + {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, + {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, }; - // Initialize the timer for the turn signal animation animationTimer = new QTimer(this); connect(animationTimer, &QTimer::timeout, this, [this] { animationFrameIndex = (animationFrameIndex + 1) % totalFrames; @@ -1173,180 +1299,192 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() { // Initialize the timer for the screen recorder QTimer *record_timer = new QTimer(this); connect(record_timer, &QTimer::timeout, this, [this]() { - if (this->recorder_btn) { - this->recorder_btn->update_screen(); + if (recorder_btn) { + recorder_btn->update_screen(); } }); record_timer->start(1000 / UI_FREQ); } -void AnnotatedCameraWidget::updateFrogPilotWidgets(QPainter &p) { - accelerationPath = scene.acceleration_path; - adjacentPath = scene.adjacent_path; - alwaysOnLateral = scene.always_on_lateral_active; +void AnnotatedCameraWidget::updateFrogPilotWidgets() { + alertSize = scene.alert_size; + + alwaysOnLateralActive = scene.always_on_lateral_active; + showAlwaysOnLateralStatusBar = scene.show_aol_status_bar; + blindSpotLeft = scene.blind_spot_left; blindSpotRight = scene.blind_spot_right; + cameraView = scene.camera_view; + compass = scene.compass; - conditionalExperimental = scene.conditional_experimental; - conditionalSpeed = scene.conditional_speed; - conditionalSpeedLead = scene.conditional_speed_lead; + conditionalStatus = scene.conditional_status; - cruiseAdjustment = fmax((0.1 * fmax(setSpeed - scene.adjusted_cruise, 0) + 0.9 * cruiseAdjustment) - 1, 0); + showConditionalExperimentalStatusBar = scene.show_cem_status_bar; + + bool disableSmoothing = vtscControllingCurve ? scene.disable_smoothing_vtsc : scene.disable_smoothing_mtsc; + cruiseAdjustment = disableSmoothing || !is_cruise_set ? fmax(setSpeed - scene.adjusted_cruise, 0) : fmax(0.25 * (setSpeed - scene.adjusted_cruise) + 0.75 * cruiseAdjustment - 1, 0); + vtscControllingCurve = scene.vtsc_controlling_curve; + customColors = scene.custom_colors; - desiredFollow = scene.desired_follow; + experimentalMode = scene.experimental_mode; - hideSpeed = scene.hide_speed; + + laneDetectionWidth = scene.lane_detection_width; laneWidthLeft = scene.lane_width_left; laneWidthRight = scene.lane_width_right; + leadInfo = scene.lead_info; - mapOpen = scene.map_open; - muteDM = scene.mute_dm; obstacleDistance = scene.obstacle_distance; obstacleDistanceStock = scene.obstacle_distance_stock; - onroadAdjustableProfiles = scene.personalities_via_screen; - reverseCruise = scene.reverse_cruise; + + mapOpen = scene.map_open; + bigMapOpen = mapOpen && scene.big_map; + + onroadDistanceButton = scene.onroad_distance_button; + roadNameUI = scene.road_name_ui; - showDriverCamera = scene.show_driver_camera; - showSLCOffset = scene.show_slc_offset; - slcOverridden = scene.speed_limit_overridden; - slcOverriddenSpeed = scene.speed_limit_overridden_speed; - slcSpeedLimit = scene.speed_limit; + + speedLimitController = scene.speed_limit_controller; + showSLCOffset = speedLimitController && scene.show_slc_offset; + slcOverridden = speedLimitController && scene.speed_limit_overridden; slcSpeedLimitOffset = scene.speed_limit_offset * (is_metric ? MS_TO_KPH : MS_TO_MPH); - stoppedEquivalence = scene.stopped_equivalence; - turnSignalLeft = scene.turn_signal_left; - turnSignalRight = scene.turn_signal_right; - useSI = scene.use_si; useViennaSLCSign = scene.use_vienna_slc_sign; - if (!showDriverCamera) { - if (leadInfo) { - drawLeadInfo(p); + trafficModeActive = scene.traffic_mode_active; + + turnSignalLeft = scene.turn_signal_left; + turnSignalRight = scene.turn_signal_right; + + if (currentHolidayTheme != scene.current_holiday_theme || customSignals != scene.custom_signals) { + currentHolidayTheme = scene.current_holiday_theme; + customSignals = scene.custom_signals; + + QString themePath; + + if (currentHolidayTheme != 0) { + themePath = QString("../frogpilot/assets/holiday_themes/%1/images").arg( + holidayThemeConfiguration.find(currentHolidayTheme) != holidayThemeConfiguration.end() ? + std::get<0>(holidayThemeConfiguration[currentHolidayTheme]) : ""); + } else { + themePath = QString("../frogpilot/assets/custom_themes/%1/images").arg( + themeConfiguration.find(customSignals) != themeConfiguration.end() ? + std::get<0>(themeConfiguration[customSignals]) : ""); } - if (alwaysOnLateral || conditionalExperimental || roadNameUI) { - drawStatusBar(p); + const QStringList imagePaths = { + themePath + "/turn_signal_1.png", + themePath + "/turn_signal_2.png", + themePath + "/turn_signal_3.png", + themePath + "/turn_signal_4.png" + }; + + signalImgVector.clear(); + signalImgVector.reserve(2 * imagePaths.size() + 2); + + for (const QString &imagePath : imagePaths) { + QPixmap pixmap(imagePath); + signalImgVector.push_back(pixmap); + signalImgVector.push_back(pixmap.transformed(QTransform().scale(-1, 1))); } - if (customSignals && (turnSignalLeft || turnSignalRight)) { - if (!animationTimer->isActive()) { - animationTimer->start(totalFrames * 11); // 440 milliseconds per loop; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks - } - drawTurnSignals(p); - } else if (animationTimer->isActive()) { - animationTimer->stop(); - } + const QPixmap blindSpotPixmap(themePath + "/turn_signal_1_red.png"); + signalImgVector.push_back(blindSpotPixmap); + signalImgVector.push_back(blindSpotPixmap.transformed(QTransform().scale(-1, 1))); + } +} + +void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) { + if ((showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI) && !bigMapOpen) { + drawStatusBar(p); } - const bool enableCompass = compass && !hideBottomIcons; + if (customSignals != 0 && (turnSignalLeft || turnSignalRight) && !bigMapOpen) { + if (!animationTimer->isActive()) { + animationTimer->start(totalFrames * 11); // 440 milliseconds per loop; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks + } + drawTurnSignals(p); + } else if (animationTimer->isActive()) { + animationTimer->stop(); + } + + if (leadInfo && !bigMapOpen) { + drawLeadInfo(p); + } + + if (scene.speed_limit_changed) { + drawSLCConfirmation(p); + } + + bool enableCompass = compass && !hideBottomIcons; compass_img->setVisible(enableCompass); if (enableCompass) { - if (bearingDeg != scene.bearing_deg) { - bearingDeg = scene.bearing_deg; - compass_img->updateState(bearingDeg); - } + compass_img->updateState(); bottom_layout->setAlignment(compass_img, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight)); } - const bool enablePersonalityButton = onroadAdjustableProfiles && !hideBottomIcons; - personality_btn->setVisible(enablePersonalityButton); - if (enablePersonalityButton) { - if (paramsMemory.getBool("PersonalityChangedViaWheel")) { - personality_btn->checkUpdate(); - } - bottom_layout->setAlignment(personality_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft)); + bool enableDistanceButton = onroadDistanceButton && !hideBottomIcons; + distance_btn->setVisible(enableDistanceButton); + if (enableDistanceButton) { + distance_btn->updateState(); + bottom_layout->setAlignment(distance_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft)); + } + + bool enablePedalIcons = scene.pedals_on_ui && !bigMapOpen; + pedal_icons->setVisible(enablePedalIcons); + if (enablePedalIcons) { + pedal_icons->updateState(); } map_settings_btn_bottom->setEnabled(map_settings_btn->isEnabled()); if (map_settings_btn_bottom->isEnabled()) { - map_settings_btn_bottom->setVisible(!hideBottomIcons && !compass); + map_settings_btn_bottom->setVisible(!hideBottomIcons && !compass && !scene.hide_map_icon); bottom_layout->setAlignment(map_settings_btn_bottom, rightHandDM ? Qt::AlignLeft : Qt::AlignRight); } - // Update the turn signal animation images upon toggle change - if (customSignals != scene.custom_signals) { - customSignals = scene.custom_signals; - - const QString theme_path = QString("../frogpilot/assets/custom_themes/%1/images").arg(themeConfiguration.find(customSignals) != themeConfiguration.end() ? - themeConfiguration[customSignals].first : "stock_theme"); - const QStringList imagePaths = { - theme_path + "/turn_signal_1.png", - theme_path + "/turn_signal_2.png", - theme_path + "/turn_signal_3.png", - theme_path + "/turn_signal_4.png" - }; - - signalImgVector.clear(); - signalImgVector.reserve(4 * imagePaths.size() + 2); // Reserve space for both regular and flipped images - for (int i = 0; i < 2; ++i) { - for (const QString &imagePath : imagePaths) { - QPixmap pixmap(imagePath); - signalImgVector.push_back(pixmap); // Regular image - signalImgVector.push_back(pixmap.transformed(QTransform().scale(-1, 1))); // Flipped image - } - } - signalImgVector.push_back(QPixmap(theme_path + "/turn_signal_1_red.png")); // Regular blindspot image - signalImgVector.push_back(QPixmap(theme_path + "/turn_signal_1_red.png").transformed(QTransform().scale(-1, 1))); // Flipped blindspot image - } + recorder_btn->setVisible(scene.screen_recorder && !mapOpen); } -Compass::Compass(QWidget *parent) : QWidget(parent) { +Compass::Compass(QWidget *parent) : QWidget(parent), scene(uiState()->scene) { setFixedSize(btn_size * 1.5, btn_size * 1.5); compassSize = btn_size; circleOffset = compassSize / 2; degreeLabelOffset = circleOffset + 25; innerCompass = compassSize / 2; + x = (btn_size * 1.5) / 2 + 20; y = (btn_size * 1.5) / 2; compassInnerImg = loadPixmap("../frogpilot/assets/other_images/compass_inner.png", QSize(compassSize / 1.75, compassSize / 1.75)); - initializeStaticElements(); -} - -void Compass::updateState(int bearing_deg) { - bearingDeg = bearing_deg; - update(); -} - -void Compass::initializeStaticElements() { staticElements = QPixmap(size()); staticElements.fill(Qt::transparent); QPainter p(&staticElements); p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); - // Configure the circles QPen whitePen(Qt::white, 2); p.setPen(whitePen); - // Draw the circle background and white inner circle p.setOpacity(1.0); p.setBrush(QColor(0, 0, 0, 100)); p.drawEllipse(x - circleOffset, y - circleOffset, circleOffset * 2, circleOffset * 2); - // Draw the white circles p.setBrush(Qt::NoBrush); p.drawEllipse(x - (innerCompass + 5), y - (innerCompass + 5), (innerCompass + 5) * 2, (innerCompass + 5) * 2); p.drawEllipse(x - degreeLabelOffset, y - degreeLabelOffset, degreeLabelOffset * 2, degreeLabelOffset * 2); - // Draw the black background for the bearing degrees QPainterPath outerCircle, innerCircle; outerCircle.addEllipse(x - degreeLabelOffset, y - degreeLabelOffset, degreeLabelOffset * 2, degreeLabelOffset * 2); innerCircle.addEllipse(x - circleOffset, y - circleOffset, compassSize, compassSize); p.fillPath(outerCircle.subtracted(innerCircle), Qt::black); +} - // Draw the static degree lines - for (int i = 0; i < 360; i += 15) { - bool isCardinalDirection = i % 90 == 0; - int lineLength = isCardinalDirection ? 15 : 10; - p.setPen(QPen(Qt::white, isCardinalDirection ? 3 : 1)); - p.save(); - p.translate(x, y); - p.rotate(i); - p.drawLine(0, -(compassSize / 2 - lineLength), 0, -(compassSize / 2)); - p.restore(); +void Compass::updateState() { + if (bearingDeg != scene.bearing_deg) { + update(); + bearingDeg = scene.bearing_deg; } } @@ -1354,20 +1492,22 @@ void Compass::paintEvent(QPaintEvent *event) { QPainter p(this); p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); - // Draw static elements + bearingDeg = fmod(bearingDeg, 360); + if (bearingDeg < 0) { + bearingDeg += 360; + } + p.drawPixmap(0, 0, staticElements); - // Rotate and draw the compassInnerImg image p.translate(x, y); p.rotate(bearingDeg); p.drawPixmap(-compassInnerImg.width() / 2, -compassInnerImg.height() / 2, compassInnerImg); p.rotate(-bearingDeg); p.translate(-x, -y); - // Draw the dynamic bearing degree numbers and lines QFont font = InterFont(10, QFont::Normal); for (int i = 0; i < 360; i += 15) { - bool isBold = abs(i - static_cast(bearingDeg)) <= 7; + bool isBold = abs(i - bearingDeg) <= 7; font.setWeight(isBold ? QFont::Bold : QFont::Normal); p.setFont(font); p.setPen(QPen(Qt::white, i % 90 == 0 ? 2 : 1)); @@ -1382,118 +1522,208 @@ void Compass::paintEvent(QPaintEvent *event) { p.restore(); } - // Draw cardinal directions p.setFont(InterFont(20, QFont::Bold)); - QString directions[] = {"N", "E", "S", "W", "N"}; - int fromAngles[] = {337, 68, 158, 248, 337}; - int toAngles[] = {22, 112, 202, 292, 360}; - int alignmentFlags[] = {Qt::AlignTop | Qt::AlignHCenter, Qt::AlignRight | Qt::AlignVCenter, Qt::AlignBottom | Qt::AlignHCenter, Qt::AlignLeft | Qt::AlignVCenter, Qt::AlignTop | Qt::AlignHCenter}; + std::map, int, QColor>> directionInfo = { + {"N", {{292.5, 67.5}, Qt::AlignTop | Qt::AlignHCenter, Qt::white}}, + {"E", {{22.5, 157.5}, Qt::AlignRight | Qt::AlignVCenter, Qt::white}}, + {"S", {{112.5, 247.5}, Qt::AlignBottom | Qt::AlignHCenter, Qt::white}}, + {"W", {{202.5, 337.5}, Qt::AlignLeft | Qt::AlignVCenter, Qt::white}} + }; int directionOffset = 20; - for (int i = 0; i < 5; ++i) { - int offset = (directions[i] == "E") ? -5 : (directions[i] == "W" ? 5 : 0); - p.setOpacity((bearingDeg >= fromAngles[i] && bearingDeg < toAngles[i]) ? 1.0 : 0.2); - QRect textRect(x - innerCompass + offset + directionOffset, y - innerCompass + directionOffset, innerCompass * 2 - 2 * directionOffset, innerCompass * 2 - 2 * directionOffset); - p.drawText(textRect, alignmentFlags[i], directions[i]); + for (auto &item : directionInfo) { + QString direction = item.first; + auto &[range, alignmentFlag, color] = item.second; + auto &[minRange, maxRange] = range; + + QRect textRect(x - innerCompass + directionOffset, y - innerCompass + directionOffset, innerCompass * 2 - 2 * directionOffset, innerCompass * 2 - 2 * directionOffset); + + bool isInRange = false; + if (minRange > maxRange) { + isInRange = bearingDeg >= minRange || bearingDeg <= maxRange; + } else { + isInRange = bearingDeg >= minRange && bearingDeg <= maxRange; + } + + p.setOpacity(isInRange ? 1.0 : 0.2); + p.setPen(QPen(color)); + p.drawText(textRect, alignmentFlag, direction); + } +} + +DistanceButton::DistanceButton(QWidget *parent) : QPushButton(parent), scene(uiState()->scene) { + setFixedSize(btn_size * 1.5, btn_size * 1.5); + + profile_data = { + {QPixmap("../frogpilot/assets/other_images/traffic.png"), "Traffic"}, + {QPixmap("../frogpilot/assets/other_images/aggressive.png"), "Aggressive"}, + {QPixmap("../frogpilot/assets/other_images/standard.png"), "Standard"}, + {QPixmap("../frogpilot/assets/other_images/relaxed.png"), "Relaxed"} + }; + + profile_data_kaofui = { + {QPixmap("../frogpilot/assets/other_images/traffic_kaofui.png"), "Traffic"}, + {QPixmap("../frogpilot/assets/other_images/aggressive_kaofui.png"), "Aggressive"}, + {QPixmap("../frogpilot/assets/other_images/standard_kaofui.png"), "Standard"}, + {QPixmap("../frogpilot/assets/other_images/relaxed_kaofui.png"), "Relaxed"} + }; + + transitionTimer.start(); + + connect(this, &QPushButton::pressed, this, &DistanceButton::buttonPressed); + connect(this, &QPushButton::released, this, &DistanceButton::buttonReleased); +} + +void DistanceButton::buttonPressed() { + paramsMemory.putBool("OnroadDistanceButtonPressed", true); +} + +void DistanceButton::buttonReleased() { + paramsMemory.putBool("OnroadDistanceButtonPressed", false); +} + +void DistanceButton::updateState() { + if (trafficModeActive != scene.traffic_mode_active || personality != static_cast(scene.personality) && !trafficModeActive) { + transitionTimer.restart(); + } + + personality = static_cast(scene.personality); + trafficModeActive = scene.traffic_mode_active; +} + +void DistanceButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); + + constexpr qreal fadeDuration = 1000.0; + constexpr qreal textDuration = 3000.0; + int elapsed = transitionTimer.elapsed(); + + qreal textOpacity = qBound(0.0, 1.0 - ((elapsed - textDuration) / fadeDuration), 1.0); + qreal imageOpacity = qBound(0.0, (elapsed - textDuration) / fadeDuration, 1.0); + + int profile = trafficModeActive ? 0 : personality + 1; + auto &[profileImage, profileText] = scene.use_kaofui_icons ? profile_data_kaofui[profile] : profile_data[profile]; + + if (textOpacity != 0.0) { + p.setOpacity(textOpacity); + p.setFont(InterFont(40, QFont::Bold)); + p.setPen(Qt::white); + QRect textRect(-25, 0, width(), height() + 95); + p.drawText(textRect, Qt::AlignCenter, profileText); + } + + if (imageOpacity != 0.0) { + drawIcon(p, QPoint((btn_size / 2) * 1.25, btn_size / 2 + 95), profileImage, Qt::transparent, imageOpacity); } } void AnnotatedCameraWidget::drawLeadInfo(QPainter &p) { - SubMaster &sm = *uiState()->sm; - - // Declare the variables static QElapsedTimer timer; static bool isFiveSecondsPassed = false; + static double maxAcceleration = 0.0; constexpr int maxAccelDuration = 5000; - // Constants for units and conversions - QString unit_a = " m/s²"; - QString unit_d = mapOpen ? "m" : "meters"; - QString unit_s = "kmh"; - float distanceConversion = 1.0f; - float speedConversion = 1.0f; + QString accelerationUnit = tr(" m/s²"); + leadDistanceUnit = tr(mapOpen ? "m" : "meters"); + leadSpeedUnit = tr("m/s"); - // Conversion factors and units - constexpr float toFeet = 3.28084f; - constexpr float toMph = 2.23694f; + float accelerationConversion = 1.0f; + distanceConversion = 1.0f; + speedConversion = 1.0f; - // Metric speed conversion - if (!(is_metric || useSI)) { - // US imperial conversion - unit_a = " ft/s²"; - unit_d = mapOpen ? "ft" : "feet"; - unit_s = "mph"; - distanceConversion = toFeet; - speedConversion = toMph; + if (!scene.use_si) { + if (is_metric) { + leadSpeedUnit = tr("kph"); + speedConversion = MS_TO_KPH; + } else { + accelerationUnit = tr(" ft/s²"); + leadDistanceUnit = tr(mapOpen ? "ft" : "feet"); + leadSpeedUnit = tr("mph"); + + accelerationConversion = METER_TO_FOOT; + distanceConversion = METER_TO_FOOT; + speedConversion = MS_TO_MPH; + } } - // Update acceleration - double currentAcceleration = std::round(sm["carState"].getCarState().getAEgo() * 100) / 100; - static double maxAcceleration = 0.0; + double acceleration = std::round(scene.acceleration * 100) / 100; + int randomEvent = scene.current_random_event; - if (currentAcceleration > maxAcceleration && status == STATUS_ENGAGED) { - maxAcceleration = currentAcceleration; + if (acceleration > maxAcceleration && (status == STATUS_ENGAGED || status == STATUS_TRAFFIC_MODE_ACTIVE)) { + maxAcceleration = acceleration; + isFiveSecondsPassed = false; + timer.start(); + } else if (randomEvent == 2 && maxAcceleration < 3.0) { + maxAcceleration = 3.0; + isFiveSecondsPassed = false; + timer.start(); + } else if (randomEvent == 3 && maxAcceleration < 3.5) { + maxAcceleration = 3.5; + isFiveSecondsPassed = false; + timer.start(); + } else if (randomEvent == 4 && maxAcceleration < 4.0) { + maxAcceleration = 4.0; isFiveSecondsPassed = false; timer.start(); } else { isFiveSecondsPassed = timer.hasExpired(maxAccelDuration); } - // Construct text segments auto createText = [&](const QString &title, const double data) { - return title + QString::number(std::round(data * distanceConversion)) + " " + unit_d; + return title + QString::number(std::round(data * distanceConversion)) + " " + leadDistanceUnit; }; - // Create segments for insights - QString accelText = QString("Accel: %1%2") - .arg(currentAcceleration * speedConversion, 0, 'f', 2) - .arg(unit_a); + QString accelText = QString(tr("Accel: %1%2")) + .arg(acceleration * accelerationConversion, 0, 'f', 2) + .arg(accelerationUnit); - QString maxAccSuffix = QString(mapOpen ? "" : " - Max: %1%2") - .arg(maxAcceleration * speedConversion, 0, 'f', 2) - .arg(unit_a); + QString maxAccSuffix; + if (!mapOpen) { + maxAccSuffix = tr(" - Max: %1%2") + .arg(maxAcceleration * accelerationConversion, 0, 'f', 2) + .arg(accelerationUnit); + } - QString obstacleText = createText(mapOpen ? " | Obstacle: " : " | Obstacle Factor: ", obstacleDistance); - QString stopText = createText(mapOpen ? " - Stop: " : " - Stop Factor: ", stoppedEquivalence); - QString followText = " = " + createText(mapOpen ? "Follow: " : "Follow Distance: ", desiredFollow); + QString obstacleText = createText(mapOpen ? tr(" | Obstacle: ") : tr(" | Obstacle Factor: "), obstacleDistance); + QString stopText = createText(mapOpen ? tr(" - Stop: ") : tr(" - Stop Factor: "), scene.stopped_equivalence); + QString followText = " = " + createText(mapOpen ? tr("Follow: ") : tr("Follow Distance: "), scene.desired_follow); - // Check if the longitudinal toggles have an impact on the driving logics auto createDiffText = [&](const double data, const double stockData) { double difference = std::round((data - stockData) * distanceConversion); return difference != 0 ? QString(" (%1%2)").arg(difference > 0 ? "+" : "").arg(difference) : QString(); }; - // Prepare rectangle for insights p.save(); + QRect insightsRect(rect().left() - 1, rect().top() - 60, rect().width() + 2, 100); p.setBrush(QColor(0, 0, 0, 150)); p.drawRoundedRect(insightsRect, 30, 30); - p.setFont(InterFont(30, QFont::DemiBold)); + p.setFont(InterFont(28, QFont::Bold)); p.setRenderHint(QPainter::TextAntialiasing); - // Calculate positioning for text drawing QRect adjustedRect = insightsRect.adjusted(0, 27, 0, 27); int textBaseLine = adjustedRect.y() + (adjustedRect.height() + p.fontMetrics().height()) / 2 - p.fontMetrics().descent(); - // Calculate the entire text width to ensure perfect centering int totalTextWidth = p.fontMetrics().horizontalAdvance(accelText) - + p.fontMetrics().horizontalAdvance(maxAccSuffix) - + p.fontMetrics().horizontalAdvance(obstacleText) - + p.fontMetrics().horizontalAdvance(createDiffText(obstacleDistance, obstacleDistanceStock)) - + p.fontMetrics().horizontalAdvance(stopText) - + p.fontMetrics().horizontalAdvance(followText); + + p.fontMetrics().horizontalAdvance(maxAccSuffix) + + p.fontMetrics().horizontalAdvance(obstacleText) + + p.fontMetrics().horizontalAdvance(createDiffText(obstacleDistance, obstacleDistanceStock)) + + p.fontMetrics().horizontalAdvance(stopText) + + p.fontMetrics().horizontalAdvance(followText); int textStartPos = adjustedRect.x() + (adjustedRect.width() - totalTextWidth) / 2; - // Draw the text - auto drawText = [&](const QString &text, const QColor color) { + auto drawText = [&](const QString &text, const QColor &color) { p.setPen(color); p.drawText(textStartPos, textBaseLine, text); textStartPos += p.fontMetrics().horizontalAdvance(text); }; drawText(accelText, Qt::white); - drawText(maxAccSuffix, isFiveSecondsPassed ? Qt::white : Qt::red); + if (!maxAccSuffix.isEmpty()) { + drawText(maxAccSuffix, isFiveSecondsPassed ? Qt::white : redColor()); + } drawText(obstacleText, Qt::white); drawText(createDiffText(obstacleDistance, obstacleDistanceStock), (obstacleDistance - obstacleDistanceStock) > 0 ? Qt::green : Qt::red); drawText(stopText, Qt::white); @@ -1502,130 +1732,135 @@ void AnnotatedCameraWidget::drawLeadInfo(QPainter &p) { p.restore(); } -PersonalityButton::PersonalityButton(QWidget *parent) : QPushButton(parent), scene(uiState()->scene) { - setFixedSize(btn_size * 1.5, btn_size * 1.5); +PedalIcons::PedalIcons(QWidget *parent) : QWidget(parent), scene(uiState()->scene) { + setFixedSize(btn_size, btn_size); - // Configure the profile vector - profile_data = { - {QPixmap("../frogpilot/assets/other_images/aggressive.png"), "Aggressive"}, - {QPixmap("../frogpilot/assets/other_images/standard.png"), "Standard"}, - {QPixmap("../frogpilot/assets/other_images/relaxed.png"), "Relaxed"} - }; - - // Start the timer as soon as the button is created - transitionTimer.start(); - - // Initialize the click event - connect(this, &QPushButton::clicked, this, &PersonalityButton::handleClick); - - personalityProfile = params.getInt("LongitudinalPersonality"); - setVisible(scene.personalities_via_screen); + brake_pedal_img = loadPixmap("../frogpilot/assets/other_images/brake_pedal.png", QSize(img_size, img_size)); + gas_pedal_img = loadPixmap("../frogpilot/assets/other_images/gas_pedal.png", QSize(img_size, img_size)); } -void PersonalityButton::checkUpdate() { - // Sync with the steering wheel button - personalityProfile = params.getInt("LongitudinalPersonality"); - updateState(); - paramsMemory.putBool("PersonalityChangedViaWheel", false); +void PedalIcons::updateState() { + acceleration = scene.acceleration; + + accelerating = acceleration > 0.25; + decelerating = acceleration < -0.25; + + if (accelerating || decelerating) { + update(); + } } -void PersonalityButton::handleClick() { - int mapping[] = {2, 0, 1}; - personalityProfile = mapping[personalityProfile]; - - params.putInt("LongitudinalPersonality", personalityProfile); - paramsMemory.putBool("PersonalityChangedViaUI", true); - - updateState(); -} - -void PersonalityButton::updateState() { - // Start the transition - transitionTimer.restart(); -} - -void PersonalityButton::paintEvent(QPaintEvent *) { - // Declare the constants - constexpr qreal fadeDuration = 1000.0; // 1 second - constexpr qreal textDuration = 3000.0; // 3 seconds - +void PedalIcons::paintEvent(QPaintEvent *event) { QPainter p(this); - int elapsed = transitionTimer.elapsed(); - qreal textOpacity = qBound(0.0, 1.0 - ((elapsed - textDuration) / fadeDuration), 1.0); - qreal imageOpacity = qBound(0.0, (elapsed - textDuration) / fadeDuration, 1.0); + p.setRenderHint(QPainter::Antialiasing); - // Enable Antialiasing - p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); + int totalWidth = 2 * img_size; + int startX = (width() - totalWidth) / 2; - // Configure the button - const auto &[profile_image, profile_text] = profile_data[personalityProfile]; - QRect rect(0, 0, width(), height() + 95); + int brakeX = startX + img_size / 2; + int gasX = startX + img_size; - // Draw the profile text with the calculated opacity - if (textOpacity > 0.0) { - p.setOpacity(textOpacity); - p.setFont(InterFont(40, QFont::Bold)); - p.setPen(Qt::white); - p.drawText(rect, Qt::AlignCenter, profile_text); - } + float brakeOpacity = scene.standstill ? 1.0f : decelerating ? std::max(0.25f, std::abs(acceleration)) : 0.25f; + float gasOpacity = accelerating ? std::max(0.25f, acceleration) : 0.25f; - // Draw the profile image with the calculated opacity - if (imageOpacity > 0.0) { - drawIcon(p, QPoint((btn_size / 2) * 1.25, btn_size / 2 + 95), profile_image, Qt::transparent, imageOpacity); - } + p.setOpacity(brakeOpacity); + p.drawPixmap(brakeX, (height() - img_size) / 2, brake_pedal_img); + + p.setOpacity(gasOpacity); + p.drawPixmap(gasX, (height() - img_size) / 2, gas_pedal_img); +} + +void AnnotatedCameraWidget::drawSLCConfirmation(QPainter &p) { + p.save(); + + QSize size = this->size(); + + QRect leftRect(0, 0, size.width() / 2, size.height()); + QRect rightRect = leftRect.translated(size.width() / 2, 0); + + p.setOpacity(0.5); + p.fillRect(leftRect, rightHandDM ? redColor() : greenColor()); + p.fillRect(rightRect, rightHandDM ? greenColor() : redColor()); + p.setOpacity(1.0); + + p.setFont(InterFont(75, QFont::Bold)); + p.setPen(Qt::white); + + QString unitText = is_metric ? tr("kph") : tr("mph"); + QString speedText = QString::number(std::nearbyint(scene.unconfirmed_speed_limit * (is_metric ? MS_TO_KPH : MS_TO_MPH))) + " " + unitText; + QString confirmText = tr("Confirm speed limit\n") + speedText; + QString ignoreText = tr("Ignore speed limit\n") + speedText; + + QRect textLeftRect = QRect(leftRect.left(), leftRect.top() + leftRect.height() / 2 - 225, leftRect.width(), leftRect.height() / 2); + QRect textRightRect = QRect(rightRect.left(), rightRect.top() + rightRect.height() / 2 - 225, rightRect.width(), rightRect.height() / 2); + + p.drawText(textLeftRect, Qt::AlignCenter, rightHandDM ? ignoreText : confirmText); + p.drawText(textRightRect, Qt::AlignCenter, rightHandDM ? confirmText : ignoreText); + + p.restore(); } void AnnotatedCameraWidget::drawStatusBar(QPainter &p) { p.save(); - // Variable declarations - QElapsedTimer timer; - static QString lastShownStatus; - static QString newStatus; - static bool displayStatusText = false; constexpr qreal fadeDuration = 1500.0; constexpr qreal textDuration = 5000.0; - // Draw status bar + static QElapsedTimer timer; + static QString lastShownStatus; + + QString newStatus; + QRect currentRect = rect(); QRect statusBarRect(currentRect.left() - 1, currentRect.bottom() - 50, currentRect.width() + 2, 100); + p.setBrush(QColor(0, 0, 0, 150)); p.setOpacity(1.0); p.drawRoundedRect(statusBarRect, 30, 30); -QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadName")) : QString(); -// // QString roadName = QString::fromStdString(paramsMemory.get("oscar_debug")); + std::map conditionalStatusMap = { + {0, tr("Conditional Experimental Mode ready")}, + {1, tr("Conditional Experimental overridden")}, + {2, tr("Experimental Mode manually activated")}, + {3, tr("Conditional Experimental overridden")}, + {4, tr("Experimental Mode manually activated")}, + {5, tr("Conditional Experimental overridden")}, + {6, tr("Experimental Mode manually activated")}, + {7, tr("Experimental Mode activated for") + (mapOpen ? tr(" intersection") : tr(" upcoming intersection"))}, + {8, tr("Experimental Mode activated for") + (mapOpen ? tr(" turn") : tr(" upcoming turn"))}, + {9, tr("Experimental Mode activated due to") + (mapOpen ? tr(" SLC") : tr(" no speed limit set"))}, + {10, tr("Experimental Mode activated due to") + (mapOpen ? tr(" speed") : tr(" speed being less than ") + QString::number(scene.conditional_speed_lead) + (is_metric ? tr(" kph") : tr(" mph")))}, + {11, tr("Experimental Mode activated due to") + (mapOpen ? tr(" speed") : tr(" speed being less than ") + QString::number(scene.conditional_speed) + (is_metric ? tr(" kph") : tr(" mph")))}, + {12, tr("Experimental Mode activated for slower lead")}, + {13, tr("Experimental Mode activated for turn") + (mapOpen ? "" : tr(" / lane change"))}, + {14, tr("Experimental Mode activated for curve")}, + {15, tr("Experimental Mode activated for stop") + (mapOpen ? "" : tr(" sign / stop light"))}, + }; -// QMap conditionalStatusMap = { -// {0, "Conditional Experimental Mode ready"}, -// {1, "Conditional Experimental overridden"}, -// {2, "Experimental Mode manually activated"}, -// {3, "Conditional Experimental overridden"}, -// {4, "Experimental Mode manually activated"}, -// {5, "Experimental Mode activated for navigation" + (mapOpen ? "" : QString(" instructions input"))}, -// {6, "Experimental Mode activated due to" + (mapOpen ? "SLC" : QString(" no speed limit set"))}, -// {7, "Experimental Mode activated due to" + (mapOpen ? " speed" : " speed being less than " + QString::number(conditionalSpeedLead) + (is_metric ? " kph" : " mph"))}, -// {8, "Experimental Mode activated due to" + (mapOpen ? " speed" : " speed being less than " + QString::number(conditionalSpeed) + (is_metric ? " kph" : " mph"))}, -// {9, "Experimental Mode activated for slower lead"}, -// {10, "Experimental Mode activated for turn" + (mapOpen ? "" : QString(" / lane change"))}, -// {11, "Experimental Mode activated for curve"}, -// {12, "Experimental Mode activated for stop" + (mapOpen ? "" : QString(" sign / stop light"))}, -// }; + QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadName")) : QString(); - QString screenSuffix = ". Double tap the screen to revert"; - QString wheelSuffix = ". Double press the \"LKAS\" button to revert"; + if (alwaysOnLateralActive && showAlwaysOnLateralStatusBar) { + newStatus = tr("Always On Lateral active") + (mapOpen ? "" : tr(". Press the \"Cruise Control\" button to disable")); + } else if (showConditionalExperimentalStatusBar) { + newStatus = conditionalStatusMap[status != STATUS_DISENGAGED ? conditionalStatus : 0]; + } -// if (alwaysOnLateral) { -// newStatus = QString("Always On Lateral active") + (mapOpen ? "" : ". Press the \"Cruise Control\" button to disable"); -// } else if (conditionalExperimental) { -// newStatus = conditionalStatusMap.contains(conditionalStatus) && status != STATUS_DISENGAGED ? conditionalStatusMap[conditionalStatus] : conditionalStatusMap[0]; -// } + QString distanceSuffix = tr(". Long press the \"distance\" button to revert"); + QString lkasSuffix = tr(". Double press the \"LKAS\" button to revert"); + QString screenSuffix = tr(". Double tap the screen to revert"); - newStatus = QString::fromStdString(paramsMemory.get("oscar_debug")); + if (!alwaysOnLateralActive && !mapOpen && status != STATUS_DISENGAGED && !newStatus.isEmpty()) { + if (conditionalStatus == 1 || conditionalStatus == 2) { + newStatus += distanceSuffix; + } else if (conditionalStatus == 3 || conditionalStatus == 4) { + newStatus += lkasSuffix; + } else if (conditionalStatus == 5 || conditionalStatus == 6) { + newStatus += screenSuffix; + } + } - // Check if status has changed or if the road name is empty if (newStatus != lastShownStatus || roadName.isEmpty()) { displayStatusText = true; lastShownStatus = newStatus; @@ -1633,16 +1868,11 @@ QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadNam } else if (displayStatusText && timer.hasExpired(textDuration + fadeDuration)) { displayStatusText = false; } - if (!alwaysOnLateral && !mapOpen && status != STATUS_DISENGAGED && !newStatus.isEmpty()) { - newStatus += (conditionalStatus == 3 || conditionalStatus == 4) ? screenSuffix : (conditionalStatus == 1 || conditionalStatus == 2) ? wheelSuffix : ""; - } - // Configure the text p.setFont(InterFont(40, QFont::Bold)); p.setPen(Qt::white); p.setRenderHint(QPainter::TextAntialiasing); - // Calculate text opacity static qreal roadNameOpacity; static qreal statusTextOpacity; int elapsed = timer.elapsed(); @@ -1651,16 +1881,14 @@ QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadNam roadNameOpacity = 1.0 - statusTextOpacity; } else { roadNameOpacity = qBound(0.0, elapsed / fadeDuration, 1.0); - statusTextOpacity = 1.0 - roadNameOpacity; + statusTextOpacity = 0.0; } - // Draw the status text p.setOpacity(statusTextOpacity); QRect textRect = p.fontMetrics().boundingRect(statusBarRect, Qt::AlignCenter | Qt::TextWordWrap, newStatus); textRect.moveBottom(statusBarRect.bottom() - 50); p.drawText(textRect, Qt::AlignCenter | Qt::TextWordWrap, newStatus); - // Draw the road name with the calculated opacity if (!roadName.isEmpty()) { p.setOpacity(roadNameOpacity); textRect = p.fontMetrics().boundingRect(statusBarRect, Qt::AlignCenter | Qt::TextWordWrap, roadName); @@ -1672,32 +1900,25 @@ QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadNam } void AnnotatedCameraWidget::drawTurnSignals(QPainter &p) { - // Declare the turn signal size constexpr int signalHeight = 480; constexpr int signalWidth = 360; - // Calculate the vertical position for the turn signals - int baseYPosition = (height() - signalHeight) / 2 + (alwaysOnLateral || conditionalExperimental || roadNameUI ? 225 : 300); - // Calculate the x-coordinates for the turn signals + p.setRenderHint(QPainter::Antialiasing); + + int baseYPosition = (height() - signalHeight) / 2 + (showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI ? 225 : 300) - alertSize; int leftSignalXPosition = 75 + width() - signalWidth - 300 * (blindSpotLeft ? 0 : animationFrameIndex); int rightSignalXPosition = -75 + 300 * (blindSpotRight ? 0 : animationFrameIndex); - // Enable Antialiasing - p.setRenderHint(QPainter::Antialiasing); - - // Draw the turn signals if (animationFrameIndex < signalImgVector.size()) { auto drawSignal = [&](bool signalActivated, int xPosition, bool flip, bool blindspot) { if (signalActivated) { - // Get the appropriate image from the signalImgVector - int uniqueImages = signalImgVector.size() / 4; // Each image has a regular, flipped, and two blindspot versions + int uniqueImages = signalImgVector.size() / 4; int index = (blindspot ? 2 * uniqueImages : 2 * animationFrameIndex % totalFrames) + (flip ? 1 : 0); QPixmap &signal = signalImgVector[index]; p.drawPixmap(xPosition, baseYPosition, signalWidth, signalHeight, signal); } }; - // Display the animation based on which signal is activated drawSignal(turnSignalLeft, leftSignalXPosition, false, blindSpotLeft); drawSignal(turnSignalRight, rightSignalXPosition, true, blindSpotRight); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h old mode 100755 new mode 100644 index 07c1c3f..963b22d --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -2,7 +2,8 @@ #include -#include +#include +#include #include #include #include @@ -16,7 +17,6 @@ const int btn_size = 192; const int img_size = (btn_size / 4) * 3; -// FrogPilot global variables static double fps; // ***** onroad widgets ***** @@ -42,13 +42,14 @@ class Compass : public QWidget { public: explicit Compass(QWidget *parent = nullptr); - void initializeStaticElements(); - void updateState(int bearing_deg); + void updateState(); protected: void paintEvent(QPaintEvent *event) override; private: + UIScene &scene; + int bearingDeg; int circleOffset; int compassSize; @@ -56,10 +57,37 @@ private: int innerCompass; int x; int y; + QPixmap compassInnerImg; QPixmap staticElements; }; +class DistanceButton : public QPushButton { +public: + explicit DistanceButton(QWidget *parent = nullptr); + + void buttonPressed(); + void buttonReleased(); + void updateState(); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + Params paramsMemory{"/dev/shm/params"}; + + UIScene &scene; + + bool trafficModeActive; + + int personality; + + QElapsedTimer transitionTimer; + + QVector> profile_data; + QVector> profile_data_kaofui; +}; + class ExperimentalButton : public QPushButton { Q_OBJECT @@ -78,14 +106,24 @@ private: bool engageable; // FrogPilot variables + Params paramsMemory{"/dev/shm/params"}; UIScene &scene; - std::map wheelImages; + QMap wheelImages; + QMap wheelImagesGif; + QMovie engage_gif; + QLabel *gifLabel; + + bool docRandomEventTriggered; bool firefoxRandomEventTriggered; bool rotatingWheel; + bool treeFiddyRandomEventTriggered; + bool weebRandomEventTriggered; + int steeringAngleDeg; int wheelIcon; + int wheelIconGif; int y_offset; }; @@ -102,28 +140,25 @@ private: QPixmap settings_img; }; -// FrogPilot buttons -class PersonalityButton : public QPushButton { -public: - explicit PersonalityButton(QWidget *parent = 0); +class PedalIcons : public QWidget { + Q_OBJECT - void checkUpdate(); - void handleClick(); +public: + explicit PedalIcons(QWidget *parent = 0); void updateState(); private: void paintEvent(QPaintEvent *event) override; - Params params; - Params paramsMemory{"/dev/shm/params"}; + QPixmap brake_pedal_img; + QPixmap gas_pedal_img; UIScene &scene; - int personalityProfile = 0; + bool accelerating; + bool decelerating; - QElapsedTimer transitionTimer; - - QVector> profile_data; + float acceleration; }; // container window for the NVG UI @@ -163,79 +198,86 @@ private: bool wide_cam_requested = false; // FrogPilot widgets + void initializeFrogPilotWidgets(); + void paintFrogPilotWidgets(QPainter &p); + void updateFrogPilotWidgets(); + void drawLeadInfo(QPainter &p); + void drawSLCConfirmation(QPainter &p); void drawStatusBar(QPainter &p); void drawTurnSignals(QPainter &p); - void initializeFrogPilotWidgets(); - void updateFrogPilotWidgets(QPainter &p); // FrogPilot variables Params paramsMemory{"/dev/shm/params"}; - UIScene &scene; Compass *compass_img; - PersonalityButton *personality_btn; + DistanceButton *distance_btn; + PedalIcons *pedal_icons; ScreenRecorder *recorder_btn; QHBoxLayout *bottom_layout; - bool accelerationPath; - bool adjacentPath; - bool alwaysOnLateral; + bool alwaysOnLateralActive; + bool bigMapOpen; bool blindSpotLeft; bool blindSpotRight; bool compass; - bool conditionalExperimental; bool experimentalMode; - bool hideSpeed; bool leadInfo; bool mapOpen; - bool muteDM; - bool onroadAdjustableProfiles; - bool reverseCruise; + bool onroadDistanceButton; bool roadNameUI; - bool showDriverCamera; + bool showAlwaysOnLateralStatusBar; + bool showConditionalExperimentalStatusBar; bool showSLCOffset; bool slcOverridden; + bool speedLimitController; + bool trafficModeActive; bool turnSignalLeft; bool turnSignalRight; - bool useSI; bool useViennaSLCSign; - double maxAcceleration; + bool vtscControllingCurve; + float cruiseAdjustment; + float distanceConversion; + float laneDetectionWidth; float laneWidthLeft; float laneWidthRight; - float slcOverriddenSpeed; - float slcSpeedLimit; float slcSpeedLimitOffset; - int bearingDeg; + float speedConversion; + + int alertSize; int cameraView; - int conditionalSpeed; - int conditionalSpeedLead; int conditionalStatus; + int currentHolidayTheme; int customColors; int customSignals; - int desiredFollow; int obstacleDistance; int obstacleDistanceStock; - int stoppedEquivalence; int totalFrames = 8; - QTimer *animationTimer; + + QString leadDistanceUnit; + QString leadSpeedUnit; + size_t animationFrameIndex; - inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); } - - std::unordered_map>>> themeConfiguration; + std::unordered_map>> themeConfiguration; + std::unordered_map>> holidayThemeConfiguration; std::vector signalImgVector; + QTimer *animationTimer; + + inline QColor blueColor(int alpha = 255) { return QColor(0, 150, 255, alpha); } + inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); } + protected: void paintGL() override; void initializeGL() override; void showEvent(QShowEvent *event) override; void updateFrameMat() override; void drawLaneLines(QPainter &painter, const UIState *s); - void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd); + void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd, const float v_ego); void drawHud(QPainter &p); void drawDriverState(QPainter &painter, const UIState *s); void paintEvent(QPaintEvent *event) override; @@ -270,6 +312,8 @@ private: // FrogPilot variables UIScene &scene; + Params params; + Params paramsMemory{"/dev/shm/params"}; QPoint timeoutPoint = QPoint(420, 69); QTimer clickTimer; diff --git a/selfdrive/ui/qt/python_helpers.py b/selfdrive/ui/qt/python_helpers.py new file mode 100644 index 0000000..88c3629 --- /dev/null +++ b/selfdrive/ui/qt/python_helpers.py @@ -0,0 +1,20 @@ +import os +from cffi import FFI + +import sip + +from openpilot.common.ffi_wrapper import suffix +from openpilot.common.basedir import BASEDIR + + +def get_ffi(): + lib = os.path.join(BASEDIR, "selfdrive", "ui", "qt", "libpython_helpers" + suffix()) + + ffi = FFI() + ffi.cdef("void set_main_window(void *w);") + return ffi, ffi.dlopen(lib) + + +def set_main_window(widget): + ffi, lib = get_ffi() + lib.set_main_window(ffi.cast('void*', sip.unwrapinstance(widget))) diff --git a/selfdrive/ui/qt/qt_window.cc b/selfdrive/ui/qt/qt_window.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/qt_window.h b/selfdrive/ui/qt/qt_window.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/setup/reset.cc b/selfdrive/ui/qt/setup/reset.cc old mode 100755 new mode 100644 index 7999dd6..c9e0525 --- a/selfdrive/ui/qt/setup/reset.cc +++ b/selfdrive/ui/qt/setup/reset.cc @@ -57,7 +57,7 @@ Reset::Reset(ResetMode mode, QWidget *parent) : QWidget(parent) { main_layout->addSpacing(60); - body = new QLabel(tr("Press confirm to erase all content and settings. Press cancel to resume boot.")); + body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot.")); body->setWordWrap(true); body->setStyleSheet("font-size: 80px; font-weight: light;"); main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft); @@ -97,11 +97,6 @@ Reset::Reset(ResetMode mode, QWidget *parent) : QWidget(parent) { body->setText(tr("Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device.")); } - // automatically start if we're just finishing up an ABL reset - if (mode == ResetMode::FORMAT) { - startReset(); - } - setStyleSheet(R"( * { font-family: Inter; @@ -129,8 +124,6 @@ int main(int argc, char *argv[]) { if (argc > 1) { if (strcmp(argv[1], "--recover") == 0) { mode = ResetMode::RECOVER; - } else if (strcmp(argv[1], "--format") == 0) { - mode = ResetMode::FORMAT; } } diff --git a/selfdrive/ui/qt/setup/reset.h b/selfdrive/ui/qt/setup/reset.h old mode 100755 new mode 100644 index 04a191d..8bf368e --- a/selfdrive/ui/qt/setup/reset.h +++ b/selfdrive/ui/qt/setup/reset.h @@ -5,7 +5,6 @@ enum ResetMode { USER_RESET, // user initiated a factory reset from openpilot RECOVER, // userdata is corrupt for some reason, give a chance to recover - FORMAT, // finish up an ABL factory reset }; class Reset : public QWidget { diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc old mode 100755 new mode 100644 index 67f1c13..08f4a0f --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -20,7 +20,7 @@ #include "selfdrive/ui/qt/widgets/input.h" const std::string USER_AGENT = "AGNOSSetup-"; -const QString DASHCAM_URL = "https://dashcam.comma.ai"; +const QString OPENPILOT_URL = "https://openpilot.comma.ai"; bool is_elf(char *fname) { FILE *fp = fopen(fname, "rb"); @@ -201,20 +201,7 @@ QWidget * Setup::network_setup() { QPushButton *cont = new QPushButton(); cont->setObjectName("navBtn"); cont->setProperty("primary", true); - QObject::connect(cont, &QPushButton::clicked, [=]() { - auto w = currentWidget(); - QTimer::singleShot(0, [=]() { - setCurrentWidget(downloading_widget); - }); - QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); - if (!url.isEmpty()) { - QTimer::singleShot(1000, this, [=]() { - download(url); - }); - } else { - setCurrentWidget(w); - } - }); + QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); blayout->addWidget(cont); // setup timer for testing internet connection @@ -229,11 +216,11 @@ QWidget * Setup::network_setup() { } repaint(); }); - request->sendRequest(DASHCAM_URL); + request->sendRequest(OPENPILOT_URL); QTimer *timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, [=]() { if (!request->active() && cont->isVisible()) { - request->sendRequest(DASHCAM_URL); + request->sendRequest(OPENPILOT_URL); } }); timer->start(1000); @@ -241,6 +228,106 @@ QWidget * Setup::network_setup() { return widget; } +QWidget * radio_button(QString title, QButtonGroup *group) { + QPushButton *btn = new QPushButton(title); + btn->setCheckable(true); + group->addButton(btn); + btn->setStyleSheet(R"( + QPushButton { + height: 230; + padding-left: 100px; + padding-right: 100px; + text-align: left; + font-size: 80px; + font-weight: 400; + border-radius: 10px; + background-color: #4F4F4F; + } + QPushButton:checked { + background-color: #465BEA; + } + )"); + + // checkmark icon + QPixmap pix(":/img_circled_check.svg"); + btn->setIcon(pix); + btn->setIconSize(QSize(0, 0)); + btn->setLayoutDirection(Qt::RightToLeft); + QObject::connect(btn, &QPushButton::toggled, [=](bool checked) { + btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0)); + }); + return btn; +} + +QWidget * Setup::software_selection() { + QWidget *widget = new QWidget(); + QVBoxLayout *main_layout = new QVBoxLayout(widget); + main_layout->setContentsMargins(55, 50, 55, 50); + main_layout->setSpacing(0); + + // title + QLabel *title = new QLabel(tr("Choose Software to Install")); + title->setStyleSheet("font-size: 90px; font-weight: 500;"); + main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); + + main_layout->addSpacing(50); + + // openpilot + custom radio buttons + QButtonGroup *group = new QButtonGroup(widget); + group->setExclusive(true); + + QWidget *openpilot = radio_button(tr("openpilot"), group); + main_layout->addWidget(openpilot); + + main_layout->addSpacing(30); + + QWidget *custom = radio_button(tr("Custom Software"), group); + main_layout->addWidget(custom); + + main_layout->addStretch(); + + // back + continue buttons + QHBoxLayout *blayout = new QHBoxLayout; + main_layout->addLayout(blayout); + blayout->setSpacing(50); + + QPushButton *back = new QPushButton(tr("Back")); + back->setObjectName("navBtn"); + QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); + blayout->addWidget(back); + + QPushButton *cont = new QPushButton(tr("Continue")); + cont->setObjectName("navBtn"); + cont->setEnabled(false); + cont->setProperty("primary", true); + blayout->addWidget(cont); + + QObject::connect(cont, &QPushButton::clicked, [=]() { + auto w = currentWidget(); + QTimer::singleShot(0, [=]() { + setCurrentWidget(downloading_widget); + }); + QString url = OPENPILOT_URL; + if (group->checkedButton() != openpilot) { + url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); + } + if (!url.isEmpty()) { + QTimer::singleShot(1000, this, [=]() { + download(url); + }); + } else { + setCurrentWidget(w); + } + }); + + connect(group, QOverload::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) { + btn->setChecked(true); + cont->setEnabled(true); + }); + + return widget; +} + QWidget * Setup::downloading() { QWidget *widget = new QWidget(); QVBoxLayout *main_layout = new QVBoxLayout(widget); @@ -326,6 +413,7 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) { addWidget(getting_started()); addWidget(network_setup()); + addWidget(software_selection()); downloading_widget = downloading(); addWidget(downloading_widget); diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h old mode 100755 new mode 100644 index 8c33acc..ee5cc85 --- a/selfdrive/ui/qt/setup/setup.h +++ b/selfdrive/ui/qt/setup/setup.h @@ -17,6 +17,7 @@ private: QWidget *low_voltage(); QWidget *getting_started(); QWidget *network_setup(); + QWidget *software_selection(); QWidget *downloading(); QWidget *download_failed(QLabel *url, QLabel *body); diff --git a/selfdrive/ui/qt/setup/updater.cc b/selfdrive/ui/qt/setup/updater.cc old mode 100755 new mode 100644 index 15c5332..ed47590 --- a/selfdrive/ui/qt/setup/updater.cc +++ b/selfdrive/ui/qt/setup/updater.cc @@ -54,12 +54,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid background-color: #3049F4; } )"); - - QObject::connect(connect, &QPushButton::clicked, [=]() { - countdownTimer->stop(); - setCurrentWidget(wifi); - }); - + QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate); hlayout->addWidget(install); } @@ -119,20 +114,6 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid addWidget(wifi); addWidget(progress); - // Initialize the countdown timer and value - countdownValue = 5; // 5 seconds countdown - countdownTimer = new QTimer(this); - countdownTimer->setInterval(1000); // 1 second interval - - // Connect the timer's timeout signal to update the countdown and button text - QObject::connect(countdownTimer, &QTimer::timeout, this, &Updater::updateCountdown); - - // Start the countdown - countdownTimer->start(); - - // Set initial button text - install->setText(tr("Install (5)")); - setStyleSheet(R"( * { color: white; @@ -163,16 +144,6 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid )"); } -void Updater::updateCountdown() { - countdownValue--; - if (countdownValue > 0) { - install->setText(tr("Install (%1)").arg(countdownValue)); - } else { - countdownTimer->stop(); - installUpdate(); // Assuming this is the method that starts the update - } -} - void Updater::installUpdate() { setCurrentWidget(progress); QObject::connect(&proc, &QProcess::readyReadStandardOutput, this, &Updater::readProgress); diff --git a/selfdrive/ui/qt/setup/updater.h b/selfdrive/ui/qt/setup/updater.h old mode 100755 new mode 100644 index ba9737f..ce46c0a --- a/selfdrive/ui/qt/setup/updater.h +++ b/selfdrive/ui/qt/setup/updater.h @@ -26,7 +26,4 @@ private: QProgressBar *bar; QPushButton *reboot; QWidget *prompt, *wifi, *progress; - QTimer *countdownTimer; - int countdownValue; - }; diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc old mode 100755 new mode 100644 index 55e9597..10178f1 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -43,16 +43,40 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed( isCPU = params.getBool("ShowCPU"); isGPU = params.getBool("ShowGPU"); + isIP = params.getBool("ShowIP"); + isMemoryUsage = params.getBool("ShowMemoryUsage"); isStorageLeft = params.getBool("ShowStorageLeft"); isStorageUsed = params.getBool("ShowStorageUsed"); - isNumericalTemp = params.getBool("NumericalTemp"); - isFahrenheit = params.getBool("Fahrenheit"); + holidayThemeConfiguration = { + {0, {"stock", {QColor(255, 255, 255)}}}, + {1, {"april_fools", {QColor(255, 165, 0)}}}, + {2, {"christmas", {QColor(0, 72, 255)}}}, + {3, {"cinco_de_mayo", {QColor(0, 104, 71)}}}, + {4, {"easter", {QColor(200, 150, 200)}}}, + {5, {"fourth_of_july", {QColor(0, 72, 255)}}}, + {6, {"halloween", {QColor(255, 0, 0)}}}, + {7, {"new_years_day", {QColor(23, 134, 68)}}}, + {8, {"st_patricks_day", {QColor(0, 128, 0)}}}, + {9, {"thanksgiving", {QColor(255, 0, 0)}}}, + {10, {"valentines_day", {QColor(23, 134, 68)}}}, + {11, {"world_frog_day", {QColor(23, 134, 68)}}}, + }; + + for (auto &[key, themeData] : holidayThemeConfiguration) { + QString &themeName = themeData.first; + QString base = themeName == "stock" ? "../assets/images" : QString("../frogpilot/assets/holiday_themes/%1/images").arg(themeName); + std::vector paths = {base + "/button_home.png", base + "/button_flag.png", base + "/button_settings.png"}; + + holiday_home_imgs[key] = loadPixmap(paths[0], home_btn.size()); + holiday_flag_imgs[key] = loadPixmap(paths[1], home_btn.size()); + holiday_settings_imgs[key] = loadPixmap(paths[2], settings_btn.size(), Qt::IgnoreAspectRatio); + } themeConfiguration = { {0, {"stock", {QColor(255, 255, 255)}}}, - {1, {"frog_theme", {QColor(0, 72, 255)}}}, + {1, {"frog_theme", {QColor(23, 134, 68)}}}, {2, {"tesla_theme", {QColor(0, 72, 255)}}}, {3, {"stalin_theme", {QColor(255, 0, 0)}}} }; @@ -70,7 +94,6 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed( home_img = home_imgs[scene.custom_icons]; flag_img = flag_imgs[scene.custom_icons]; settings_img = settings_imgs[scene.custom_icons]; - currentColors = themeConfiguration[scene.custom_colors].second; } @@ -78,35 +101,52 @@ void Sidebar::mousePressEvent(QMouseEvent *event) { // Declare the click boxes QRect cpuRect = {30, 496, 240, 126}; QRect memoryRect = {30, 654, 240, 126}; + QRect networkRect = {30, 196, 240, 126}; QRect tempRect = {30, 338, 240, 126}; static int showChip = 0; static int showMemory = 0; + static int showNetwork = 0; static int showTemp = 0; // Swap between the respective metrics upon tap if (cpuRect.contains(event->pos())) { showChip = (showChip + 1) % 3; + isCPU = (showChip == 1); isGPU = (showChip == 2); + params.putBoolNonBlocking("ShowCPU", isCPU); params.putBoolNonBlocking("ShowGPU", isGPU); + update(); } else if (memoryRect.contains(event->pos())) { showMemory = (showMemory + 1) % 4; + isMemoryUsage = (showMemory == 1); isStorageLeft = (showMemory == 2); isStorageUsed = (showMemory == 3); + params.putBoolNonBlocking("ShowMemoryUsage", isMemoryUsage); params.putBoolNonBlocking("ShowStorageLeft", isStorageLeft); params.putBoolNonBlocking("ShowStorageUsed", isStorageUsed); + + update(); + } else if (networkRect.contains(event->pos())) { + showNetwork = (showNetwork + 1) % 2; + isIP = (showNetwork == 1); + params.putBoolNonBlocking("ShowIP", isIP); + update(); } else if (tempRect.contains(event->pos())) { showTemp = (showTemp + 1) % 3; - isNumericalTemp = (showTemp != 0); - isFahrenheit = (showTemp == 2); - params.putBoolNonBlocking("Fahrenheit", isFahrenheit); - params.putBoolNonBlocking("NumericalTemp", isNumericalTemp); + + scene.fahrenheit = showTemp == 2; + scene.numerical_temp = showTemp != 0; + + params.putBoolNonBlocking("Fahrenheit", showTemp == 2); + params.putBoolNonBlocking("NumericalTemp", showTemp != 0); + update(); } else if (onroad && home_btn.contains(event->pos())) { flag_pressed = true; @@ -147,17 +187,24 @@ void Sidebar::updateState(const UIState &s) { setProperty("netStrength", strength > 0 ? strength + 1 : 0); // FrogPilot properties - home_img = home_imgs[scene.custom_icons]; - flag_img = flag_imgs[scene.custom_icons]; - settings_img = settings_imgs[scene.custom_icons]; - - currentColors = themeConfiguration[scene.custom_colors].second; + if (scene.current_holiday_theme != 0) { + home_img = holiday_home_imgs[scene.current_holiday_theme]; + flag_img = holiday_flag_imgs[scene.current_holiday_theme]; + settings_img = holiday_settings_imgs[scene.current_holiday_theme]; + currentColors = holidayThemeConfiguration[scene.current_holiday_theme].second; + } else { + home_img = home_imgs[scene.custom_icons]; + flag_img = flag_imgs[scene.custom_icons]; + settings_img = settings_imgs[scene.custom_icons]; + currentColors = themeConfiguration[scene.custom_colors].second; + } auto frogpilotDeviceState = sm["frogpilotDeviceState"].getFrogpilotDeviceState(); + bool isNumericalTemp = scene.numerical_temp; + int maxTempC = deviceState.getMaxTempC(); - QString max_temp = isFahrenheit ? QString::number(maxTempC * 9 / 5 + 32) + "°F" : QString::number(maxTempC) + "°C"; - QColor theme_color = currentColors[0]; + QString max_temp = scene.fahrenheit ? QString::number(maxTempC * 9 / 5 + 32) + "°F" : QString::number(maxTempC) + "°C"; // FrogPilot metrics if (isCPU || isGPU) { @@ -171,11 +218,11 @@ void Sidebar::updateState(const UIState &s) { QString metric = isGPU ? gpu : cpu; int usage = isGPU ? gpu_usage : cpu_usage; - ItemStatus cpuStatus = {{tr(isGPU ? "GPU" : "CPU"), metric}, theme_color}; + ItemStatus cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, currentColors[0]}; if (usage >= 85) { - cpuStatus = {{tr(isGPU ? "GPU" : "CPU"), metric}, danger_color}; + cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, danger_color}; } else if (usage >= 70) { - cpuStatus = {{tr(isGPU ? "GPU" : "CPU"), metric}, warning_color}; + cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, warning_color}; } setProperty("cpuStatus", QVariant::fromValue(cpuStatus)); } @@ -186,10 +233,10 @@ void Sidebar::updateState(const UIState &s) { int storage_used = frogpilotDeviceState.getUsedSpace(); QString memory = QString::number(memory_usage) + "%"; - QString storage = QString::number(isStorageLeft ? storage_left : storage_used) + " GB"; + QString storage = QString::number(isStorageLeft ? storage_left : storage_used) + tr(" GB"); if (isMemoryUsage) { - ItemStatus memoryStatus = {{tr("MEMORY"), memory}, theme_color}; + ItemStatus memoryStatus = {{tr("MEMORY"), memory}, currentColors[0]}; if (memory_usage >= 85) { memoryStatus = {{tr("MEMORY"), memory}, danger_color}; } else if (memory_usage >= 70) { @@ -197,11 +244,11 @@ void Sidebar::updateState(const UIState &s) { } setProperty("memoryStatus", QVariant::fromValue(memoryStatus)); } else { - ItemStatus storageStatus = {{tr(isStorageLeft ? "LEFT" : "USED"), storage}, theme_color}; - if (10 <= storage_left && storage_left < 25) { - storageStatus = {{tr(isStorageLeft ? "LEFT" : "USED"), storage}, warning_color}; - } else if (storage_left < 10) { - storageStatus = {{tr(isStorageLeft ? "LEFT" : "USED"), storage}, danger_color}; + ItemStatus storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, currentColors[0]}; + if (25 > storage_left && storage_left >= 10) { + storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, warning_color}; + } else if (10 > storage_left) { + storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, danger_color}; } setProperty("storageStatus", QVariant::fromValue(storageStatus)); } @@ -213,7 +260,7 @@ void Sidebar::updateState(const UIState &s) { connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color}; } else { connectStatus = nanos_since_boot() - last_ping < 80e9 - ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, theme_color} + ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, currentColors[0]} : ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color}; } setProperty("connectStatus", QVariant::fromValue(connectStatus)); @@ -221,13 +268,13 @@ void Sidebar::updateState(const UIState &s) { ItemStatus tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("HIGH")}, danger_color}; auto ts = deviceState.getThermalStatus(); if (ts == cereal::DeviceState::ThermalStatus::GREEN) { - tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("GOOD")}, theme_color}; + tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("GOOD")}, currentColors[0]}; } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("OK")}, warning_color}; } setProperty("tempStatus", QVariant::fromValue(tempStatus)); - ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, theme_color}; + ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, currentColors[0]}; if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color}; } else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) { @@ -253,14 +300,24 @@ void Sidebar::paintEvent(QPaintEvent *event) { // network int x = 58; const QColor gray(0x54, 0x54, 0x54); - for (int i = 0; i < 5; ++i) { - p.setBrush(i < net_strength ? Qt::white : gray); - p.drawEllipse(x, 196, 27, 27); - x += 37; + p.setFont(InterFont(35)); + + if (isIP) { + p.setPen(QColor(0xff, 0xff, 0xff)); + p.save(); + p.setFont(InterFont(30)); + QRect ipBox = QRect(50, 196, 225, 27); + p.drawText(ipBox, Qt::AlignLeft | Qt::AlignVCenter, uiState()->wifi->getIp4Address()); + p.restore(); + } else { + for (int i = 0; i < 5; ++i) { + p.setBrush(i < net_strength ? Qt::white : gray); + p.drawEllipse(x, 196, 27, 27); + x += 37; + } + p.setPen(QColor(0xff, 0xff, 0xff)); } - p.setFont(InterFont(35)); - p.setPen(QColor(0xff, 0xff, 0xff)); const QRect r = QRect(50, 247, 100, 50); p.drawText(r, Qt::AlignCenter, net_type); diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h old mode 100755 new mode 100644 index e3a4d55..4aae071 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -71,17 +71,22 @@ private: ItemStatus cpu_status, memory_status, storage_status; + bool isCPU; + bool isGPU; + bool isIP; + bool isMemoryUsage; + bool isStorageLeft; + bool isStorageUsed; + std::unordered_map>> themeConfiguration; std::unordered_map flag_imgs; std::unordered_map home_imgs; std::unordered_map settings_imgs; - std::vector currentColors; - bool isCPU; - bool isFahrenheit; - bool isGPU; - bool isMemoryUsage; - bool isNumericalTemp; - bool isStorageLeft; - bool isStorageUsed; + std::unordered_map>> holidayThemeConfiguration; + std::unordered_map holiday_flag_imgs; + std::unordered_map holiday_home_imgs; + std::unordered_map holiday_settings_imgs; + + std::vector currentColors; }; diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc old mode 100755 new mode 100644 index efb44f5..7ca2f1a --- a/selfdrive/ui/qt/spinner.cc +++ b/selfdrive/ui/qt/spinner.cc @@ -11,7 +11,7 @@ #include #include -#include "system/hardware/hw.h" +#include "system/hardware/hw.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" @@ -88,7 +88,7 @@ Spinner::Spinner(QWidget *parent) : QWidget(parent) { } QProgressBar::chunk { border-radius: 10px; - background-color: rgba(179, 0, 0, 255); + background-color: rgba(23, 134, 68, 255); } )"); diff --git a/selfdrive/ui/qt/spinner.h b/selfdrive/ui/qt/spinner.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc old mode 100755 new mode 100644 index 4143fee..21ec5ee --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -4,32 +4,11 @@ #include #include #include -#include #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/network/wifi_manager.h" - -// wifi connection screen -// wifi = new QWidget; -// { -// QVBoxLayout *layout = new QVBoxLayout(wifi); -// layout->setContentsMargins(100, 100, 100, 100); - -// Networking *networking = new Networking(this, false); -// networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }"); -// layout->addWidget(networking, 1); - -// QPushButton *back = new QPushButton(tr("Back")); -// back->setObjectName("navBtn"); -// back->setStyleSheet("padding-left: 60px; padding-right: 60px;"); -// QObject::connect(back, &QPushButton::clicked, [=]() { -// setCurrentWidget(prompt); -// }); -// layout->addWidget(back, 0, Qt::AlignLeft); -// } int main(int argc, char *argv[]) { initApp(argc, argv); @@ -52,36 +31,11 @@ int main(int argc, char *argv[]) { scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum()); }); - - QPushButton *btnupdate = new QPushButton(); -#ifdef __aarch64__ - btnupdate->setText(QObject::tr("Update")); - QObject::connect(btnupdate, &QPushButton::clicked, [=]() { - QProcess process; - label->setText("Attempting to connect to wifi"); - // TODO: make this work, then copy the compiled binary into git - // wifi = new WifiManager(null); - // connect(wifi, &WifiManager::refreshSignal, [=]() { - // label->setText("Performing update"); - // process.setWorkingDirectory("/data/openpilot/"); - // process.start("/bin/bash", QStringList{"-c", "update.sh"}); - // process.waitForFinished(); - // label->setText("Rebooting"); - // Hardware::reboot(); - // }); - // wifi->start(); - }); -#else - btnupdate->setText(QObject::tr("Exit")); - QObject::connect(btnupdate, &QPushButton::clicked, &a, &QApplication::quit); -#endif - main_layout->addWidget(btnupdate, 0, 0, Qt::AlignLeft | Qt::AlignBottom); - QPushButton *btn = new QPushButton(); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); QObject::connect(btn, &QPushButton::clicked, [=]() { - Hardware::reboot(); // bbot this is the dreaded crash reboot button + Hardware::reboot(); }); #else btn->setText(QObject::tr("Exit")); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc old mode 100755 new mode 100644 index e18f92a..a83d353 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -25,7 +26,7 @@ QString getVersion() { } QString getBrand() { - return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("OscarPilot"); + return QObject::tr("FrogPilot"); } QString getUserAgent() { @@ -114,6 +115,11 @@ void initApp(int argc, char *argv[], bool disable_hidpi) { qputenv("QT_DBL_CLICK_DIST", QByteArray::number(150)); + // ensure the current dir matches the exectuable's directory + QApplication tmp(argc, argv); + QString appDir = QCoreApplication::applicationDirPath(); + QDir::setCurrent(appDir); + setQtSurfaceFormat(); } diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc old mode 100755 new mode 100644 index d7d52f9..7818da8 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -41,6 +41,8 @@ const char frame_fragment_shader[] = "out vec4 colorOut;\n" "void main() {\n" " colorOut = texture(uTexture, vTexCoord);\n" + // gamma to improve worst case visibility when dark + " colorOut.rgb = pow(colorOut.rgb, vec3(1.0/1.28));\n" "}\n"; #else #ifdef __APPLE__ @@ -217,7 +219,6 @@ void CameraWidget::updateFrameMat() { if (active_stream_type == VISION_STREAM_DRIVER) { if (stream_width > 0 && stream_height > 0) { frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); - frame_mat.v[0] *= -1.0; } } else { // Project point at "infinity" to compute x and y offsets diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h old mode 100755 new mode 100644 index 2468afa..4a2c7f3 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -127,15 +127,15 @@ public: QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControl::toggleFlipped); } - void setVisualOn() { - toggle.togglePosition(); - } - void setEnabled(bool enabled) { toggle.setEnabled(enabled); toggle.update(); } + void refresh() { + toggle.togglePosition(); + } + signals: void toggleFlipped(bool state); @@ -206,7 +206,7 @@ public: background-color: #4a4a4a; } QPushButton:checked:enabled { - background-color: #0048FF; + background-color: #33Ab4C; } QPushButton:disabled { color: #33E4E4E4; @@ -227,10 +227,8 @@ public: button_group->addButton(button, i); } - QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) { - if (checked) { - params.put(key, std::to_string(id)); - } + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { + params.put(key, std::to_string(id)); }); } @@ -240,6 +238,19 @@ public: } } + void setCheckedButton(int id) { + button_group->button(id)->setChecked(true); + } + + void refresh() { + int value = atoi(params.get(key).c_str()); + button_group->button(value)->setChecked(true); + } + + void showEvent(QShowEvent *event) override { + refresh(); + } + private: std::string key; Params params; diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc old mode 100755 new mode 100644 index 31009f0..86070c8 --- a/selfdrive/ui/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -5,7 +5,6 @@ #include #include -#include "common/params.h" #include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/util.h" @@ -16,19 +15,19 @@ static QLabel* newLabel(const QString& text, const QString &type) { } DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { - metric_ = Params().getBool("IsMetric"); + metric_ = params.getBool("IsMetric"); QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(50, 50, 50, 60); + main_layout->setContentsMargins(50, 25, 50, 20); - auto add_stats_layouts = [=](const QString &title, StatsLabels& labels) { + auto add_stats_layouts = [=](const QString &title, StatsLabels& labels, bool FrogPilot=false) { QGridLayout* grid_layout = new QGridLayout; grid_layout->setVerticalSpacing(10); grid_layout->setContentsMargins(0, 10, 0, 10); int row = 0; - grid_layout->addWidget(newLabel(title, "title"), row++, 0, 1, 3); - grid_layout->addItem(new QSpacerItem(0, 50), row++, 0, 1, 1); + grid_layout->addWidget(newLabel(title, FrogPilot ? "frogpilot_title" : "title"), row++, 0, 1, 3); + grid_layout->addItem(new QSpacerItem(0, 10), row++, 0, 1, 1); grid_layout->addWidget(labels.routes = newLabel("0", "number"), row, 0, Qt::AlignLeft); grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); @@ -39,11 +38,12 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft); main_layout->addLayout(grid_layout); + main_layout->addStretch(1); }; add_stats_layouts(tr("ALL TIME"), all_); - main_layout->addStretch(); add_stats_layouts(tr("PAST WEEK"), week_); + add_stats_layouts(tr("FROGPILOT"), frogPilot_, true); if (auto dongleId = getDongleId()) { QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; @@ -57,13 +57,25 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { border-radius: 10px; } - QLabel[type="title"] { font-size: 51px; font-weight: 500; } - QLabel[type="number"] { font-size: 78px; font-weight: 500; } - QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; } + QLabel[type="title"] { font-size: 50px; font-weight: 500; } + QLabel[type="frogpilot_title"] { font-size: 50px; font-weight: 500; color: #178643; } + QLabel[type="number"] { font-size: 65px; font-weight: 400; } + QLabel[type="unit"] { font-size: 50px; font-weight: 300; color: #A0A0A0; } )"); } void DriveStats::updateStats() { + QJsonObject json = stats_.object(); + + auto updateFrogPilot = [this](const QJsonObject& obj, StatsLabels& labels) { + labels.routes->setText(QString::number(paramsStorage.getInt("FrogPilotDrives"))); + labels.distance->setText(QString::number(int(paramsStorage.getFloat("FrogPilotKilometers") * (metric_ ? 1 : KM_TO_MILE)))); + labels.distance_unit->setText(getDistanceUnit()); + labels.hours->setText(QString::number(int(paramsStorage.getFloat("FrogPilotMinutes") / 60))); + }; + + updateFrogPilot(json["frogpilot"].toObject(), frogPilot_); + auto update = [=](const QJsonObject& obj, StatsLabels& labels) { labels.routes->setText(QString::number((int)obj["routes"].toDouble())); labels.distance->setText(QString::number(int(obj["distance"].toDouble() * (metric_ ? MILE_TO_KM : 1)))); @@ -71,7 +83,6 @@ void DriveStats::updateStats() { labels.hours->setText(QString::number((int)(obj["minutes"].toDouble() / 60))); }; - QJsonObject json = stats_.object(); update(json["all"].toObject(), all_); update(json["week"].toObject(), week_); } @@ -89,9 +100,6 @@ void DriveStats::parseResponse(const QString& response, bool success) { } void DriveStats::showEvent(QShowEvent* event) { - bool metric = Params().getBool("IsMetric"); - if (metric_ != metric) { - metric_ = metric; - updateStats(); - } + metric_ = params.getBool("IsMetric"); + updateStats(); } diff --git a/selfdrive/ui/qt/widgets/drive_stats.h b/selfdrive/ui/qt/widgets/drive_stats.h old mode 100755 new mode 100644 index 5e2d96b..fb2f9a1 --- a/selfdrive/ui/qt/widgets/drive_stats.h +++ b/selfdrive/ui/qt/widgets/drive_stats.h @@ -3,6 +3,8 @@ #include #include +#include "common/params.h" + class DriveStats : public QFrame { Q_OBJECT @@ -15,10 +17,12 @@ private: inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); } bool metric_; + Params params; + Params paramsStorage{"/persist/params"}; QJsonDocument stats_; struct StatsLabels { QLabel *routes, *distance, *distance_unit, *hours; - } all_, week_; + } all_, week_, frogPilot_; private slots: void parseResponse(const QString &response, bool success); diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/keyboard.cc b/selfdrive/ui/qt/widgets/keyboard.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/keyboard.h b/selfdrive/ui/qt/widgets/keyboard.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc old mode 100755 new mode 100644 index f9db008..f1cde09 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -37,13 +37,9 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent disable_check_btn->setFixedSize(625, 125); footer_layout->addWidget(disable_check_btn, 1, Qt::AlignBottom | Qt::AlignCenter); QObject::connect(disable_check_btn, &QPushButton::clicked, [=]() { - if (!params.getBool("FireTheBabysitter")) { - params.putBool("FireTheBabysitter", true); - } - if (!params.getBool("OfflineMode")) { - params.putBool("OfflineMode", true); - } - Hardware::reboot(); + params.putBool("SnoozeUpdate", true); + params.putBool("DeviceManagement", true); + params.putBool("OfflineMode", true); }); QObject::connect(disable_check_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); disable_check_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)"); diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.h b/selfdrive/ui/qt/widgets/offroad_alerts.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/scrollview.h b/selfdrive/ui/qt/widgets/scrollview.h old mode 100755 new mode 100644 index 1e67bbe..51acc4c --- a/selfdrive/ui/qt/widgets/scrollview.h +++ b/selfdrive/ui/qt/widgets/scrollview.h @@ -10,6 +10,7 @@ public: // FrogPilot functions void restorePosition(int previousScrollPosition); + protected: void hideEvent(QHideEvent *e) override; }; diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/toggle.cc b/selfdrive/ui/qt/widgets/toggle.cc old mode 100755 new mode 100644 index ffee7e5..82302ad --- a/selfdrive/ui/qt/widgets/toggle.cc +++ b/selfdrive/ui/qt/widgets/toggle.cc @@ -75,7 +75,7 @@ void Toggle::setEnabled(bool value) { enabled = value; if (value) { circleColor.setRgb(0xfafafa); - green.setRgb(0x0048FF); + green.setRgb(0x33ab4c); } else { circleColor.setRgb(0x888888); green.setRgb(0x227722); diff --git a/selfdrive/ui/qt/widgets/toggle.h b/selfdrive/ui/qt/widgets/toggle.h old mode 100755 new mode 100644 diff --git a/selfdrive/ui/qt/widgets/wifi.cc b/selfdrive/ui/qt/widgets/wifi.cc old mode 100755 new mode 100644 index 9c5289a..591c1ce --- a/selfdrive/ui/qt/widgets/wifi.cc +++ b/selfdrive/ui/qt/widgets/wifi.cc @@ -81,6 +81,34 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) { } stack->addWidget(uploading); + QWidget *notUploading = new QWidget; + QVBoxLayout *not_uploading_layout = new QVBoxLayout(notUploading); + not_uploading_layout->setContentsMargins(64, 56, 64, 56); + not_uploading_layout->setSpacing(36); + { + QHBoxLayout *title_layout = new QHBoxLayout; + { + QLabel *title = new QLabel(tr("Uploading disabled")); + title->setStyleSheet("font-size: 64px; font-weight: 600;"); + title->setWordWrap(true); + title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + title_layout->addWidget(title); + title_layout->addStretch(); + + QLabel *icon = new QLabel; + QPixmap pixmap("../frogpilot/assets/other_images/icon_wifi_uploading_disabled.svg"); + icon->setPixmap(pixmap.scaledToWidth(120, Qt::SmoothTransformation)); + title_layout->addWidget(icon); + } + not_uploading_layout->addLayout(title_layout); + + QLabel *desc = new QLabel(tr("Toggle off the 'Disable Uploading' toggle to enable uploads.")); + desc->setStyleSheet("font-size: 48px; font-weight: 400;"); + desc->setWordWrap(true); + not_uploading_layout->addWidget(desc); + } + stack->addWidget(notUploading); + setStyleSheet(R"( WiFiPromptWidget { background-color: #333333; @@ -99,5 +127,6 @@ void WiFiPromptWidget::updateState(const UIState &s) { auto network_type = sm["deviceState"].getDeviceState().getNetworkType(); auto uploading = network_type == cereal::DeviceState::NetworkType::WIFI || network_type == cereal::DeviceState::NetworkType::ETHERNET; - stack->setCurrentIndex(uploading ? 1 : 0); + bool uploading_disabled = params.getBool("DeviceManagement") && params.getBool("NoUploads"); + stack->setCurrentIndex(uploading_disabled ? 2 : uploading ? 1 : 0); } diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h old mode 100755 new mode 100644 index 60c865f..30278c1 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -18,6 +18,10 @@ signals: public slots: void updateState(const UIState &s); +private: + // FrogPilot variables + Params params; + protected: QStackedLayout *stack; }; diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc old mode 100755 new mode 100644 index 18e51c7..4e4bd36 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -13,14 +13,14 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings); QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings); - oscarSettingsWindow = new OscarSettingsWindow(this); - main_layout->addWidget(oscarSettingsWindow); - QObject::connect(oscarSettingsWindow, &OscarSettingsWindow::closeSettings, this, &MainWindow::closeSettings); - // QObject::connect(oscarSettingsWindow, &OscarSettingsWindow::reviewTrainingGuide, [=]() { - // onboardingWindow->showTrainingGuide(); - // main_layout->setCurrentWidget(onboardingWindow); - // }); - QObject::connect(oscarSettingsWindow, &OscarSettingsWindow::showDriverView, [=] { + settingsWindow = new SettingsWindow(this); + main_layout->addWidget(settingsWindow); + QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings); + QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() { + onboardingWindow->showTrainingGuide(); + main_layout->setCurrentWidget(onboardingWindow); + }); + QObject::connect(settingsWindow, &SettingsWindow::showDriverView, [=] { homeWindow->showDriverView(true); }); @@ -38,11 +38,11 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { closeSettings(); } }); - // QObject::connect(device(), &Device::interactiveTimeout, [=]() { - // if (main_layout->currentWidget() == oscarSettingsWindow) { - // closeSettings(); - // } - // }); + QObject::connect(device(), &Device::interactiveTimeout, [=]() { + if (main_layout->currentWidget() == settingsWindow) { + closeSettings(); + } + }); // load fonts QFontDatabase::addApplicationFont("../assets/fonts/Inter-Black.ttf"); @@ -66,8 +66,8 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { } void MainWindow::openSettings(int index, const QString ¶m) { - main_layout->setCurrentWidget(oscarSettingsWindow); - oscarSettingsWindow->setCurrentPanel(index, param); + main_layout->setCurrentWidget(settingsWindow); + settingsWindow->setCurrentPanel(index, param); } void MainWindow::closeSettings() { @@ -93,12 +93,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) { case QEvent::MouseMove: { // ignore events when device is awakened by resetInteractiveTimeout ignore = !device()->isAwake(); - // if (main_layout->currentWidget() == oscarSettingsWindow) { - // Not working... - // device()->resetInteractiveTimeout(60 * 5); // 5 minute timeout if looking at settings window - // } else { - device()->resetInteractiveTimeout(); // Default 30 seconds otherwise - // } + device()->resetInteractiveTimeout(uiState()->scene.screen_timeout, uiState()->scene.screen_timeout_onroad); break; } default: diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h old mode 100755 new mode 100644 index ed024ce..f1389c2 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -6,7 +6,6 @@ #include "selfdrive/ui/qt/home.h" #include "selfdrive/ui/qt/offroad/onboarding.h" #include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/oscarpilot/settings/settings.h" class MainWindow : public QWidget { Q_OBJECT @@ -21,7 +20,7 @@ private: QStackedLayout *main_layout; HomeWindow *homeWindow; - OscarSettingsWindow *oscarSettingsWindow; + SettingsWindow *settingsWindow; OnboardingWindow *onboardingWindow; // FrogPilot variables diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py old mode 100755 new mode 100644 index 8e17de7..7a9c612 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -1,10 +1,9 @@ import math import numpy as np +import os import time -import threading import wave -from typing import Dict, Optional, Tuple from cereal import car, messaging from openpilot.common.basedir import BASEDIR @@ -29,7 +28,7 @@ DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied AudibleAlert = car.CarControl.HUDControl.AudibleAlert -sound_list: Dict[int, Tuple[str, Optional[int], float]] = { +sound_list: dict[int, tuple[str, int | None, float]] = { # AudibleAlert, file name, play count (none for infinite) AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME), @@ -42,11 +41,21 @@ sound_list: Dict[int, Tuple[str, Optional[int], float]] = { AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), - AudibleAlert.firefox: ("firefox.wav", None, MAX_VOLUME), + # Random Events + AudibleAlert.angry: ("angry.wav", 1, MAX_VOLUME), + AudibleAlert.doc: ("doc.wav", 1, MAX_VOLUME), + AudibleAlert.fart: ("fart.wav", 1, MAX_VOLUME), + AudibleAlert.firefox: ("firefox.wav", 1, MAX_VOLUME), + AudibleAlert.nessie: ("nessie.wav", 1, MAX_VOLUME), + AudibleAlert.noice: ("noice.wav", 1, MAX_VOLUME), + AudibleAlert.uwu: ("uwu.wav", 1, MAX_VOLUME), + + # Other + AudibleAlert.goat: ("goat.wav", None, MAX_VOLUME), } def check_controls_timeout_alert(sm): - controls_missing = time.monotonic() - sm.rcv_time['controlsState'] + controls_missing = time.monotonic() - sm.recv_time['controlsState'] if controls_missing > CONTROLS_TIMEOUT: if sm['controlsState'].enabled and (controls_missing - CONTROLS_TIMEOUT) < 10: @@ -61,9 +70,20 @@ class Soundd: self.params = Params() self.params_memory = Params("/dev/shm/params") - self.update_frogpilot_params() + self.previous_sound_directory = None + self.random_events_directory = BASEDIR + "/selfdrive/frogpilot/assets/random_events/sounds/" - self.load_sounds() + self.random_events_map = { + AudibleAlert.angry: MAX_VOLUME, + AudibleAlert.doc: MAX_VOLUME, + AudibleAlert.fart: MAX_VOLUME, + AudibleAlert.firefox: MAX_VOLUME, + AudibleAlert.nessie: MAX_VOLUME, + AudibleAlert.noice: MAX_VOLUME, + AudibleAlert.uwu: MAX_VOLUME, + } + + self.update_frogpilot_params() self.current_alert = AudibleAlert.none self.current_volume = MIN_VOLUME @@ -74,13 +94,22 @@ class Soundd: self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) def load_sounds(self): - self.loaded_sounds: Dict[int, np.ndarray] = {} + self.loaded_sounds: dict[int, np.ndarray] = {} # Load all sounds for sound in sound_list: + if sound == AudibleAlert.goat and not self.goat_scream: + continue + filename, play_count, volume = sound_list[sound] - wavefile = wave.open(self.sound_directory + filename, 'r') + if sound in self.random_events_map: + wavefile = wave.open(self.random_events_directory + filename, 'r') + else: + try: + wavefile = wave.open(self.sound_directory + filename, 'r') + except FileNotFoundError: + wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r') assert wavefile.getnchannels() == 1 assert wavefile.getsampwidth() == 2 @@ -156,9 +185,15 @@ class Soundd: while True: sm.update(0) - if sm.updated['microphone'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert + if sm.updated['microphone'] and self.current_alert == AudibleAlert.none and not self.alert_volume_control: # only update volume filter when not playing alert self.spl_filter_weighted.update(sm["microphone"].soundPressureWeightedDb) - self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x)) if not self.silent_mode else 0 + self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x)) + + elif self.alert_volume_control and self.current_alert in self.volume_map: + self.current_volume = self.volume_map[self.current_alert] / 100.0 + + elif self.current_alert in self.random_events_map: + self.current_volume = self.random_events_map[self.current_alert] self.get_audible_alert(sm) @@ -166,28 +201,67 @@ class Soundd: assert stream.active - # Update FrogPilot parameters - if self.params_memory.get_bool("FrogPilotTogglesUpdated"): - updateFrogPilotParams = threading.Thread(target=self.update_frogpilot_params) - updateFrogPilotParams.start() + # Update FrogPilot parameters + if self.params_memory.get_bool("FrogPilotTogglesUpdated"): + self.update_frogpilot_params() def update_frogpilot_params(self): - self.silent_mode = self.params.get_bool("SilentMode") + self.alert_volume_control = self.params.get_bool("AlertVolumeControl") + + self.volume_map = { + AudibleAlert.engage: self.params.get_int("EngageVolume"), + AudibleAlert.disengage: self.params.get_int("DisengageVolume"), + AudibleAlert.refuse: self.params.get_int("RefuseVolume"), + + AudibleAlert.prompt: self.params.get_int("PromptVolume"), + AudibleAlert.promptRepeat: self.params.get_int("PromptVolume"), + AudibleAlert.promptDistracted: self.params.get_int("PromptDistractedVolume"), + + AudibleAlert.warningSoft: self.params.get_int("WarningSoftVolume"), + AudibleAlert.warningImmediate: self.params.get_int("WarningImmediateVolume"), + + AudibleAlert.goat: self.params.get_int("PromptVolume"), + } custom_theme = self.params.get_bool("CustomTheme") custom_sounds = self.params.get_int("CustomSounds") if custom_theme else 0 + self.goat_scream = custom_sounds == 1 and self.params.get_bool("GoatScream") theme_configuration = { - 0: "stock", 1: "frog_theme", 2: "tesla_theme", 3: "stalin_theme" } - theme_name = theme_configuration.get(custom_sounds, "stock") - self.sound_directory = (f"{BASEDIR}/selfdrive/frogpilot/assets/custom_themes/{theme_name}/sounds/" if custom_sounds else f"{BASEDIR}/selfdrive/assets/sounds/") + holiday_themes = custom_theme and self.params.get_bool("HolidayThemes") + current_holiday_theme = self.params_memory.get_int("CurrentHolidayTheme") if holiday_themes else 0 - self.load_sounds() + holiday_theme_configuration = { + 1: "april_fools", + 2: "christmas", + 3: "cinco_de_mayo", + 4: "easter", + 5: "fourth_of_july", + 6: "halloween", + 7: "new_years_day", + 8: "st_patricks_day", + 9: "thanksgiving", + 10: "valentines_day", + 11: "world_frog_day", + } + + if current_holiday_theme != 0: + theme_name = holiday_theme_configuration.get(current_holiday_theme) + self.sound_directory = BASEDIR + ("/selfdrive/frogpilot/assets/holiday_themes/" + theme_name + "/sounds/") + self.goat_scream = False + else: + theme_name = theme_configuration.get(custom_sounds) + self.sound_directory = BASEDIR + ("/selfdrive/frogpilot/assets/custom_themes/" + theme_name + "/sounds/" if custom_sounds != 0 else "/selfdrive/assets/sounds/") + + if self.sound_directory != self.previous_sound_directory: + self.load_sounds() + + self.previous_sound_directory = self.sound_directory def main(): s = Soundd() diff --git a/selfdrive/ui/spinner b/selfdrive/ui/spinner index 35feab3..965c8f5 100755 --- a/selfdrive/ui/spinner +++ b/selfdrive/ui/spinner @@ -1,7 +1,7 @@ #!/bin/sh -if [ -f /TICI ] && [ ! -f qt/spinner ]; then - cp qt/spinner_larch64 qt/spinner +if [ -f /TICI ] && [ ! -f _spinner ]; then + cp qt/spinner_larch64 _spinner fi -exec ./qt/spinner "$1" +exec ./_spinner "$1" diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore new file mode 100644 index 0000000..6c624b6 --- /dev/null +++ b/selfdrive/ui/tests/.gitignore @@ -0,0 +1,6 @@ +test +playsound +test_sound +test_translations +ui_snapshot +test_ui/report \ No newline at end of file diff --git a/selfdrive/ui/tests/__init__.py b/selfdrive/ui/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/selfdrive/ui/tests/body.py b/selfdrive/ui/tests/body.py new file mode 100644 index 0000000..c34e717 --- /dev/null +++ b/selfdrive/ui/tests/body.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import time +import cereal.messaging as messaging + +if __name__ == "__main__": + while True: + pm = messaging.PubMaster(['carParams', 'carState']) + batt = 1. + while True: + msg = messaging.new_message('carParams') + msg.carParams.carName = "COMMA BODY" + msg.carParams.notCar = True + pm.send('carParams', msg) + + for b in range(100, 0, -1): + msg = messaging.new_message('carState') + msg.carState.charging = True + msg.carState.fuelGauge = b / 100. + pm.send('carState', msg) + time.sleep(0.1) + + time.sleep(1) diff --git a/selfdrive/ui/tests/create_test_translations.sh b/selfdrive/ui/tests/create_test_translations.sh new file mode 100644 index 0000000..451a3cb --- /dev/null +++ b/selfdrive/ui/tests/create_test_translations.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +UI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"/.. +TEST_TEXT="(WRAPPED_SOURCE_TEXT)" +TEST_TS_FILE=$UI_DIR/translations/main_test_en.ts +TEST_QM_FILE=$UI_DIR/translations/main_test_en.qm + +# translation strings +UNFINISHED="<\/translation>" +TRANSLATED="$TEST_TEXT<\/translation>" + +mkdir -p $UI_DIR/translations +rm -f $TEST_TS_FILE $TEST_QM_FILE +lupdate -recursive "$UI_DIR" -ts $TEST_TS_FILE +sed -i "s/$UNFINISHED/$TRANSLATED/" $TEST_TS_FILE +lrelease $TEST_TS_FILE diff --git a/selfdrive/ui/tests/cycle_offroad_alerts.py b/selfdrive/ui/tests/cycle_offroad_alerts.py new file mode 100644 index 0000000..75b19ce --- /dev/null +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import os +import sys +import time +import json + +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert + +if __name__ == "__main__": + params = Params() + + with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) as f: + offroad_alerts = json.load(f) + + t = 10 if len(sys.argv) < 2 else int(sys.argv[1]) + while True: + print("setting alert update") + params.put_bool("UpdateAvailable", True) + r = open(os.path.join(BASEDIR, "RELEASES.md")).read() + r = r[:r.find('\n\n')] # Slice latest release notes + params.put("UpdaterNewReleaseNotes", r + "\n") + + time.sleep(t) + params.put_bool("UpdateAvailable", False) + + # cycle through normal alerts + for a in offroad_alerts: + print("setting alert:", a) + set_offroad_alert(a, True) + time.sleep(t) + set_offroad_alert(a, False) + + print("no alert") + time.sleep(t) diff --git a/selfdrive/ui/tests/playsound.cc b/selfdrive/ui/tests/playsound.cc new file mode 100644 index 0000000..6487d04 --- /dev/null +++ b/selfdrive/ui/tests/playsound.cc @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +int main(int argc, char **argv) { + + QApplication a(argc, argv); + + QTimer::singleShot(0, [=]{ + QSoundEffect s; + const char *vol = getenv("VOLUME"); + s.setVolume(vol ? atof(vol) : 1.0); + for (int i = 1; i < argc; i++) { + QString fn = argv[i]; + qDebug() << "playing" << fn; + + QEventLoop loop; + s.setSource(QUrl::fromLocalFile(fn)); + QEventLoop::connect(&s, &QSoundEffect::loadedChanged, &loop, &QEventLoop::quit); + loop.exec(); + s.play(); + QEventLoop::connect(&s, &QSoundEffect::playingChanged, &loop, &QEventLoop::quit); + loop.exec(); + } + QCoreApplication::exit(); + }); + + return a.exec(); +} diff --git a/selfdrive/ui/tests/test_runner.cc b/selfdrive/ui/tests/test_runner.cc new file mode 100644 index 0000000..ac63139 --- /dev/null +++ b/selfdrive/ui/tests/test_runner.cc @@ -0,0 +1,25 @@ +#define CATCH_CONFIG_RUNNER +#include "catch2/catch.hpp" + +#include +#include +#include +#include + +int main(int argc, char **argv) { + // unit tests for Qt + QApplication app(argc, argv); + + QString language_file = "main_test_en"; + qDebug() << "Loading language:" << language_file; + + QTranslator translator; + QString translationsPath = QDir::cleanPath(qApp->applicationDirPath() + "/../translations"); + if (!translator.load(language_file, translationsPath)) { + qDebug() << "Failed to load translation file!"; + } + app.installTranslator(&translator); + + const int res = Catch::Session().run(argc, argv); + return (res < 0xff ? res : 0xff); +} diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py new file mode 100644 index 0000000..94ce26e --- /dev/null +++ b/selfdrive/ui/tests/test_soundd.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import unittest + +from cereal import car +from cereal import messaging +from cereal.messaging import SubMaster, PubMaster +from openpilot.selfdrive.ui.soundd import CONTROLS_TIMEOUT, check_controls_timeout_alert + +import time + +AudibleAlert = car.CarControl.HUDControl.AudibleAlert + + +class TestSoundd(unittest.TestCase): + def test_check_controls_timeout_alert(self): + sm = SubMaster(['controlsState']) + pm = PubMaster(['controlsState']) + + for _ in range(100): + cs = messaging.new_message('controlsState') + cs.controlsState.enabled = True + + pm.send("controlsState", cs) + + time.sleep(0.01) + + sm.update(0) + + self.assertFalse(check_controls_timeout_alert(sm)) + + for _ in range(CONTROLS_TIMEOUT * 110): + sm.update(0) + time.sleep(0.01) + + self.assertTrue(check_controls_timeout_alert(sm)) + + # TODO: add test with micd for checking that soundd actually outputs sounds + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/ui/tests/test_translations.cc b/selfdrive/ui/tests/test_translations.cc new file mode 100644 index 0000000..fcefc57 --- /dev/null +++ b/selfdrive/ui/tests/test_translations.cc @@ -0,0 +1,48 @@ +#include "catch2/catch.hpp" + +#include "common/params.h" +#include "selfdrive/ui/qt/window.h" + +const QString TEST_TEXT = "(WRAPPED_SOURCE_TEXT)"; // what each string should be translated to +QRegExp RE_NUM("\\d*"); + +QStringList getParentWidgets(QWidget* widget){ + QStringList parentWidgets; + while (widget->parentWidget() != Q_NULLPTR) { + widget = widget->parentWidget(); + parentWidgets.append(widget->metaObject()->className()); + } + return parentWidgets; +} + +template +void checkWidgetTrWrap(MainWindow &w) { + for (auto widget : w.findChildren()) { + const QString text = widget->text(); + bool isNumber = RE_NUM.exactMatch(text); + bool wrapped = text.contains(TEST_TEXT); + QString parentWidgets = getParentWidgets(widget).join("->"); + + if (!text.isEmpty() && !isNumber && !wrapped) { + FAIL(("\"" + text + "\" must be wrapped. Parent widgets: " + parentWidgets).toStdString()); + } + + // warn if source string wrapped, but UI adds text + // TODO: add way to ignore this + if (wrapped && text != TEST_TEXT) { + WARN(("\"" + text + "\" is dynamic and needs a custom retranslate function. Parent widgets: " + parentWidgets).toStdString()); + } + } +} + +// Tests all strings in the UI are wrapped with tr() +TEST_CASE("UI: test all strings wrapped") { + Params().remove("LanguageSetting"); + Params().remove("HardwareSerial"); + Params().remove("DongleId"); + qputenv("TICI", "1"); + + MainWindow w; + checkWidgetTrWrap(w); + checkWidgetTrWrap(w); +} diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 9ba9054..8e50695 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -12,7 +12,7 @@ from parameterized import parameterized_class from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations -with open(LANGUAGES_FILE, "r") as f: +with open(LANGUAGES_FILE) as f: translation_files = json.load(f) UNFINISHED_TRANSLATION_TAG = " + + + +{% for name, (image, ref_image) in cases.items() %} + +

{{name}}

+
+
+ +
+
+ +
+ +{% endfor %} + \ No newline at end of file diff --git a/selfdrive/ui/tests/ui_snapshot.cc b/selfdrive/ui/tests/ui_snapshot.cc new file mode 100644 index 0000000..14e0fab --- /dev/null +++ b/selfdrive/ui/tests/ui_snapshot.cc @@ -0,0 +1,66 @@ +#include "selfdrive/ui/tests/ui_snapshot.h" + +#include +#include +#include +#include +#include + +#include "selfdrive/ui/qt/home.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/window.h" +#include "selfdrive/ui/ui.h" + +void saveWidgetAsImage(QWidget *widget, const QString &fileName) { + QImage image(widget->size(), QImage::Format_ARGB32); + QPainter painter(&image); + widget->render(&painter); + image.save(fileName); +} + +int main(int argc, char *argv[]) { + initApp(argc, argv); + + QApplication app(argc, argv); + + QCommandLineParser parser; + parser.setApplicationDescription("Take a snapshot of the UI."); + parser.addHelpOption(); + parser.addOption(QCommandLineOption(QStringList() << "o" + << "output", + "Output image file path. The file's suffix is used to " + "determine the format. Supports PNG and JPEG formats. " + "Defaults to \"snapshot.png\".", + "file", "snapshot.png")); + parser.process(app); + + const QString output = parser.value("output"); + if (output.isEmpty()) { + qCritical() << "No output file specified"; + return 1; + } + + auto current = QDir::current(); + + // change working directory to find assets + if (!QDir::setCurrent(QCoreApplication::applicationDirPath() + QDir::separator() + "..")) { + qCritical() << "Failed to set current directory"; + return 1; + } + + MainWindow w; + w.setFixedSize(2160, 1080); + w.show(); + app.installEventFilter(&w); + + // restore working directory + QDir::setCurrent(current.absolutePath()); + + // wait for the UI to update + QObject::connect(uiState(), &UIState::uiUpdate, [&](const UIState &s) { + saveWidgetAsImage(&w, output); + app.quit(); + }); + + return app.exec(); +} diff --git a/selfdrive/ui/tests/ui_snapshot.h b/selfdrive/ui/tests/ui_snapshot.h new file mode 100644 index 0000000..b4699f6 --- /dev/null +++ b/selfdrive/ui/tests/ui_snapshot.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void saveWidgetAsImage(QWidget *widget, const QString &fileName); diff --git a/selfdrive/ui/text b/selfdrive/ui/text index b44bec4..b12235f 100755 --- a/selfdrive/ui/text +++ b/selfdrive/ui/text @@ -1,7 +1,7 @@ #!/bin/sh -if [ -f /TICI ] && [ ! -f qt/text ]; then - cp qt/text_larch64 qt/text +if [ -f /TICI ] && [ ! -f _text ]; then + cp qt/text_larch64 _text fi -exec ./qt/text "$1" +exec ./_text "$1" diff --git a/selfdrive/ui/translations/README.md b/selfdrive/ui/translations/README.md new file mode 100644 index 0000000..4f11fe3 --- /dev/null +++ b/selfdrive/ui/translations/README.md @@ -0,0 +1,71 @@ +# Multilanguage + +[![languages](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge.svg)](#) + +## Contributing + +Before getting started, make sure you have set up the openpilot Ubuntu development environment by reading the [tools README.md](/tools/README.md). + +### Policy + +Most of the languages supported by openpilot come from and are maintained by the community via pull requests. A pull request likely to be merged is one that [fixes a translation or adds missing translations.](https://github.com/commaai/openpilot/blob/master/selfdrive/ui/translations/README.md#improving-an-existing-language) + +We also generally merge pull requests adding support for a new language if there are community members willing to maintain it. Maintaining a language is ensuring quality and completion of translations before each openpilot release. + +comma may remove or hide language support from releases depending on translation quality and completeness. + +### Adding a New Language + +openpilot provides a few tools to help contributors manage their translations and to ensure quality. To get started: + +1. Add your new language to [languages.json](/selfdrive/ui/translations/languages.json) with the appropriate [language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) and the localized language name (Traditional Chinese is `中文(繁體)`). +2. Generate the XML translation file (`*.ts`): + ```shell + selfdrive/ui/update_translations.py + ``` +3. Edit the translation file, marking each translation as completed: + ```shell + linguist selfdrive/ui/translations/your_language_file.ts + ``` +4. View your finished translations by compiling and starting the UI, then find it in the language selector: + ```shell + scons -j$(nproc) selfdrive/ui && selfdrive/ui/ui + ``` +5. Read [Checking the UI](#checking-the-ui) to double-check your translations fit in the UI. + +### Improving an Existing Language + +Follow step 3. above, you can review existing translations and add missing ones. Once you're done, just open a pull request to openpilot. + +### Checking the UI +Different languages use varying space to convey the same message, so it's a good idea to double-check that your translations do not overlap and fit into each widget. Start the UI (step 4. above) and view each page, making adjustments to translations as needed. + +#### To view offroad alerts: + +With the UI started, you can view the offroad alerts with: +```shell +selfdrive/ui/tests/cycle_offroad_alerts.py +``` + +### Updating the UI + +Any time you edit source code in the UI, you need to update the translations to ensure the line numbers and contexts are up to date (first step above). + +### Testing + +openpilot has a few unit tests to make sure all translations are up-to-date and that all strings are wrapped in a translation marker. They are run in CI, but you can also run them locally. + +Tests translation files up to date: + +```shell +selfdrive/ui/tests/test_translations.py +``` + +Tests all static source strings are wrapped: + +```shell +selfdrive/ui/tests/create_test_translations.sh && selfdrive/ui/tests/test_translations +``` + +--- +![multilanguage_onroad](https://user-images.githubusercontent.com/25857203/178912800-2c798af8-78e3-498e-9e19-35906e0bafff.png) diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py new file mode 100644 index 0000000..c2e4bbc --- /dev/null +++ b/selfdrive/ui/translations/auto_translate.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os +import pathlib +import xml.etree.ElementTree as ET +from typing import cast + +import requests + +TRANSLATIONS_DIR = pathlib.Path(__file__).resolve().parent +TRANSLATIONS_LANGUAGES = TRANSLATIONS_DIR / "languages.json" + +OPENAI_MODEL = "gpt-4" +OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") +OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code). " + \ + "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly." + + +def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]: + files = {} + + with open(TRANSLATIONS_LANGUAGES) as fp: + language_dict = json.load(fp) + + for filename in language_dict.values(): + path = TRANSLATIONS_DIR / f"{filename}.ts" + language = path.stem.split("main_")[1] + + if languages is None or language in languages: + files[language] = path + + return files + + +def translate_phrase(text: str, language: str) -> str: + response = requests.post( + "https://api.openai.com/v1/chat/completions", + json={ + "model": OPENAI_MODEL, + "messages": [ + { + "role": "system", + "content": OPENAI_PROMPT.format(language=language), + }, + { + "role": "user", + "content": text, + }, + ], + "temperature": 0.8, + "max_tokens": 1024, + "top_p": 1, + }, + headers={ + "Authorization": f"Bearer {OPENAI_API_KEY}", + "Content-Type": "application/json", + }, + ) + + if 400 <= response.status_code < 600: + raise requests.HTTPError(f'Error {response.status_code}: {response.json()}', response=response) + + data = response.json() + + return cast(str, data["choices"][0]["message"]["content"]) + + +def translate_file(path: pathlib.Path, language: str, all_: bool) -> None: + tree = ET.parse(path) + + root = tree.getroot() + + for context in root.findall("./context"): + name = context.find("name") + if name is None: + raise ValueError("name not found") + + print(f"Context: {name.text}") + + for message in context.findall("./message"): + source = message.find("source") + translation = message.find("translation") + + if source is None or translation is None: + raise ValueError("source or translation not found") + + if not all_ and translation.attrib.get("type") != "unfinished": + continue + + llm_translation = translate_phrase(cast(str, source.text), language) + + print(f"Source: {source.text}\n" + + f"Current translation: {translation.text}\n" + + f"LLM translation: {llm_translation}") + + translation.text = llm_translation + + with path.open("w", encoding="utf-8") as fp: + fp.write('\n' + + '\n' + + ET.tostring(root, encoding="utf-8").decode()) + + +def main(): + arg_parser = argparse.ArgumentParser("Auto translate") + + group = arg_parser.add_mutually_exclusive_group(required=True) + group.add_argument("-a", "--all-files", action="store_true", help="Translate all files") + group.add_argument("-f", "--file", nargs="+", help="Translate the selected files. (Example: -f fr de)") + + arg_parser.add_argument("-t", "--all-translations", action="store_true", default=False, help="Translate all sections. (Default: only unfinished)") + + args = arg_parser.parse_args() + + if OPENAI_API_KEY is None: + print("OpenAI API key is missing. (Hint: use `export OPENAI_API_KEY=YOUR-KEY` before you run the script).\n" + + "If you don't have one go to: https://beta.openai.com/account/api-keys.") + exit(1) + + files = get_language_files(None if args.all_files else args.file) + + if args.file: + missing_files = set(args.file) - set(files) + if len(missing_files): + print(f"No language files found: {missing_files}") + exit(1) + + print(f"Translation mode: {'all' if args.all_translations else 'only unfinished'}. Files: {list(files)}") + + for lang, path in files.items(): + print(f"Translate {lang} ({path})") + translate_file(path, lang, args.all_translations) + + +if __name__ == "__main__": + main() diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py new file mode 100644 index 0000000..f56f894 --- /dev/null +++ b/selfdrive/ui/translations/create_badges.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import json +import os +import requests +import xml.etree.ElementTree as ET + +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.ui.tests.test_translations import UNFINISHED_TRANSLATION_TAG +from openpilot.selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR + +TRANSLATION_TAG = " 90 else "red" + + # Download badge + badge_label = f"LANGUAGE {name}" + badge_message = f"{percent_finished}% complete" + if unfinished_translations != 0: + badge_message += f" ({unfinished_translations} unfinished)" + + r = requests.get(f"{SHIELDS_URL}/{badge_label}-{badge_message}-{color}", timeout=10) + assert r.status_code == 200, "Error downloading badge" + content_svg = r.content.decode("utf-8") + + xml = ET.fromstring(content_svg) + assert "width" in xml.attrib + max_badge_width = max(max_badge_width, int(xml.attrib["width"])) + + # Make tag ids in each badge unique to combine them into one svg + for tag in ("r", "s"): + content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"') + content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{tag}{idx})"') + + badge_svg.extend([f'', content_svg, ""]) + + badge_svg.insert(0, '') + badge_svg.append("") + + with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: + badge_f.write("\n".join(badge_svg)) diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json old mode 100755 new mode 100644 diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts old mode 100755 new mode 100644 index 2032807..10c87d8 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -562,10 +562,6 @@ Exit إغلاق - - dashcam - dashcam - openpilot openpilot @@ -652,14 +648,14 @@ This may take up to a minute. يتم إعادة ضبط الجهاز... قد يستغرق الأمر حوالي الدقيقة. - - Press confirm to erase all content and settings. Press cancel to resume boot. - اضغط على تأكيد لمسح جميع المحتويات والإعدادات. اضغط على إلغاء لمتابعة التشغيل. - Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. غير قادر على تحميل جزء البيانات. قد يكون الجزء تالفاً. اضغط على تأكيد لمسح جهازك وإعادة ضبطه. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow @@ -766,6 +762,18 @@ This may take up to a minute. Select a language اختر لغة + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1095,10 +1103,6 @@ This may take up to a minute. Driving Personality شخصية القيادة - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - يوصى بالوضع القياسي. في الوضع الهجومي، سيتبع openpilot السيارات الرائدة بشكل أقرب، ويصبح أكثر هجومية في دواسات الوقود والمكابح. في وضعية الراحة يبقى openplot بعيداً لمسافة جيدة عن السيارة الرائدة. - 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> التي لا تكون جاهزة في وضع الراحة: @@ -1143,6 +1147,10 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. تمكين التحكم الطولي من openpilot (ألفا) للسماح بالوضع التجريبي. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts old mode 100755 new mode 100644 index 782d65b..cdcea24 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -557,10 +557,6 @@ Exit Verlassen - - dashcam - dashcam - openpilot openpilot @@ -634,12 +630,12 @@ - Press confirm to erase all content and settings. Press cancel to resume boot. + Resetting device... +This may take up to a minute. - Resetting device... -This may take up to a minute. + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. @@ -748,6 +744,18 @@ This may take up to a minute. Select a language Sprache wählen + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1097,10 +1105,6 @@ This may take up to a minute. 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 enabling openpilot longitudinal control alpha. - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - - End-to-End Longitudinal Control @@ -1129,6 +1133,10 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/translations/main_en.ts b/selfdrive/ui/translations/main_en.ts old mode 100755 new mode 100644 diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts old mode 100755 new mode 100644 index 219e935..b1c39db --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -68,23 +68,23 @@ Hidden Network - + Réseau Caché CONNECT - CONNECTER + CONNECTER Enter SSID - Entrer le SSID + Entrer le SSID Enter password - Entrer le mot de passe + Entrer le mot de passe for "%1" - pour "%1" + pour "%1" @@ -558,10 +558,6 @@ Exit Quitter - - dashcam - dashcam - openpilot openpilot @@ -624,10 +620,6 @@ Cela peut prendre jusqu'à une minute.
System Reset Réinitialisation du système - - Press confirm to erase all content and settings. Press cancel to resume boot. - Appuyez sur confirmer pour effacer tout le contenu et les paramètres. Appuyez sur annuler pour reprendre le démarrage. - Cancel Annuler @@ -644,6 +636,10 @@ Cela peut prendre jusqu'à une minute.
Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. Impossible de monter la partition data. La partition peut être corrompue. Appuyez sur confirmer pour effacer et réinitialiser votre appareil. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow @@ -750,6 +746,18 @@ Cela peut prendre jusqu'à une minute.
Select a language Choisir une langue + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1079,10 +1087,6 @@ Cela peut prendre jusqu'à une minute. Driving Personality Personnalité de conduite - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - Le mode standard est recommandé. En mode agressif, openpilot suivra de plus près les voitures de tête et sera plus agressif avec l'accélérateur et le frein. En mode détendu, openpilot restera plus éloigné des voitures de tête. - 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: Par défaut, openpilot conduit en <b>mode détente</b>. Le mode expérimental permet d'activer des <b>fonctionnalités alpha</b> qui ne sont pas prêtes pour le mode détente. Les fonctionnalités expérimentales sont listées ci-dessous : @@ -1127,6 +1131,10 @@ Cela peut prendre jusqu'à une minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. Activer le contrôle longitudinal d'openpilot (en alpha) pour autoriser le mode expérimental. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts old mode 100755 new mode 100644 index ba3ca8f..5360c35 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -556,10 +556,6 @@ Exit 閉じる - - dashcam - ドライブレコーダー - openpilot openpilot @@ -630,12 +626,12 @@ - Press confirm to erase all content and settings. Press cancel to resume boot. + Resetting device... +This may take up to a minute. - Resetting device... -This may take up to a minute. + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. @@ -744,6 +740,18 @@ This may take up to a minute. Select a language 言語を選択 + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1089,10 +1097,6 @@ This may take up to a minute. 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 enabling openpilot longitudinal control alpha. - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - - End-to-End Longitudinal Control @@ -1121,6 +1125,10 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts old mode 100755 new mode 100644 index 8c1a0fb..ed75191 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -68,23 +68,23 @@ Hidden Network - + 숨겨진 네트워크 CONNECT - 연결됨 + 연결됨 Enter SSID - SSID 입력 + SSID 입력 Enter password - 비밀번호를 입력하세요 + 비밀번호를 입력하세요 for "%1" - "%1"에 접속하려면 비밀번호가 필요합니다 + "%1"에 접속하려면 비밀번호가 필요합니다 @@ -557,10 +557,6 @@ Exit 종료 - - dashcam - 블랙박스 - openpilot openpilot @@ -630,16 +626,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. 데이터 파티션을 마운트할 수 없습니다. 파티션이 손상되었을 수 있습니다. 모든 설정을 삭제하고 장치를 초기화하려면 확인을 누르세요. - - Press confirm to erase all content and settings. Press cancel to resume boot. - 모든 콘텐츠와 설정을 삭제하려면 확인을 누르세요. 계속 부팅하려면 취소를 누르세요. - Resetting device... This may take up to a minute. 장치를 초기화하는 중... 최대 1분이 소요될 수 있습니다. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + 시스템 재설정이 시작되었습니다. 모든 콘텐츠와 설정을 지우려면 확인을 누르시고 부팅을 재개하려면 취소를 누르세요. + SettingsWindow @@ -746,6 +742,18 @@ This may take up to a minute. Select a language 언어를 선택하세요 + + Choose Software to Install + 설치할 소프트웨어 선택 + + + openpilot + openpilot + + + Custom Software + 커스텀 소프트웨어 + SetupWidget @@ -1095,10 +1103,6 @@ This may take up to a minute. Driving Personality 주행 모드 - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - 표준 모드를 권장합니다. 공격적 모드에서 openpilot은 앞 차량을 더 가까이 따라가며 적극적으로 가감속합니다. 편안한 모드에서 openpilot은 앞 차량을 더 멀리서 따라갑니다. - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. openpilot 가감속 제어 알파 버전은 비 릴리즈 브랜치에서 실험 모드와 함께 테스트할 수 있습니다. @@ -1123,6 +1127,10 @@ This may take up to a minute. 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. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. 주행 시각화는 저속으로 주행 시 도로를 향한 광각 카메라로 자동 전환되어 일부 곡선 경로를 더 잘 보여줍니다. 실험 모드 로고는 우측 상단에 표시됩니다. 내비게이션 목적지가 설정되고 주행 모델에 입력되면 지도의 주행 경로가 녹색으로 바뀝니다. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + 표준 모드를 권장합니다. 공격적 모드의 openpilot은 선두 차량을 더 가까이 따라가고 가감속제어를 사용하여 더욱 공격적으로 움직입니다. 편안한 모드의 openpilot은 선두 차량으로부터 더 멀리 떨어져 있습니다. 지원되는 차량에서는 스티어링 휠 거리 버튼을 사용하여 이러한 특성을 순환할 수 있습니다. + Updater diff --git a/selfdrive/ui/translations/main_nl.ts b/selfdrive/ui/translations/main_nl.ts old mode 100755 new mode 100644 diff --git a/selfdrive/ui/translations/main_pl.ts b/selfdrive/ui/translations/main_pl.ts old mode 100755 new mode 100644 diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts old mode 100755 new mode 100644 index f983c8f..c32aeac --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -68,23 +68,23 @@ Hidden Network - + Rede Oculta CONNECT - CONEXÃO + CONECTE Enter SSID - Insira SSID + Digite o SSID Enter password - Insira a senha + Insira a senha for "%1" - para "%1" + para "%1" @@ -558,10 +558,6 @@ Exit Sair - - dashcam - dashcam - openpilot openpilot @@ -634,16 +630,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. Não é possível montar a partição de dados. Partição corrompida. Confirme para apagar e redefinir o dispositivo. - - Press confirm to erase all content and settings. Press cancel to resume boot. - Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancelar para voltar. - Resetting device... This may take up to a minute. Redefinindo o dispositivo Isso pode levar até um minuto. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + Reinicialização do sistema acionada. Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancel para retomar a inicialização. + SettingsWindow @@ -750,6 +746,18 @@ Isso pode levar até um minuto. Select a language Selecione o Idioma + + Choose Software to Install + Escolha o Software a ser Instalado + + + openpilot + openpilot + + + Custom Software + Software Customizado + SetupWidget @@ -1099,10 +1107,6 @@ Isso pode levar até um minuto. Driving Personality Temperamento de Direção - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - Neutro é o recomendado. No modo disputa o openpilot seguirá o carro da frente mais de perto e será mais agressivo com a aceleração e frenagem. No modo calmo o openpilot se manterá mais longe do carro da frente. - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. Uma versão embrionária do controle longitudinal openpilot pode ser testada em conjunto com o modo Experimental, em branches que não sejam de produção. @@ -1127,6 +1131,10 @@ Isso pode levar até um minuto. 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. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. A visualização de condução fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será mostrado no canto superior direito. Quando um destino de navegação é definido e o modelo de condução o utiliza como entrada o caminho de condução no mapa fica verde. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + Neutro é o recomendado. No modo disputa o openpilot seguirá o carro da frente mais de perto e será mais agressivo com a aceleração e frenagem. No modo calmo o openpilot se manterá mais longe do carro da frente. Em carros compatíveis, você pode alternar esses temperamentos com o botão de distância do volante. + Updater diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts old mode 100755 new mode 100644 index 0d0bda4..25a8aae --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -557,10 +557,6 @@ Exit ปิด - - dashcam - กล้องติดรถยนต์ - openpilot openpilot @@ -632,14 +628,14 @@ This may take up to a minute. กำลังรีเซ็ตอุปกรณ์... อาจใช้เวลาถึงหนึ่งนาที - - Press confirm to erase all content and settings. Press cancel to resume boot. - กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตต่อ - Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. ไม่สามารถเมานต์พาร์ติชั่นข้อมูลได้ พาร์ติชั่นอาจเสียหาย กดยืนยันเพื่อลบและรีเซ็ตอุปกรณ์ของคุณ + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow @@ -746,6 +742,18 @@ This may take up to a minute. Select a language เลือกภาษา + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1095,10 +1103,6 @@ This may take up to a minute. Driving Personality บุคลิกการขับขี่ - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - แนะนำให้ใช้แบบมาตรฐาน ในโหมดดุดัน openpilot จะตามรถคันหน้าใกล้ขึ้นและเร่งและเบรคแบบดุดันมากขึ้น ในโหมดผ่อนคลาย openpilot จะอยู่ห่างจากรถคันหน้ามากขึ้น - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. ระบบควบคุมการเร่ง/เบรคโดย openpilot เวอร์ชัน alpha สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา @@ -1123,6 +1127,10 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. เปิดระบบควบคุมการเร่ง/เบรคโดย openpilot (alpha) เพื่อเปิดใช้งานโหมดทดลอง + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts old mode 100755 new mode 100644 index d14dd51..dfe4b36 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -556,10 +556,6 @@ Exit Çık - - dashcam - araç yol kamerası - openpilot openpilot @@ -631,11 +627,11 @@ This may take up to a minute. - Press confirm to erase all content and settings. Press cancel to resume boot. + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. - Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. @@ -744,6 +740,18 @@ This may take up to a minute. Select a language Dil seçin + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1073,10 +1081,6 @@ This may take up to a minute. 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 enabling openpilot longitudinal control alpha. - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - - 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: @@ -1121,6 +1125,10 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts old mode 100755 new mode 100644 index ef0d784..d985945 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -68,23 +68,23 @@ Hidden Network - + 隐藏的网络 CONNECT - CONNECT + 连线 Enter SSID - 输入SSID + 输入 SSID Enter password - 输入密码 + 输入密码 for "%1" - 网络名称:"%1" + 网络名称:"%1" @@ -557,10 +557,6 @@ Exit 退出 - - dashcam - 行车记录仪 - openpilot openpilot @@ -630,16 +626,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. 无法挂载数据分区。分区可能已经损坏。请确认是否要删除并重新设置。 - - Press confirm to erase all content and settings. Press cancel to resume boot. - 按下确认以删除所有内容及设置。按下取消来继续开机。 - Resetting device... This may take up to a minute. 设备重置中… 这可能需要一分钟的时间。 + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + 系统重置已触发。按下“确认”以清除所有内容和设置,按下“取消”以继续启动。 + SettingsWindow @@ -746,6 +742,18 @@ This may take up to a minute. Select a language 选择语言 + + Choose Software to Install + 选择要安装的软件 + + + openpilot + openpilot + + + Custom Software + 定制软件 + SetupWidget @@ -1095,10 +1103,6 @@ This may take up to a minute. Driving Personality 驾驶风格 - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - 推荐使用标准模式。在积极模式中,openpilot 会更靠近前车并在加速和刹车方面更积极。在舒适模式中,openpilot 会与前车保持较远的距离。 - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. 在正式(release)版本以外的分支上,可以测试 openpilot 纵向控制的 Alpha 版本以及实验模式。 @@ -1123,6 +1127,10 @@ This may take up to a minute. 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. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. 行驶画面将在低速时切换到道路朝向的广角摄像头,以更好地显示一些转弯。实验模式标志也将显示在右上角。当设置了导航目的地并且驾驶模型正在使用它作为输入时,地图上的驾驶路径将变为绿色。 + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts old mode 100755 new mode 100644 index 121bf58..e64134f --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -68,23 +68,23 @@ Hidden Network - + 隱藏的網路 CONNECT - 雲端服務 + 連線 Enter SSID - 輸入 SSID + 輸入 SSID Enter password - 輸入密碼 + 輸入密碼 for "%1" - 給 "%1" + 給 "%1" @@ -557,10 +557,6 @@ Exit 離開 - - dashcam - 行車記錄器 - openpilot openpilot @@ -630,16 +626,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. 無法掛載資料分割區。分割區可能已經毀損。請確認是否要刪除並重新設定。 - - Press confirm to erase all content and settings. Press cancel to resume boot. - 按下確認以刪除所有內容及設定。按下取消來繼續開機。 - Resetting device... This may take up to a minute. 設備重設中… 這可能需要一分鐘的時間。 + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + 系統重設已啟動。按下「確認」以清除所有內容和設定,或按下「取消」以繼續開機。 + SettingsWindow @@ -746,6 +742,18 @@ This may take up to a minute. Select a language 選擇語言 + + Choose Software to Install + 選擇要安裝的軟體 + + + openpilot + openpilot + + + Custom Software + 自訂軟體 + SetupWidget @@ -1095,10 +1103,6 @@ This may take up to a minute. Driving Personality 駕駛風格 - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - 推薦使用標準模式。在積極模式中,openpilot 會更靠近前車並在加速和剎車方面更積極。在舒適模式中,openpilot 會與前車保持較遠的距離。 - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. 在正式 (release) 版以外的分支上可以測試 openpilot 縱向控制的 Alpha 版本以及實驗模式。 @@ -1123,6 +1127,10 @@ This may take up to a minute. 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. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. 行駛畫面將在低速時切換至道路朝向的廣角鏡頭,以更好地顯示一些轉彎。實驗模式圖示也將顯示在右上角。當設定了導航目的地並且行駛模型正在將其作為輸入時,地圖上的行駛路徑將變為綠色。 + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + Updater diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc old mode 100755 new mode 100644 index ed95e9d..d5688c4 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -13,8 +13,6 @@ #include "common/watchdog.h" #include "system/hardware/hw.h" -#include "selfdrive/frogpilot/ui/frogpilot_functions.h" - #define BACKLIGHT_DT 0.05 #define BACKLIGHT_TS 10.00 @@ -46,12 +44,15 @@ int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_h return max_idx; } -void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line) { - for (int i = 0; i < 2; ++i) { - auto lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo(); - if (lead_data.getStatus()) { - float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())]; - calib_frame_to_full_frame(s, lead_data.getDRel(), -lead_data.getYRel(), z + 1.22, &s->scene.lead_vertices[i]); +void update_leads(UIState *s, const cereal::ModelDataV2::Reader &model_data) { + const cereal::XYZTData::Reader &line = model_data.getPosition(); + for (int i = 0; i < model_data.getLeadsV3().size() && i < 2; ++i) { + const auto &lead = model_data.getLeadsV3()[i]; + if (lead.getProb() > 0.5) { + float d_rel = lead.getX()[0]; + float y_rel = lead.getY()[0]; + float z = line.getZ()[get_path_length_idx(line, d_rel)]; + calib_frame_to_full_frame(s, d_rel, y_rel, z + 1.22, &s->scene.lead_vertices[i]); } } } @@ -99,7 +100,7 @@ void update_model(UIState *s, int max_idx = get_path_length_idx(lane_lines[0], max_distance); for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { scene.lane_line_probs[i] = lane_line_probs[i]; - update_line_data(s, lane_lines[i], scene.model_ui ? scene.lane_line_width * scene.lane_line_probs[i] : 0.025 * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx); + update_line_data(s, lane_lines[i], (scene.model_ui ? scene.lane_line_width : 0.025) * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx); } // update road edges @@ -111,20 +112,31 @@ void update_model(UIState *s, } // update path - auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne(); - if (lead_one.getStatus()) { - const float lead_d = lead_one.getDRel() * 2.; - max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); + float path; + if (scene.dynamic_path_width) { + float multiplier = scene.enabled ? 1.0f : scene.always_on_lateral_active ? 0.75f : 0.50f; + path = scene.path_width * multiplier; + } else { + path = scene.path_width; + } + + auto lead_count = model.getLeadsV3().size(); + if (lead_count > 0) { + auto lead_one = model.getLeadsV3()[0]; + if (lead_one.getProb() > 0.5) { + const float lead_d = lead_one.getX()[0] * 2.; + max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); + } } max_idx = get_path_length_idx(plan_position, max_distance); - update_line_data(s, plan_position, scene.model_ui ? scene.path_width * (1 - scene.path_edge_width / 100) : 0.9, 1.22, &scene.track_vertices, max_idx, false); + update_line_data(s, plan_position, scene.model_ui ? path * (1 - scene.path_edge_width / 100) : 0.9, 1.22, &scene.track_vertices, max_idx, false); - // update path edges - update_line_data(s, plan_position, scene.model_ui ? scene.path_width : 0, 1.22, &scene.track_edge_vertices, max_idx, false); + // Update path edges + update_line_data(s, plan_position, scene.model_ui ? path : 0, 1.22, &scene.track_edge_vertices, max_idx, false); - // update adjacent paths + // Update adjacent paths for (int i = 4; i <= 5; i++) { - update_line_data(s, lane_lines[i], scene.blind_spot_path ? (i == 4 ? scene.lane_width_left : scene.lane_width_right) / 2 : 0, 0, &scene.track_adjacent_vertices[i], max_idx); + update_line_data(s, lane_lines[i], (i == 4 ? scene.lane_width_left : scene.lane_width_right) / 2.0f, 0, &scene.track_adjacent_vertices[i], max_idx); } } @@ -159,6 +171,8 @@ void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &drivers kpt_this = matvecmul3(r_xyz, kpt_this); scene.face_kpts_draw[kpi] = (vec3){{(float)kpt_this.v[0], (float)kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; } + + scene.right_hand_drive = is_rhd; } static void update_sockets(UIState *s) { @@ -211,67 +225,69 @@ static void update_state(UIState *s) { } if (sm.updated("carParams")) { scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); + ui_update_frogpilot_params(s); } if (sm.updated("carState")) { auto carState = sm["carState"].getCarState(); - if (scene.blind_spot_path || scene.custom_signals) { - scene.blind_spot_left = carState.getLeftBlindspot(); - scene.blind_spot_right = carState.getRightBlindspot(); - } - if (scene.custom_signals) { - scene.turn_signal_left = carState.getLeftBlinker(); - scene.turn_signal_right = carState.getRightBlinker(); - } - if (scene.rotating_wheel) { - scene.steering_angle_deg = carState.getSteeringAngleDeg(); - } - if (scene.driver_camera) { - scene.show_driver_camera = carState.getGearShifter() == cereal::CarState::GearShifter::REVERSE; - } + scene.acceleration = carState.getAEgo(); + scene.blind_spot_left = carState.getLeftBlindspot(); + scene.blind_spot_right = carState.getRightBlindspot(); + scene.parked = carState.getGearShifter() == cereal::CarState::GearShifter::PARK; + scene.reverse = carState.getGearShifter() == cereal::CarState::GearShifter::REVERSE; + scene.standstill = carState.getStandstill(); + scene.steering_angle_deg = carState.getSteeringAngleDeg(); + scene.turn_signal_left = carState.getLeftBlinker(); + scene.turn_signal_right = carState.getRightBlinker(); } if (sm.updated("controlsState")) { auto controlsState = sm["controlsState"].getControlsState(); + scene.alert_size = controlsState.getAlertSize() == cereal::ControlsState::AlertSize::MID ? 350 : controlsState.getAlertSize() == cereal::ControlsState::AlertSize::SMALL ? 200 : 0; scene.enabled = controlsState.getEnabled(); scene.experimental_mode = controlsState.getExperimentalMode(); } + if (sm.updated("deviceState")) { + auto deviceState = sm["deviceState"].getDeviceState(); + scene.online = deviceState.getNetworkType() != cereal::DeviceState::NetworkType::NONE; + } if (sm.updated("frogpilotCarControl")) { auto frogpilotCarControl = sm["frogpilotCarControl"].getFrogpilotCarControl(); - if (scene.always_on_lateral) { - scene.always_on_lateral_active = !scene.enabled && frogpilotCarControl.getAlwaysOnLateral(); - } + scene.always_on_lateral_active = !scene.enabled && frogpilotCarControl.getAlwaysOnLateral(); + scene.speed_limit_changed = scene.speed_limit_controller && frogpilotCarControl.getSpeedLimitChanged(); + scene.traffic_mode_active = frogpilotCarControl.getTrafficModeActive(); } - if (sm.updated("frogpilotLateralPlan")) { - auto frogpilotLateralPlan = sm["frogpilotLateralPlan"].getFrogpilotLateralPlan(); - if (scene.blind_spot_path) { - scene.lane_width_left = frogpilotLateralPlan.getLaneWidthLeft(); - scene.lane_width_right = frogpilotLateralPlan.getLaneWidthRight(); - } - } - if (sm.updated("frogpilotLongitudinalPlan")) { - auto frogpilotLongitudinalPlan = sm["frogpilotLongitudinalPlan"].getFrogpilotLongitudinalPlan(); - if (scene.lead_info) { - scene.desired_follow = frogpilotLongitudinalPlan.getDesiredFollowDistance(); - scene.obstacle_distance = frogpilotLongitudinalPlan.getSafeObstacleDistance(); - scene.obstacle_distance_stock = frogpilotLongitudinalPlan.getSafeObstacleDistanceStock(); - scene.stopped_equivalence = frogpilotLongitudinalPlan.getStoppedEquivalenceFactor(); - } - if (scene.speed_limit_controller) { - scene.speed_limit = frogpilotLongitudinalPlan.getSlcSpeedLimit(); - scene.speed_limit_offset = frogpilotLongitudinalPlan.getSlcSpeedLimitOffset(); - scene.speed_limit_overridden = frogpilotLongitudinalPlan.getSlcOverridden(); - scene.speed_limit_overridden_speed = frogpilotLongitudinalPlan.getSlcOverriddenSpeed(); - } - scene.adjusted_cruise = frogpilotLongitudinalPlan.getAdjustedCruise(); + if (sm.updated("frogpilotPlan")) { + auto frogpilotPlan = sm["frogpilotPlan"].getFrogpilotPlan(); + scene.acceleration_jerk = frogpilotPlan.getAccelerationJerk(); + scene.acceleration_jerk_difference = frogpilotPlan.getAccelerationJerkStock() - scene.acceleration_jerk; + scene.adjusted_cruise = frogpilotPlan.getAdjustedCruise(); + scene.desired_follow = frogpilotPlan.getDesiredFollowDistance(); + scene.ego_jerk = frogpilotPlan.getEgoJerk(); + scene.ego_jerk_difference = frogpilotPlan.getEgoJerkStock() - scene.ego_jerk; + scene.lane_width_left = frogpilotPlan.getLaneWidthLeft(); + scene.lane_width_right = frogpilotPlan.getLaneWidthRight(); + scene.obstacle_distance = frogpilotPlan.getSafeObstacleDistance(); + scene.obstacle_distance_stock = frogpilotPlan.getSafeObstacleDistanceStock(); + scene.speed_limit = frogpilotPlan.getSlcSpeedLimit(); + scene.speed_limit_offset = frogpilotPlan.getSlcSpeedLimitOffset(); + scene.speed_limit_overridden = frogpilotPlan.getSlcOverridden(); + scene.speed_limit_overridden_speed = frogpilotPlan.getSlcOverriddenSpeed(); + scene.stopped_equivalence = frogpilotPlan.getStoppedEquivalenceFactor(); + scene.unconfirmed_speed_limit = frogpilotPlan.getUnconfirmedSlcSpeedLimit(); + scene.vtsc_controlling_curve = frogpilotPlan.getVtscControllingCurve(); } if (sm.updated("liveLocationKalman")) { auto liveLocationKalman = sm["liveLocationKalman"].getLiveLocationKalman(); - if (scene.compass) { - auto orientation = liveLocationKalman.getCalibratedOrientationNED(); - if (orientation.getValid()) { - scene.bearing_deg = RAD2DEG(orientation.getValue()[2]); - } + auto orientation = liveLocationKalman.getCalibratedOrientationNED(); + if (orientation.getValid()) { + scene.bearing_deg = RAD2DEG(orientation.getValue()[2]); } } + if (sm.updated("liveTorqueParameters")) { + auto torque_params = sm["liveTorqueParameters"].getLiveTorqueParameters(); + scene.friction = torque_params.getFrictionCoefficientFiltered(); + scene.lat_accel = torque_params.getLatAccelFactorFiltered(); + scene.live_valid = torque_params.getLiveValid(); + } if (sm.updated("wideRoadCameraState")) { auto cam_state = sm["wideRoadCameraState"].getWideRoadCameraState(); float scale = (cam_state.getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f; @@ -290,72 +306,116 @@ void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); s->scene.map_on_left = params.getBool("NavSettingLeftSide"); +} - // FrogPilot variables +void ui_update_frogpilot_params(UIState *s) { + Params params = Params(); UIScene &scene = s->scene; - scene.always_on_lateral = params.getBool("AlwaysOnLateral"); - scene.camera_view = params.getInt("CameraView"); - scene.compass = params.getBool("Compass"); + std::string branch = params.get("GitBranch"); + bool isRelease = branch == "FrogPilot"; - scene.conditional_experimental = params.getBool("ConditionalExperimental"); - scene.conditional_speed = params.getInt("CESpeed"); - scene.conditional_speed_lead = params.getInt("CESpeedLead"); + bool always_on_lateral = params.getBool("AlwaysOnLateral"); + scene.show_aol_status_bar = always_on_lateral && !params.getBool("HideAOLStatusBar"); - scene.custom_onroad_ui = params.getBool("CustomUI"); - scene.adjacent_path = params.getBool("AdjacentPath") && scene.custom_onroad_ui; - scene.blind_spot_path = params.getBool("BlindSpotPath") && scene.custom_onroad_ui; - scene.lead_info = params.getBool("LeadInfo") && scene.custom_onroad_ui; - scene.road_name_ui = params.getBool("RoadNameUI") && scene.custom_onroad_ui; - scene.show_fps = params.getBool("ShowFPS") && scene.custom_onroad_ui; - scene.use_si = params.getBool("UseSI") && scene.custom_onroad_ui; - scene.use_vienna_slc_sign = params.getBool("UseVienna") && scene.custom_onroad_ui; + scene.conditional_experimental = scene.longitudinal_control && params.getBool("ConditionalExperimental"); + scene.conditional_speed = scene.conditional_experimental ? params.getInt("CESpeed") : 0; + scene.conditional_speed_lead = scene.conditional_experimental ? params.getInt("CESpeedLead") : 0; + scene.show_cem_status_bar = scene.conditional_experimental && !params.getBool("HideCEMStatusBar"); - scene.custom_theme = params.getBool("CustomTheme"); - scene.custom_colors = scene.custom_theme ? params.getInt("CustomColors") : 0; - scene.custom_icons = scene.custom_theme ? params.getInt("CustomIcons") : 0; - scene.custom_signals = scene.custom_theme ? params.getInt("CustomSignals") : 0; + bool custom_onroad_ui = params.getBool("CustomUI"); + bool custom_paths = custom_onroad_ui && params.getBool("CustomPaths"); + bool developer_ui = !isRelease && custom_onroad_ui && params.getBool("DeveloperUI"); + scene.acceleration_path = custom_paths && params.getBool("AccelerationPath"); + scene.adjacent_path = custom_paths && params.getBool("AdjacentPath"); + scene.adjacent_path_metrics = scene.adjacent_path && params.getBool("AdjacentPathMetrics"); + scene.blind_spot_path = custom_paths && params.getBool("BlindSpotPath"); + scene.compass = custom_onroad_ui && params.getBool("Compass"); + scene.fps_counter = custom_onroad_ui && params.getBool("FPSCounter"); + scene.lead_info = scene.longitudinal_control && custom_onroad_ui && params.getBool("LeadInfo"); + scene.pedals_on_ui = custom_onroad_ui && params.getBool("PedalsOnUI"); + scene.road_name_ui = custom_onroad_ui && params.getBool("RoadNameUI"); + scene.rotating_wheel = custom_onroad_ui && params.getBool("RotatingWheel"); + scene.show_jerk = scene.longitudinal_control && developer_ui && params.getBool("ShowJerk"); + scene.show_tuning = scene.has_auto_tune && developer_ui && !(params.getBool("LateralTune") && params.getBool("NNFF")) && params.getBool("ShowTuning"); + scene.use_si = (scene.lead_info || developer_ui) && params.getBool("UseSI"); + scene.wheel_icon = custom_onroad_ui ? params.getInt("WheelIcon") : 0; + + bool custom_theme = params.getBool("CustomTheme"); + scene.custom_colors = custom_theme ? params.getInt("CustomColors") : 0; + scene.custom_icons = custom_theme ? params.getInt("CustomIcons") : 0; + scene.custom_signals = custom_theme ? params.getInt("CustomSignals") : 0; + scene.holiday_themes = custom_theme && params.getBool("HolidayThemes"); + scene.random_events = custom_theme && params.getBool("RandomEvents"); + + scene.disable_smoothing_mtsc = params.getBool("MTSCEnabled") && params.getBool("DisableMTSCSmoothing"); + scene.disable_smoothing_vtsc = params.getBool("VisionTurnControl") && params.getBool("DisableVTSCSmoothing"); + scene.experimental_mode_via_screen = scene.longitudinal_control && params.getBool("ExperimentalModeActivation") && params.getBool("ExperimentalModeViaTap"); + + bool lane_detection = params.getBool("NudgelessLaneChange") && params.getInt("LaneDetectionWidth") != 0; + scene.lane_detection_width = lane_detection ? params.getInt("LaneDetectionWidth") * (scene.is_metric ? 1 : FOOT_TO_METER) / 10 : 9.0f; scene.model_ui = params.getBool("ModelUI"); - scene.acceleration_path = params.getBool("AccelerationPath") && scene.model_ui; - scene.lane_line_width = params.getInt("LaneLinesWidth") * (scene.is_metric ? 1 : INCH_TO_CM) / 200; + scene.dynamic_path_width = scene.model_ui && params.getBool("DynamicPathWidth"); + scene.hide_lead_marker = scene.model_ui && params.getBool("HideLeadMarker"); + scene.lane_line_width = params.getInt("LaneLinesWidth") * (scene.is_metric ? 1.0f : INCH_TO_CM) / 200.0f; scene.path_edge_width = params.getInt("PathEdgeWidth"); - scene.path_width = params.getInt("PathWidth") / 10.0 * (scene.is_metric ? 1 : FOOT_TO_METER) / 2; - scene.road_edge_width = params.getInt("RoadEdgesWidth") * (scene.is_metric ? 1 : INCH_TO_CM) / 200; - scene.unlimited_road_ui_length = params.getBool("UnlimitedLength") && scene.model_ui; + scene.path_width = params.getInt("PathWidth") / 10.0f * (scene.is_metric ? 1.0f : FOOT_TO_METER) / 2.0f; + scene.road_edge_width = params.getInt("RoadEdgesWidth") * (scene.is_metric ? 1.0f : INCH_TO_CM) / 200.0f; + scene.unlimited_road_ui_length = scene.model_ui && params.getBool("UnlimitedLength"); - scene.driver_camera = params.getBool("DriverCamera"); - scene.experimental_mode_via_screen = params.getBool("ExperimentalModeViaScreen") && params.getBool("ExperimentalModeActivation"); - scene.mute_dm = params.getBool("MuteDM") && params.getBool("FireTheBabysitter"); - // scene.personalities_via_screen = (params.getInt("AdjustablePersonalities") == 2 || params.getInt("AdjustablePersonalities") == 3); - scene.personalities_via_screen = false; + bool quality_of_life_controls = params.getBool("QOLControls"); + scene.onroad_distance_button = scene.longitudinal_control && quality_of_life_controls && params.getBool("OnroadDistanceButton"); + scene.use_kaofui_icons = scene.onroad_distance_button && params.getBool("KaofuiIcons"); + scene.reverse_cruise = quality_of_life_controls && params.getBool("ReverseCruise"); + scene.reverse_cruise_ui = scene.reverse_cruise && params.getBool("ReverseCruiseUI"); - scene.quality_of_life_controls = params.getBool("QOLControls"); - scene.reverse_cruise = params.getBool("ReverseCruise") && scene.quality_of_life_controls; + bool quality_of_life_visuals = params.getBool("QOLVisuals"); + scene.big_map = quality_of_life_visuals && params.getBool("BigMap"); + scene.full_map = scene.big_map && params.getBool("FullMap"); + scene.camera_view = quality_of_life_visuals ? params.getInt("CameraView") : 0; + scene.driver_camera = quality_of_life_visuals && params.getBool("DriverCamera"); + scene.hide_speed = quality_of_life_visuals && params.getBool("HideSpeed"); + scene.hide_speed_ui = scene.hide_speed && params.getBool("HideSpeedUI"); + scene.map_style = quality_of_life_visuals ? params.getInt("MapStyle") : 0; + scene.numerical_temp = quality_of_life_visuals && params.getBool("NumericalTemp"); + scene.fahrenheit = scene.numerical_temp && params.getBool("Fahrenheit"); + scene.wheel_speed = quality_of_life_visuals && params.getBool("WheelSpeed"); - scene.quality_of_life_visuals = params.getBool("QOLVisuals"); - scene.full_map = params.getBool("FullMap") && scene.quality_of_life_visuals; - scene.hide_speed = params.getBool("HideSpeed") && scene.quality_of_life_visuals; - scene.show_slc_offset = params.getBool("ShowSLCOffset") && scene.quality_of_life_visuals; + bool screen_management = params.getBool("ScreenManagement"); + bool hide_ui_elements = screen_management && params.getBool("HideUIElements"); + scene.hide_alerts = hide_ui_elements && params.getBool("HideAlerts"); + scene.hide_map_icon = hide_ui_elements && params.getBool("HideMapIcon"); + scene.hide_max_speed = hide_ui_elements && params.getBool("HideMaxSpeed"); + scene.screen_brightness = screen_management ? params.getInt("ScreenBrightness") : 101; + scene.screen_brightness_onroad = screen_management ? params.getInt("ScreenBrightnessOnroad") : 101; + scene.screen_recorder = screen_management && params.getBool("ScreenRecorder"); + scene.screen_timeout = screen_management ? params.getInt("ScreenTimeout") : 30; + scene.screen_timeout_onroad = screen_management ? params.getInt("ScreenTimeoutOnroad") : 10; + scene.standby_mode = screen_management && params.getBool("StandbyMode"); - scene.random_events = params.getBool("RandomEvents"); - scene.rotating_wheel = params.getBool("RotatingWheel"); - scene.screen_brightness = params.getInt("ScreenBrightness"); - scene.speed_limit_controller = params.getBool("SpeedLimitController"); - scene.wheel_icon = params.getInt("WheelIcon"); + scene.speed_limit_controller = scene.longitudinal_control && params.getBool("SpeedLimitController"); + scene.show_slc_offset = scene.speed_limit_controller && params.getBool("ShowSLCOffset"); + scene.show_slc_offset_ui = scene.speed_limit_controller && params.getBool("ShowSLCOffsetUI"); + scene.use_vienna_slc_sign = scene.speed_limit_controller && params.getBool("UseVienna"); } void UIState::updateStatus() { if (scene.started && sm->updated("controlsState")) { auto controls_state = (*sm)["controlsState"].getControlsState(); auto state = controls_state.getState(); + auto previous_status = status; if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { status = STATUS_OVERRIDE; } else if (scene.always_on_lateral_active) { - status = STATUS_LATERAL_ACTIVE; + status = STATUS_ALWAYS_ON_LATERAL_ACTIVE; + } else if (scene.traffic_mode_active && scene.enabled) { + status = STATUS_TRAFFIC_MODE_ACTIVE; } else { status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; } + + scene.wake_up_screen = controls_state.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL || status != previous_status; } // Handle onroad/offroad transition @@ -375,8 +435,8 @@ UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", - "frogpilotCarControl", "frogpilotDeviceState", "frogpilotLateralPlan", "frogpilotLongitudinalPlan", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", "liveTorqueParameters", + "frogpilotCarControl", "frogpilotDeviceState", "frogpilotPlan", }); Params params; @@ -393,8 +453,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { wifi = new WifiManager(this); - ui_update_params(this); - setDefaultParams(); + ui_update_frogpilot_params(this); } void UIState::update() { @@ -408,19 +467,15 @@ void UIState::update() { emit uiUpdate(*this); // Update FrogPilot variables when they are changed - Params paramsMemory{"/dev/shm/params"}; if (paramsMemory.getBool("FrogPilotTogglesUpdated")) { - std::thread updateFrogPilotParams(ui_update_params, this); - updateFrogPilotParams.detach(); + ui_update_frogpilot_params(this); } // FrogPilot live variables that need to be constantly checked - if (scene.conditional_experimental) { - scene.conditional_status = paramsMemory.getInt("CEStatus"); - } - if (scene.random_events) { - scene.current_random_event = paramsMemory.getInt("CurrentRandomEvent"); - } + scene.conditional_status = scene.conditional_experimental ? paramsMemory.getInt("CEStatus") : 0; + scene.current_holiday_theme = scene.holiday_themes ? paramsMemory.getInt("CurrentHolidayTheme") : 0; + scene.current_random_event = scene.random_events ? paramsMemory.getInt("CurrentRandomEvent") : 0; + scene.driver_camera_timer = (scene.driver_camera && scene.reverse) ? scene.driver_camera_timer + 1 : 0; } void UIState::setPrimeType(PrimeType type) { @@ -459,9 +514,11 @@ void Device::setAwake(bool on) { } } -void Device::resetInteractiveTimeout(int timeout) { +void Device::resetInteractiveTimeout(int timeout, int timeout_onroad) { if (timeout == -1) { - timeout = 60; + timeout = (ignition_on ? 10 : 30); + } else { + timeout = (ignition_on ? timeout_onroad : timeout); } interactive_timeout = timeout * UI_FREQ; } @@ -485,8 +542,11 @@ void Device::updateBrightness(const UIState &s) { int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; - } else if (s.scene.screen_brightness <= 100) { - // Bring the screen brightness up to 5% upon screen tap + } else if (s.scene.started && s.scene.standby_mode && !s.scene.wake_up_screen && interactive_timeout == 0) { + brightness = 0; + } else if (s.scene.started && s.scene.screen_brightness_onroad != 101) { + brightness = interactive_timeout > 0 ? fmax(5, s.scene.screen_brightness_onroad) : s.scene.screen_brightness_onroad; + } else if (s.scene.screen_brightness != 101) { brightness = fmax(5, s.scene.screen_brightness); } @@ -499,16 +559,26 @@ void Device::updateBrightness(const UIState &s) { } void Device::updateWakefulness(const UIState &s) { - bool ignition_just_turned_off = !s.scene.ignition && ignition_on; + bool ignition_state_changed = s.scene.ignition != ignition_on; ignition_on = s.scene.ignition; - if (ignition_just_turned_off) { - resetInteractiveTimeout(); + if (ignition_on && s.scene.standby_mode) { + if (s.scene.wake_up_screen) { + resetInteractiveTimeout(s.scene.screen_timeout, s.scene.screen_timeout_onroad); + } + } + + if (ignition_state_changed) { + if (ignition_on && s.scene.screen_brightness_onroad == 0 && !s.scene.standby_mode) { + resetInteractiveTimeout(0, 0); + } else { + resetInteractiveTimeout(s.scene.screen_timeout, s.scene.screen_timeout_onroad); + } } else if (interactive_timeout > 0 && --interactive_timeout == 0) { emit interactiveTimeout(); } - if (s.scene.screen_brightness != 0) { + if (s.scene.screen_brightness_onroad != 0) { setAwake(s.scene.ignition || interactive_timeout > 0); } else { setAwake(interactive_timeout > 0); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h old mode 100755 new mode 100644 index 91346ea..9f0bdcb --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -77,7 +77,12 @@ struct Alert { const int controls_missing = (nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9; // Handle controls timeout - if (controls_frame < started_frame) { + if (std::ifstream("/data/community/crashes/error.txt")) { + alert = {"openpilot crashed", "Please post the error log in the FrogPilot Discord!", + "controlsWaiting", cereal::ControlsState::AlertSize::MID, + cereal::ControlsState::AlertStatus::NORMAL, + AudibleAlert::NONE}; + } else if (controls_frame < started_frame) { // car is started, but controlsState hasn't been seen at all alert = {"openpilot Unavailable", "Waiting for controls to start", "controlsWaiting", cereal::ControlsState::AlertSize::MID, @@ -108,7 +113,9 @@ typedef enum UIStatus { STATUS_ENGAGED, // FrogPilot statuses - STATUS_LATERAL_ACTIVE, + STATUS_ALWAYS_ON_LATERAL_ACTIVE, + STATUS_TRAFFIC_MODE_ACTIVE, + } UIStatus; enum PrimeType { @@ -127,7 +134,8 @@ const QColor bg_colors [] = { [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), // FrogPilot colors - [STATUS_LATERAL_ACTIVE] = QColor(0x0a, 0xba, 0xb5, 0xf1), + [STATUS_ALWAYS_ON_LATERAL_ACTIVE] = QColor(0x0a, 0xba, 0xb5, 0xf1), + [STATUS_TRAFFIC_MODE_ACTIVE] = QColor(0xc9, 0x22, 0x31, 0xf1), }; static std::map alert_colors = { @@ -163,6 +171,7 @@ typedef struct UIScene { vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; bool navigate_on_openpilot = false; + cereal::LongitudinalPersonality personality; float light_sensor; bool started, ignition, is_metric, map_on_left, longitudinal_control; @@ -172,69 +181,117 @@ typedef struct UIScene { // FrogPilot variables bool acceleration_path; bool adjacent_path; - bool always_on_lateral; + bool adjacent_path_metrics; bool always_on_lateral_active; + bool big_map; bool blind_spot_left; bool blind_spot_path; bool blind_spot_right; bool compass; bool conditional_experimental; - bool custom_onroad_ui; - bool custom_theme; + bool disable_smoothing_mtsc; + bool disable_smoothing_vtsc; bool driver_camera; + bool dynamic_path_width; bool enabled; bool experimental_mode; bool experimental_mode_via_screen; + bool fahrenheit; + bool fps_counter; bool full_map; + bool has_auto_tune; + bool hide_alerts; + bool hide_lead_marker; + bool hide_map_icon; + bool hide_max_speed; bool hide_speed; + bool hide_speed_ui; + bool holiday_themes; bool lead_info; + bool live_valid; bool map_open; bool model_ui; - bool mute_dm; - bool personalities_via_screen; - bool quality_of_life_controls; - bool quality_of_life_visuals; + bool numerical_temp; + bool online; + bool onroad_distance_button; + bool parked; + bool pedals_on_ui; bool random_events; + bool reverse; bool reverse_cruise; + bool reverse_cruise_ui; + bool right_hand_drive; bool road_name_ui; bool rotating_wheel; - bool show_driver_camera; + bool screen_recorder; + bool show_aol_status_bar; + bool show_cem_status_bar; + bool show_jerk; bool show_slc_offset; - bool show_fps; + bool show_slc_offset_ui; + bool show_tuning; + bool speed_limit_changed; bool speed_limit_controller; bool speed_limit_overridden; + bool standby_mode; + bool standstill; bool tethering_enabled; + bool traffic_mode; + bool traffic_mode_active; bool turn_signal_left; bool turn_signal_right; bool unlimited_road_ui_length; + bool use_kaofui_icons; bool use_si; bool use_vienna_slc_sign; + bool vtsc_controlling_curve; + bool wake_up_screen; + bool wheel_speed; + + float acceleration; + float acceleration_jerk; + float acceleration_jerk_difference; float adjusted_cruise; + float ego_jerk; + float ego_jerk_difference; + float friction; + float lane_detection_width; float lane_line_width; float lane_width_left; float lane_width_right; + float lat_accel; float path_edge_width; float path_width; float road_edge_width; float speed_limit; float speed_limit_offset; float speed_limit_overridden_speed; + float unconfirmed_speed_limit; + + int alert_size; int bearing_deg; int camera_view; int conditional_speed; int conditional_speed_lead; int conditional_status; + int current_holiday_theme; int current_random_event; int custom_colors; int custom_icons; int custom_signals; int desired_follow; + int driver_camera_timer; + int map_style; int obstacle_distance; int obstacle_distance_stock; int screen_brightness; + int screen_brightness_onroad; + int screen_timeout; + int screen_timeout_onroad; int steering_angle_deg; int stopped_equivalence; int wheel_icon; + QPolygonF track_adjacent_vertices[6]; QPolygonF track_edge_vertices; @@ -265,6 +322,9 @@ public: QTransform car_space_transform; + // FrogPilot variables + WifiManager *wifi = nullptr; + signals: void uiUpdate(const UIState &s); void offroadTransition(bool offroad); @@ -279,7 +339,8 @@ private: bool started_prev = false; PrimeType prime_type = PrimeType::UNKNOWN; - WifiManager *wifi = nullptr; + // FrogPilot variables + Params paramsMemory{"/dev/shm/params"}; }; UIState *uiState(); @@ -314,7 +375,7 @@ signals: void interactiveTimeout(); public slots: - void resetInteractiveTimeout(int timeout = -1); + void resetInteractiveTimeout(int timeout = -1, int timeout_onroad = -1); void update(const UIState &s); }; @@ -326,6 +387,9 @@ void update_model(UIState *s, const cereal::ModelDataV2::Reader &model, const cereal::UiPlan::Reader &plan); void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); -void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); +void update_leads(UIState *s, const cereal::ModelDataV2::Reader &model_data); void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); + +// FrogPilot functions +void ui_update_frogpilot_params(UIState *s); diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py new file mode 100644 index 0000000..660495b --- /dev/null +++ b/selfdrive/ui/ui.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +import os +import signal + +signal.signal(signal.SIGINT, signal.SIG_DFL) + +import cereal.messaging as messaging +from openpilot.system.hardware import HARDWARE + +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QStackedLayout, QApplication +from openpilot.selfdrive.ui.qt.python_helpers import set_main_window + + +if __name__ == "__main__": + app = QApplication([]) + win = QWidget() + set_main_window(win) + + bg = QLabel("", alignment=Qt.AlignCenter) + + alert1 = QLabel() + alert2 = QLabel() + vlayout = QVBoxLayout() + vlayout.addWidget(alert1, alignment=Qt.AlignCenter) + vlayout.addWidget(alert2, alignment=Qt.AlignCenter) + + tmp = QWidget() + tmp.setLayout(vlayout) + + stack = QStackedLayout(win) + stack.addWidget(tmp) + stack.addWidget(bg) + stack.setStackingMode(QStackedLayout.StackAll) + + win.setObjectName("win") + win.setStyleSheet(""" + #win { + background-color: black; + } + QLabel { + color: white; + font-size: 40px; + } + """) + + sm = messaging.SubMaster(['deviceState', 'controlsState']) + + def update(): + sm.update(0) + + onroad = sm.all_checks(['deviceState']) and sm['deviceState'].started + if onroad: + cs = sm['controlsState'] + color = ("grey" if str(cs.state) in ("overriding", "preEnabled") else "green") if cs.enabled else "blue" + bg.setText("\U0001F44D" if cs.engageable else "\U0001F6D1") + bg.setStyleSheet(f"font-size: 100px; background-color: {color};") + bg.show() + + alert1.setText(cs.alertText1) + alert2.setText(cs.alertText2) + + if not sm.alive['controlsState']: + alert1.setText("waiting for controls...") + else: + bg.hide() + alert1.setText("") + alert2.setText("offroad") + + HARDWARE.set_screen_brightness(100 if onroad else 40) + os.system("echo 0 > /sys/class/backlight/panel0-backlight/bl_power") + + timer = QTimer() + timer.timeout.connect(update) + timer.start(50) + + app.exec_() diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index e988a13..0fe0f05 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -28,7 +28,7 @@ def update_translations(vanish: bool = False, translation_files: None | list[str generate_translations_include() if translation_files is None: - with open(LANGUAGES_FILE, "r") as f: + with open(LANGUAGES_FILE) as f: translation_files = json.load(f).values() for file in translation_files: diff --git a/selfdrive/ui/watch3.cc b/selfdrive/ui/watch3.cc old mode 100755 new mode 100644