diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7f83f61e0a..1c22d5fece 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,6 +7,10 @@ on: jobs: build_masterci: name: build master-ci + env: + TARGET_DIR: /tmp/openpilot + container: + image: ghcr.io/commaai/openpilot-base:latest runs-on: ubuntu-20.04 if: github.repository == 'commaai/openpilot' steps: @@ -22,7 +26,19 @@ jobs: submodules: true fetch-depth: 0 - name: Pull LFS - run: git lfs pull + run: | + git config --global --add safe.directory '*' + git lfs pull - name: Build master-ci run: | + release/build_devel.sh + - name: Run tests + run: | + export PYTHONPATH=$TARGET_DIR + cd $TARGET_DIR + scons -j$(nproc) + selfdrive/car/tests/test_car_interfaces.py + - name: Push master-ci + run: | + unset TARGET_DIR BRANCH=master-ci release/build_devel.sh diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 9d92392b3a..268a3f3bcd 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -81,61 +81,87 @@ jobs: ${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \ scons -j$(nproc) --cache-populate" - #build_mac: - # name: build macos - # runs-on: macos-latest - # steps: - # - uses: actions/checkout@v3 - # with: - # submodules: true - # - name: Determine pre-existing Homebrew packages - # if: steps.dependency-cache.outputs.cache-hit != 'true' - # run: | - # echo 'EXISTING_CELLAR<> $GITHUB_ENV - # ls -1 /usr/local/Cellar >> $GITHUB_ENV - # echo 'EOF' >> $GITHUB_ENV - # - name: Cache dependencies - # id: dependency-cache - # uses: actions/cache@v2 - # with: - # path: | - # ~/.pyenv - # ~/.local/share/virtualenvs/ - # /usr/local/Cellar - # ~/github_brew_cache_entries.txt - # /tmp/scons_cache - # key: macos-${{ hashFiles('tools/mac_setup.sh', 'update_requirements.sh', 'poetry.lock') }} - # restore-keys: macos- - # - name: Brew link restored dependencies - # run: | - # if [ -f ~/github_brew_cache_entries.txt ]; then - # while read pkg; do - # brew link --force "$pkg" # `--force` for keg-only packages - # done < ~/github_brew_cache_entries.txt - # else - # echo "Cache entries not found" - # fi - # - name: Install dependencies - # run: ./tools/mac_setup.sh - # - name: Build openpilot - # run: | - # source tools/openpilot_env.sh - # poetry run selfdrive/manager/build.py - # - # # cleanup scons cache - # rm -rf /tmp/scons_cache/ - # poetry run scons -j$(nproc) --cache-populate - # - name: Remove pre-existing Homebrew packages for caching - # if: steps.dependency-cache.outputs.cache-hit != 'true' - # run: | - # cd /usr/local/Cellar - # new_cellar=$(ls -1) - # comm -12 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | while read pkg; do - # if [[ $pkg != "zstd" ]]; then # caching step needs zstd - # rm -rf "$pkg" - # fi - # done - # comm -13 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | tee ~/github_brew_cache_entries.txt + build_mac: + name: build macos + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + - name: Determine pre-existing Homebrew packages + if: steps.dependency-cache.outputs.cache-hit != 'true' + run: | + echo 'EXISTING_CELLAR<> $GITHUB_ENV + brew list --formula -1 >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + - name: Restore scons cache + id: scons-restore-cache + uses: actions/cache/restore@v3 + with: + path: /tmp/scons_cache + key: macos_scons-${{ github.sha }} + restore-keys: macos_scons- + - name: Cache dependencies + id: dependency-cache + uses: actions/cache@v2 + with: + path: | + ~/github_brew_cache_entries.txt + ~/.pyenv + ~/Library/Caches/pypoetry + /usr/local/Cellar + /usr/local/opt + /usr/local/Caskroom/gcc-arm-* + /opt/homebrew/Cellar + /opt/homebrew/opt + /opt/homebrew/Caskroom/gcc-arm-* + /Applications/ArmGNUToolchain/*/*/* + key: macos_deps-${{ hashFiles('tools/mac_setup.sh', 'update_requirements.sh', 'poetry.lock') }} + restore-keys: macos_deps- + - name: Brew link restored dependencies + run: | + if [ -f ~/github_brew_cache_entries.txt ]; then + brew link --force --overwrite $(cat ~/github_brew_cache_entries.txt) # `--force` for keg-only packages + if [ -d /Applications/ArmGNUToolchain ]; then # link gcc-arm-embedded manually + GCC_TOOLCHAIN="$(echo /Applications/ArmGNUToolchain/**/**/bin)" + echo "$GCC_TOOLCHAIN" >> $GITHUB_PATH + fi + else + echo "Cache entries not found" + fi + - name: Install dependencies + if: steps.dependency-cache.outputs.cache-hit != 'true' + run: ./tools/mac_setup.sh + - name: Build openpilot + run: | + source tools/openpilot_env.sh + poetry run scons -j$(nproc) + - name: Pre Cache - Cleanup scons cache + if: github.ref == 'refs/heads/master' + run: | + source tools/openpilot_env.sh + rm -rf /tmp/scons_cache/* + poetry run scons -j$(nproc) --cache-populate + - name: Save scons cache + id: scons-save-cache + uses: actions/cache/save@v3 + if: github.ref == 'refs/heads/master' + with: + path: /tmp/scons_cache + key: macos_scons-${{ github.sha }} + - name: Pre Cache - Remove pre-existing Homebrew packages + if: steps.dependency-cache.outputs.cache-hit != 'true' + run: | + new_cellar=$(brew list --formula -1) + comm -12 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | while read pkg; do + if [[ $pkg != "zstd" ]]; then # caching step needs zstd + rm -rf "$(brew --cellar)/$pkg" + fi + done + comm -13 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | tee ~/github_brew_cache_entries.txt + # .fseventsd directory causes permission errors in dep caching step + # its used by the system to observe changes within the directory - toolchain works without it, just remove it + sudo rm -rf /Applications/ArmGNUToolchain/*/*/.fseventsd docker_push: name: docker push @@ -209,6 +235,7 @@ jobs: $UNIT_TEST system/loggerd && \ $UNIT_TEST selfdrive/car && \ $UNIT_TEST selfdrive/locationd && \ + $UNIT_TEST system/tests && \ $UNIT_TEST system/ubloxd && \ selfdrive/locationd/test/_test_locationd_lib.py && \ ./system/ubloxd/tests/test_glonass_runner && \ diff --git a/.gitignore b/.gitignore index be6629f212..de8116416e 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ selfdrive/mapd/default_speeds_by_region.json system/proclogd/proclogd selfdrive/ui/_ui selfdrive/test/longitudinal_maneuvers/out +selfdrive/car/tests/cars_dump system/camerad/camerad system/camerad/test/ae_gray_test selfdrive/modeld/_modeld diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43d861667d..cd654e40c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: check-added-large-files args: ['--maxkb=100'] - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.5 hooks: - id: codespell exclude: '^(third_party/)|(body/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' diff --git a/Jenkinsfile b/Jenkinsfile index 128273792e..1f769d7795 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -139,6 +139,7 @@ pipeline { ["test camerad", "python system/camerad/test/test_camerad.py"], ["test exposure", "python system/camerad/test/test_exposure.py"], ["test amp", "python system/hardware/tici/tests/test_amplifier.py"], + ["test rawgpsd", "python system/sensord/rawgps/test_rawgps.py"], ]) } } @@ -155,7 +156,6 @@ pipeline { ["check dirty", "release/check-dirty.sh"], ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], ["time to onroad", "cd selfdrive/test/ && pytest test_time_to_onroad.py"], - ["test car interfaces", "cd selfdrive/car/tests/ && ./test_car_interfaces.py"], ]) } } @@ -218,7 +218,7 @@ pipeline { stage('replay') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici-common", [ + phone_steps("tici-replay", [ ["build", "cd selfdrive/manager && ./build.py"], ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], ]) diff --git a/RELEASES.md b/RELEASES.md index 482e959d9b..29c08bf6a0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,11 +1,22 @@ -Version 0.9.3 (2023-06-XX) +Version 0.9.4 (2023-XX-XX) +======================== +* Navigate on openpilot +* UI updates + * navigation settings moved to home screen and map +* Ford Focus 2018 support + +Version 0.9.3 (2023-06-29) ======================== * New driving model + * Improved height estimation and added height tracking in liveCalibration + * Model inputs refactor * New driving personality setting * Three settings: aggressive, standard, and relaxed * Standard is recommended and the default - * In aggressive mode lead follow distance is shorter and quicker gas/brake response - * In relaxed mode lead follow distance is longer + * In aggressive mode, lead follow distance is shorter and acceleration response is quicker + * In relaxed mode, lead follow distance is longer +* Improved fuzzy fingerprinting for Hyundai, Kia, and Genesis +* Improved thermal management logic Version 0.9.2 (2023-05-22) ======================== diff --git a/SConstruct b/SConstruct index 45b6fd8fdf..6de1d7cce0 100644 --- a/SConstruct +++ b/SConstruct @@ -67,17 +67,22 @@ AddOption('--no-test', real_arch = arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() if platform.system() == "Darwin": arch = "Darwin" + brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() if arch == "aarch64" and AGNOS: arch = "larch64" +# create symlink to lib dir of current arch, so the ACADOS_SOURCE_DIR would have valid structure +acados_lib_path = Dir(f"#third_party/acados/lib") +if not Dir(f"#third_party/acados/lib").exists(): + os.symlink(Dir(f"#third_party/acados/{arch}/lib").abspath, acados_lib_path.abspath) lenv = { "PATH": os.environ['PATH'], "LD_LIBRARY_PATH": [Dir(f"#third_party/acados/{arch}/lib").abspath], "PYTHONPATH": Dir("#").abspath, - "ACADOS_SOURCE_DIR": Dir("#third_party/acados/include/acados").abspath, + "ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath, "ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath, "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer" } @@ -110,26 +115,21 @@ else: cflags = [] cxxflags = [] cpppath = [] + rpath += [ + Dir("#cereal").abspath, + Dir("#common").abspath + ] # MacOS if arch == "Darwin": - if real_arch == "x86_64": - lenv["TERA_PATH"] = Dir("#").abspath + f"/third_party/acados/Darwin_x86_64/t_renderer" - - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() yuv_dir = "mac" if real_arch != "arm64" else "mac_arm64" libpath = [ f"#third_party/libyuv/{yuv_dir}/lib", + f"#third_party/acados/{arch}/lib", f"{brew_prefix}/lib", - f"{brew_prefix}/Library", f"{brew_prefix}/opt/openssl@3.0/lib", - f"{brew_prefix}/Cellar", "/System/Library/Frameworks/OpenGL.framework/Libraries", ] - if real_arch == "x86_64": - libpath.append(f"#third_party/acados/Darwin_x86_64/lib") - else: - libpath.append(f"#third_party/acados/{arch}/lib") cflags += ["-DGL_SILENCE_DEPRECATION"] cxxflags += ["-DGL_SILENCE_DEPRECATION"] @@ -137,6 +137,7 @@ else: f"{brew_prefix}/include", f"{brew_prefix}/opt/openssl@3.0/include", ] + lenv["DYLD_LIBRARY_PATH"] = lenv["LD_LIBRARY_PATH"] # Linux 86_64 else: libpath = [ @@ -149,12 +150,9 @@ else: "/usr/lib", "/usr/local/lib", ] - - rpath += [ - Dir("#third_party/snpe/x86_64-linux-clang").abspath, - Dir("#cereal").abspath, - Dir("#common").abspath - ] + rpath += [ + Dir("#third_party/snpe/x86_64-linux-clang").abspath, + ] if GetOption('asan'): ccflags = ["-fsanitize=address", "-fno-omit-frame-pointer"] @@ -231,7 +229,9 @@ env = Environment( ) if arch == "Darwin": - env['RPATHPREFIX'] = "-rpath " + # RPATH is not supported on macOS, instead use the linker flags + darwin_rpath_link_flags = [f"-Wl,-rpath,{path}" for path in env["RPATH"]] + env["LINKFLAGS"] += darwin_rpath_link_flags if GetOption('compile_db'): env.CompilationDatabase('compile_commands.json') @@ -271,7 +271,7 @@ envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-decla envCython["LIBS"] = [] if arch == "Darwin": - envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"] + envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"] + darwin_rpath_link_flags elif arch == "aarch64": envCython["LINKFLAGS"] = ["-shared"] envCython["LIBS"] = [os.path.basename(py_include)] @@ -286,10 +286,7 @@ qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", " qt_libs = [] if arch == "Darwin": - if real_arch == "arm64": - qt_env['QTDIR'] = "/opt/homebrew/opt/qt@5" - else: - qt_env['QTDIR'] = "/usr/local/opt/qt@5" + qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5" qt_dirs = [ os.path.join(qt_env['QTDIR'], "include"), ] diff --git a/cereal b/cereal index f319a83ab7..f1463a08ee 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit f319a83ab7a902477c786a25aa34077b6f640990 +Subproject commit f1463a08eebd98725724cb21c5966ea7b23da8b8 diff --git a/common/modeldata.h b/common/modeldata.h index a00d3d49d3..22ede60997 100644 --- a/common/modeldata.h +++ b/common/modeldata.h @@ -10,6 +10,9 @@ const int LON_MPC_N = 32; const float MIN_DRAW_DISTANCE = 10.0; const float MAX_DRAW_DISTANCE = 100.0; +const float RYG_GREEN = 0.01165; +const float RYG_YELLOW = 0.06157; + template constexpr std::array build_idxs(float max_val) { std::array result{}; @@ -33,14 +36,3 @@ const mat3 fcam_intrinsic_matrix = (mat3){{2648.0, 0.0, 1928.0 / 2, const mat3 ecam_intrinsic_matrix = (mat3){{567.0, 0.0, 1928.0 / 2, 0.0, 567.0, 1208.0 / 2, 0.0, 0.0, 1.0}}; - -static inline mat3 get_model_yuv_transform() { - float db_s = 1.0; - const mat3 transform = (mat3){{ - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0 - }}; - // Can this be removed since scale is 1? - return transform_scale_buffer(transform, db_s); -} diff --git a/common/params.cc b/common/params.cc index 0e6b644023..288de76b0c 100644 --- a/common/params.cc +++ b/common/params.cc @@ -85,6 +85,9 @@ private: std::unordered_map keys = { {"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG}, + {"ApiCache_Device", PERSISTENT}, + {"ApiCache_DriveStats", PERSISTENT}, + {"ApiCache_NavDestinations", PERSISTENT}, {"AssistNowToken", PERSISTENT}, {"AthenadPid", PERSISTENT}, {"AthenadUploadQueue", PERSISTENT}, @@ -102,16 +105,15 @@ std::unordered_map keys = { {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"DisablePowerDown", PERSISTENT}, - {"ExperimentalMode", PERSISTENT}, - {"ExperimentalModeConfirmed", PERSISTENT}, - {"LongitudinalPersonality", PERSISTENT}, - {"ExperimentalLongitudinalEnabled", PERSISTENT}, {"DisableUpdates", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT}, {"DongleId", PERSISTENT}, {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, + {"ExperimentalLongitudinalEnabled", PERSISTENT}, + {"ExperimentalMode", PERSISTENT}, + {"ExperimentalModeConfirmed", PERSISTENT}, {"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ForcePowerDown", PERSISTENT}, {"GitBranch", PERSISTENT}, @@ -134,9 +136,9 @@ std::unordered_map keys = { {"IsOffroad", CLEAR_ON_MANAGER_START}, {"IsOnroad", PERSISTENT}, {"IsRhdDetected", PERSISTENT}, + {"IsReleaseBranch", CLEAR_ON_MANAGER_START}, {"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"IsTestedBranch", CLEAR_ON_MANAGER_START}, - {"IsReleaseBranch", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"LaikadEphemerisV3", PERSISTENT | DONT_LOG}, @@ -144,6 +146,7 @@ std::unordered_map keys = { {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, {"LastManagerExitReason", CLEAR_ON_MANAGER_START}, + {"LastOffroadStatusPacket", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"LastPowerDropDetected", CLEAR_ON_MANAGER_START}, {"LastSystemShutdown", CLEAR_ON_MANAGER_START}, {"LastUpdateException", CLEAR_ON_MANAGER_START}, @@ -151,13 +154,27 @@ std::unordered_map keys = { {"LiveParameters", PERSISTENT}, {"LiveTorqueCarParams", PERSISTENT}, {"LiveTorqueParameters", PERSISTENT | DONT_LOG}, + {"LongitudinalPersonality", PERSISTENT}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, - {"NavSettingTime24h", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, + {"NavSettingTime24h", PERSISTENT}, {"NavdRender", PERSISTENT}, {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, + {"Offroad_BadNvme", CLEAR_ON_MANAGER_START}, + {"Offroad_CarUnrecognized", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, + {"Offroad_ConnectivityNeeded", CLEAR_ON_MANAGER_START}, + {"Offroad_ConnectivityNeededPrompt", CLEAR_ON_MANAGER_START}, + {"Offroad_InvalidTime", CLEAR_ON_MANAGER_START}, + {"Offroad_IsTakingSnapshot", CLEAR_ON_MANAGER_START}, + {"Offroad_NeosUpdate", CLEAR_ON_MANAGER_START}, + {"Offroad_NoFirmware", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, + {"Offroad_Recalibration", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, + {"Offroad_StorageMissing", CLEAR_ON_MANAGER_START}, + {"Offroad_TemperatureTooHigh", CLEAR_ON_MANAGER_START}, + {"Offroad_UnofficialHardware", CLEAR_ON_MANAGER_START}, + {"Offroad_UpdateFailed", CLEAR_ON_MANAGER_START}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, @@ -176,32 +193,16 @@ std::unordered_map keys = { {"UbloxAvailable", PERSISTENT}, {"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START}, - {"UpdaterState", CLEAR_ON_MANAGER_START}, - {"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START}, - {"UpdaterTargetBranch", CLEAR_ON_MANAGER_START}, {"UpdaterAvailableBranches", CLEAR_ON_MANAGER_START}, {"UpdaterCurrentDescription", CLEAR_ON_MANAGER_START}, {"UpdaterCurrentReleaseNotes", CLEAR_ON_MANAGER_START}, + {"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START}, {"UpdaterNewDescription", CLEAR_ON_MANAGER_START}, {"UpdaterNewReleaseNotes", CLEAR_ON_MANAGER_START}, + {"UpdaterState", CLEAR_ON_MANAGER_START}, + {"UpdaterTargetBranch", CLEAR_ON_MANAGER_START}, {"Version", PERSISTENT}, {"VisionRadarToggle", PERSISTENT}, - {"ApiCache_Device", PERSISTENT}, - {"ApiCache_DriveStats", PERSISTENT}, - {"ApiCache_NavDestinations", PERSISTENT}, - {"Offroad_BadNvme", CLEAR_ON_MANAGER_START}, - {"Offroad_CarUnrecognized", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, - {"Offroad_ConnectivityNeeded", CLEAR_ON_MANAGER_START}, - {"Offroad_ConnectivityNeededPrompt", CLEAR_ON_MANAGER_START}, - {"Offroad_InvalidTime", CLEAR_ON_MANAGER_START}, - {"Offroad_IsTakingSnapshot", CLEAR_ON_MANAGER_START}, - {"Offroad_NeosUpdate", CLEAR_ON_MANAGER_START}, - {"Offroad_NoFirmware", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, - {"Offroad_StorageMissing", CLEAR_ON_MANAGER_START}, - {"Offroad_TemperatureTooHigh", CLEAR_ON_MANAGER_START}, - {"Offroad_UnofficialHardware", CLEAR_ON_MANAGER_START}, - {"Offroad_UpdateFailed", CLEAR_ON_MANAGER_START}, - {"Offroad_Recalibration", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, }; } // namespace diff --git a/common/version.h b/common/version.h index 57d60e7f68..6af006aabb 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.3" +#define COMMA_VERSION "0.9.4" diff --git a/docs/CARS.md b/docs/CARS.md index a7fb408644..f6bcca1d58 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,27 +4,27 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 252 Supported Cars +# 253 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Buick|LaCrosse 2017-19[3](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Cadillac|Escalade 2017[3](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Buick|LaCrosse 2017-19[4](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Cadillac|Escalade ESV 2016[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -34,6 +34,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Ford|Bronco Sport 2021-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Explorer 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Maverick 2022-23|Co-Pilot360 Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -41,17 +42,17 @@ A supported vehicle is one that just works when you install a comma three. All s |Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Advanced Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Performance Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (2.5T Trim) 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (3.5T Trim) 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV80 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV60 (Advanced Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV60 (Performance Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV70 (2.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV70 (3.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV80 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|GMC|Acadia 2018[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic Hatchback 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -75,9 +76,9 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (with HDA II) 2022-23[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (without HDA II) 2022-23[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 5 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 5 (without HDA II) 2022-23[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -89,7 +90,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Cruz 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -98,17 +99,17 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2022[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson Hybrid 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson Hybrid 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (with HDA II) 2022-23[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (without HDA II) 2022-23[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|EV6 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|EV6 (without HDA II) 2022-23[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Forte 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -117,9 +118,9 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro EV 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -127,10 +128,10 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2021-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -153,8 +154,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Nissan B connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -162,8 +163,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -174,14 +175,14 @@ A supported vehicle is one that just works when you install a comma three. All s |Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Fabia 2022-23[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Škoda|Kamiq 2021[7,9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Škoda|Karoq 2019-21[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Kodiaq 2017-23[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia 2015, 2018-19[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia RS 2016[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Scala 2020[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Škoda|Superb 2015-22[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Fabia 2022-23[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[12](#footnotes)|| +|Škoda|Kamiq 2021[8,10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[12](#footnotes)|| +|Škoda|Karoq 2019-21[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Kodiaq 2017-23[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia 2015, 2018-19[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia RS 2016[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Scala 2020[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[12](#footnotes)|| +|Škoda|Superb 2015-22[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -194,8 +195,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2018-20|All|Stock|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2021-23|All|openpilot|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2018-20|All|Stock|0 mph[7](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2021-23|All|openpilot|0 mph[7](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2021-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -225,54 +226,55 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat 2015-22[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat 2015-22[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[12](#footnotes)|| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[12](#footnotes)|| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[12](#footnotes)|| +|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[12](#footnotes)|| +|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,11](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| ### Footnotes 1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
-3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
-42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-5Requires a red panda for this CAN FD car. All the hardware needed is sold in the CAN FD kit.
-6openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-7Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-8Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-9Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma three functionality.
-10Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-11Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
+4Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
+52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
+6Requires a red panda for this CAN FD car. All the hardware needed is sold in the CAN FD kit.
+7openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+8Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+9Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+10Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma three functionality.
+11Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+12Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/opendbc b/opendbc index 49b31858a3..f241a87591 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 49b31858a36275dc16f768781297e443e57d16ab +Subproject commit f241a87591b0e0339c5c8b6e2b1976271807ac56 diff --git a/panda b/panda index b563405904..b2cf197679 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b56340590485bba2428538259e020f176127458c +Subproject commit b2cf1976796dbd9f700a76543466c8c9c9665827 diff --git a/poetry.lock b/poetry.lock index 679d4b40a1..d3e8e1fb3b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -508,12 +508,15 @@ python-versions = "*" [[package]] name = "casadi" -version = "3.5.5" +version = "3.6.3" description = "CasADi -- framework for algorithmic differentiation and numeric optimization" category = "main" optional = false python-versions = "*" +[package.dependencies] +numpy = "*" + [[package]] name = "certifi" version = "2022.9.24" @@ -2641,19 +2644,19 @@ PyYAML = ">=5.1.0" [[package]] name = "onnx" -version = "1.12.0" +version = "1.14.0" description = "Open Neural Network Exchange" category = "main" optional = false python-versions = "*" [package.dependencies] -numpy = ">=1.16.6" -protobuf = ">=3.12.2,<=3.20.1" +numpy = "*" +protobuf = ">=3.20.2" typing-extensions = ">=3.6.2.1" [package.extras] -lint = ["clang-format (==13.0.0)", "flake8", "mypy (==0.782)", "types-protobuf (==3.18.4)"] +lint = ["lintrunner (>=0.10.0)", "lintrunner-adapters (>=0.3)"] [[package]] name = "onnx2torch" @@ -2685,7 +2688,7 @@ mypy = ["mypy (==0.600)"] [[package]] name = "onnxruntime-gpu" -version = "1.12.1" +version = "1.15.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" category = "main" optional = false @@ -2694,7 +2697,7 @@ python-versions = "*" [package.dependencies] coloredlogs = "*" flatbuffers = "*" -numpy = ">=1.21.0" +numpy = ">=1.21.6" packaging = "*" protobuf = "*" sympy = "*" @@ -3102,7 +3105,7 @@ wcwidth = "*" [[package]] name = "protobuf" -version = "3.20.1" +version = "3.20.3" description = "Protocol Buffers" category = "main" optional = false @@ -3667,6 +3670,14 @@ packaging = ">=20.4" hiredis = ["hiredis (>=1.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +[[package]] +name = "regex" +version = "2023.6.3" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "requests" version = "2.28.1" @@ -3747,6 +3758,25 @@ future = ">=0.15" docs = ["Sphinx (>=1.6.5)", "sphinx-rtd-theme (>=0.1.9)"] tests = ["flake8 (>=2.5.4)", "hacking (>=0.11.0)", "nose (>=1.3.4)", "numpy (>=1.11.0)"] +[[package]] +name = "safetensors" +version = "0.3.1" +description = "Fast and Safe Tensor serialization" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +all = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (>=2.11.0)", "torch (>=1.10)"] +dev = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (>=2.11.0)", "torch (>=1.10)"] +jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)"] +numpy = ["numpy (>=1.21.6)"] +paddlepaddle = ["paddlepaddle (>=2.4.1)"] +quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] +tensorflow = ["tensorflow (>=2.11.0)"] +testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "numpy (>=1.21.6)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)"] +torch = ["torch (>=1.10)"] + [[package]] name = "scikit-image" version = "0.19.3" @@ -4391,6 +4421,19 @@ webencodings = ">=0.4" doc = ["sphinx", "sphinx_rtd_theme"] test = ["flake8", "isort", "pytest"] +[[package]] +name = "tokenizers" +version = "0.13.3" +description = "Fast and Customizable Tokenizers" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] +docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] +testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] + [[package]] name = "toml" version = "0.10.2" @@ -4504,6 +4547,72 @@ python-versions = ">=3.7" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest"] +[[package]] +name = "transformers" +version = "4.30.2" +description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" +category = "dev" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +filelock = "*" +huggingface-hub = ">=0.14.1,<1.0" +numpy = ">=1.17" +packaging = ">=20.0" +pyyaml = ">=5.1" +regex = "!=2019.12.17" +requests = "*" +safetensors = ">=0.3.1" +tokenizers = ">=0.11.1,<0.11.3 || >0.11.3,<0.14" +tqdm = ">=4.27" + +[package.extras] +accelerate = ["accelerate (>=0.20.2)"] +agents = ["Pillow", "accelerate (>=0.20.2)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch (>=1.9,!=1.12.0)"] +all = ["Pillow", "accelerate (>=0.20.2)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.6.9)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf (<=3.20.3)", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision"] +audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +codecarbon = ["codecarbon (==1.2.0)"] +deepspeed = ["accelerate (>=0.20.2)", "deepspeed (>=0.8.3)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.20.2)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.8.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "optuna", "parameterized", "protobuf (<=3.20.3)", "psutil", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow", "accelerate (>=0.20.2)", "av (==9.2.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.6.9)", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf (<=3.20.3)", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf (<=3.20.3)", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow", "accelerate (>=0.20.2)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf (<=3.20.3)", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +docs = ["Pillow", "accelerate (>=0.20.2)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.6.9)", "hf-doc-builder", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf (<=3.20.3)", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision"] +docs-specific = ["hf-doc-builder"] +fairscale = ["fairscale (>0.3)"] +flax = ["flax (>=0.4.1,<=0.6.9)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "optax (>=0.0.8,<=0.1.4)"] +flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +ftfy = ["ftfy"] +integrations = ["optuna", "ray[tune]", "sigopt"] +ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] +modelcreation = ["cookiecutter (==1.7.3)"] +natten = ["natten (>=0.14.6)"] +onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] +onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] +optuna = ["optuna"] +quality = ["GitPython (<3.1.19)", "black (>=23.1,<24.0)", "datasets (!=2.5.0)", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "ruff (>=0.0.241,<=0.0.259)", "urllib3 (<2.0.0)"] +ray = ["ray[tune]"] +retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] +sagemaker = ["sagemaker (>=2.31.0)"] +sentencepiece = ["protobuf (<=3.20.3)", "sentencepiece (>=0.1.91,!=0.1.92)"] +serving = ["fastapi", "pydantic", "starlette", "uvicorn"] +sigopt = ["sigopt"] +sklearn = ["scikit-learn"] +speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "parameterized", "protobuf (<=3.20.3)", "psutil", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "timeout-decorator"] +tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx"] +tf-cpu = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx"] +tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +timm = ["timm"] +tokenizers = ["tokenizers (>=0.11.1,!=0.11.3,<0.14)"] +torch = ["accelerate (>=0.20.2)", "torch (>=1.9,!=1.12.0)"] +torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +torch-vision = ["Pillow", "torchvision"] +torchhub = ["filelock", "huggingface-hub (>=0.14.1,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf (<=3.20.3)", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "tqdm (>=4.27)"] +video = ["av (==9.2.0)", "decord (==0.6.0)"] +vision = ["Pillow"] + [[package]] name = "triton" version = "2.0.0" @@ -4812,7 +4921,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "2e03ebb1f6c441a0154531ee92f6f1f8c9e74544d4295d3753029bb8dcd457d1" +content-hash = "e6fcadbd1083d80b2e70d287b927d897e1a3d4f4907f4e5443f4d4b23ac02d89" [metadata.files] adal = [ @@ -5224,44 +5333,49 @@ carla = [ {file = "carla-0.9.13-cp38-cp38-win_amd64.whl", hash = "sha256:a64ee78fe91137fa7d4828c7fc06d5824bd7312e29e4ea4f31a5d74dd28bff40"}, ] casadi = [ - {file = "casadi-3.5.5-cp27-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:d4e49cb46404cef61f83b30bb20ec9597c50ae7f55cfd6b89c17facc74675437"}, - {file = "casadi-3.5.5-cp27-none-manylinux1_i686.whl", hash = "sha256:f08a99e98b0a15083f06b1e221f064a29b3ed9e20617dc55aa8e823f2f732ace"}, - {file = "casadi-3.5.5-cp27-none-manylinux1_x86_64.whl", hash = "sha256:09e103bb597d46aa338fc57bc49270068a1f07be35f9494c9f796dea4b801aeb"}, - {file = "casadi-3.5.5-cp27-none-win32.whl", hash = "sha256:a4ce51e988570160af9ccfbbb1b9679546cbb1865d3a74ef0276f37fd94d91d9"}, - {file = "casadi-3.5.5-cp27-none-win_amd64.whl", hash = "sha256:54d89442058271007ae8573dfa33360bea10e26603545481090b45e8b90c9d10"}, - {file = "casadi-3.5.5-cp34-none-manylinux1_x86_64.whl", hash = "sha256:4143803af909f284400c02f59de4d97e5ba9319de28366215ef55ef261914f9a"}, - {file = "casadi-3.5.5-cp34-none-win32.whl", hash = "sha256:7a624d40c7b5ded7916f6cc65998af4585b4557c9ea65dc1e3a6273ebb2313ec"}, - {file = "casadi-3.5.5-cp34-none-win_amd64.whl", hash = "sha256:3aec6737c282e7fb5be41f6c7d0649e52ce49efb3508f30bada707e809bbbb5f"}, - {file = "casadi-3.5.5-cp35-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6192e2ed81c15a7dab2554f5f69b134df8d1a982f8d9f13e57bdef93364d2120"}, - {file = "casadi-3.5.5-cp35-none-manylinux1_i686.whl", hash = "sha256:49a8b713f0ff0bbc2f2af2e71c515cdced238786e25ef504f5982618c84c67a7"}, - {file = "casadi-3.5.5-cp35-none-manylinux1_x86_64.whl", hash = "sha256:13277151efc76b221de8ca6b5ab7b8bbdd2b0e139f282866840adf88dfe53bc9"}, - {file = "casadi-3.5.5-cp35-none-manylinux2014_aarch64.whl", hash = "sha256:253569c85f881a6a8fe5e1c0758858edb1ecb4c3d8bce4aee4b52e5dc59fc091"}, - {file = "casadi-3.5.5-cp35-none-win32.whl", hash = "sha256:5de5c3c1381ac303e71fdef75dace34af6e1d50b46ac081051cd209b8b933837"}, - {file = "casadi-3.5.5-cp35-none-win_amd64.whl", hash = "sha256:4932b2b5361013420189dbc8d30e970672d036b37cb382f1c09c3b6cfe651a37"}, - {file = "casadi-3.5.5-cp36-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:2322748a8d5e88750fd2fc0abcdc56cfbad1a8cd538fe0e7d7b6d8ce0cb3fa62"}, - {file = "casadi-3.5.5-cp36-none-manylinux1_i686.whl", hash = "sha256:ab6a600a9b2ea27453d56fd4464ad0db0ae69f5cea42595fcbdaabcd40396440"}, - {file = "casadi-3.5.5-cp36-none-manylinux1_x86_64.whl", hash = "sha256:5f6eb8de31735c14ecc777e3ad77b57767b5f2dbea29265909ef696f51e8be92"}, - {file = "casadi-3.5.5-cp36-none-manylinux2014_aarch64.whl", hash = "sha256:adf20c34ba2cec1840a026023d93cc6d9b3581dfda6a044f434fc75b50c9a2ce"}, - {file = "casadi-3.5.5-cp36-none-win32.whl", hash = "sha256:7309a75b27c57f09b00a61815fb38c40da8e62e3004598e55ea1b8f713d96221"}, - {file = "casadi-3.5.5-cp36-none-win_amd64.whl", hash = "sha256:ab85c7cf772ba54f2718ebe366b836fffff868443f7c0c02389ed0a288cbde1f"}, - {file = "casadi-3.5.5-cp37-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:ec26244f9d9047f1bb401f1b86ff4775e1ddf638f4b4992bbc362a27a6f56673"}, - {file = "casadi-3.5.5-cp37-none-manylinux1_i686.whl", hash = "sha256:1c451a07b2440c00d552e040b6285b6e79b677d2978212368b28b86f5d267669"}, - {file = "casadi-3.5.5-cp37-none-manylinux1_x86_64.whl", hash = "sha256:24fbac649ee26572884029dcd0e108b4a2412cad003a84ed915c4e44a94ecae7"}, - {file = "casadi-3.5.5-cp37-none-manylinux2014_aarch64.whl", hash = "sha256:a06c0b96eb9d3bc88c627eec6e465726934ca0394347dc33efc742b8c91db83d"}, - {file = "casadi-3.5.5-cp37-none-win32.whl", hash = "sha256:36db4c84d8f3aad328faaeaeaa454a633c95a854d78ea188791b147888379342"}, - {file = "casadi-3.5.5-cp37-none-win_amd64.whl", hash = "sha256:643e48f92eaf65eb82964816bb7e7064ddb8239959210fa6168e8bce6fe6ef94"}, - {file = "casadi-3.5.5-cp38-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6ce7ac8a301a145f98d46db0bfd13bc8b3831a5bb92e8054d531a1f233bb4b93"}, - {file = "casadi-3.5.5-cp38-none-manylinux1_i686.whl", hash = "sha256:473bb86fa64ac9703d74a474514703b4665fa9a384221ced620b5025e64532a7"}, - {file = "casadi-3.5.5-cp38-none-manylinux1_x86_64.whl", hash = "sha256:292e2768280393bad406256e0ef9c30ddcd4867dbd42148b36f9d92a32d9e199"}, - {file = "casadi-3.5.5-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:353a79e50aa84ac5e0d9f04bc3b2d78a2cc8edae3b842d757756449682778944"}, - {file = "casadi-3.5.5-cp38-none-win32.whl", hash = "sha256:77f33cb95be6a49b93d8d6b81f05193676ae09857699cedf8f1a14a4285d077e"}, - {file = "casadi-3.5.5-cp38-none-win_amd64.whl", hash = "sha256:fbf39dcd63f1d3b63c300fce59b7ea678bd5ea1d014e1e090a5226600a4132cb"}, - {file = "casadi-3.5.5-cp39-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:4086669280b2335d235c664373db46dcd7f6485dba4663ce1944ea01753c5e8b"}, - {file = "casadi-3.5.5-cp39-none-manylinux1_i686.whl", hash = "sha256:c3440c90c31b61ae1df82f6c784643393f723354dc08013f9d5cedf25507c67c"}, - {file = "casadi-3.5.5-cp39-none-manylinux1_x86_64.whl", hash = "sha256:bd94048388b602fc30fdac2fecb986c034110ed8d2d17af7fd13b0de45c58bd7"}, - {file = "casadi-3.5.5-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:cd630a2e6ec6df6a4977af63080fa8d63a0053ff8c06ea0200959b47ae75201c"}, - {file = "casadi-3.5.5-cp39-none-win32.whl", hash = "sha256:ac45b91616e9b8afbe266ca08e80770b28e9e6d7a5852e3677fb37e42bde2047"}, - {file = "casadi-3.5.5-cp39-none-win_amd64.whl", hash = "sha256:55df534d003efdd120c4ebfeb6b252c443d273cdc4b97a394eb0268367477795"}, + {file = "casadi-3.6.3-cp27-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:884c07617fbbedbd047900d6f2ab86e933064efc517b973fa4139fc60543e498"}, + {file = "casadi-3.6.3-cp27-none-manylinux1_i686.whl", hash = "sha256:6f9c1fadfb1eb729f8906f01cd2b45f542846a386fb63d59eb1872451dda8de3"}, + {file = "casadi-3.6.3-cp27-none-manylinux2010_x86_64.whl", hash = "sha256:041639615a866a7244e88905c40c15ef8f84bdedf0b4f92f72e4c5b56f8fe2dc"}, + {file = "casadi-3.6.3-cp27-none-win_amd64.whl", hash = "sha256:418d345e47cbc957a49e28c7765a7f6e37f5eb73ab969e6c6cfcd204189c559c"}, + {file = "casadi-3.6.3-cp310-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:9c8d360ee80dd65c0c7625e3dccfabdd6e78629a754fb28f6fd77e3d4893dc80"}, + {file = "casadi-3.6.3-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:a74cf436d42adf69d5ea16ba13d95f40585e148e856d00553f96740704fe2a03"}, + {file = "casadi-3.6.3-cp310-none-manylinux2014_aarch64.whl", hash = "sha256:1995029b4f11d492cb5277645534bdd377340eaabe51dba3c6d9ed3a25f83f4c"}, + {file = "casadi-3.6.3-cp310-none-manylinux2014_i686.whl", hash = "sha256:501ac40357dae0d224499f1b41b7d409703137ac7c8c1bcbb26444cff9d00de3"}, + {file = "casadi-3.6.3-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:d86511c92f09fa464937098bb47b5cdfd07952a0a2b236938b9370537e4532d6"}, + {file = "casadi-3.6.3-cp310-none-win_amd64.whl", hash = "sha256:4d41d07a9a4c1cd9aa55a43ffe8921721dc5937b124075e903b4c0948e5dcb9b"}, + {file = "casadi-3.6.3-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:9a2aab9799e8a597b737c8ebbde6fce9cad6177d51fef8f73310d429e4b76cb4"}, + {file = "casadi-3.6.3-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:55cff88af39c486895d5d6ec3c1862050dbd51ca879df81be1b1c86cb97e9115"}, + {file = "casadi-3.6.3-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:387e4832a6b98ecf71d7c6ceb129ca7bfe120dd6705162aecee70df13bd8cb62"}, + {file = "casadi-3.6.3-cp311-none-manylinux2014_i686.whl", hash = "sha256:8357cfae956d1c4d2c8e765e93fcfad537f83e8278d2bd7c9d4f66213fdc6f4e"}, + {file = "casadi-3.6.3-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:de87a486a4578f609bb93327004ee0a4598723cff5c0118828e1de31f01ea65b"}, + {file = "casadi-3.6.3-cp311-none-win_amd64.whl", hash = "sha256:d5b5d49b3749aa075678206645b106fea104485285441f771ba5ef347d4bc394"}, + {file = "casadi-3.6.3-cp35-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:9ba455b1afc4b70249810098458951bd37cb346350ed00e913ec93ddb3f15251"}, + {file = "casadi-3.6.3-cp35-none-manylinux1_i686.whl", hash = "sha256:397b3754a0560e9c4ebb3ce9220bfe5194f6ef63dca6caa6695b7e43a170895b"}, + {file = "casadi-3.6.3-cp35-none-manylinux2010_x86_64.whl", hash = "sha256:406423816ef79ba82e579bd999ba9aaa16ffcb071261d8ff32e3c404ee1e816b"}, + {file = "casadi-3.6.3-cp35-none-win_amd64.whl", hash = "sha256:62fcabf79dfc1f3b7cf06eca1408556bc74a59a9de9dde4b779eff20ac243006"}, + {file = "casadi-3.6.3-cp36-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:7972b476b20557592fd1193140e6d1067a91b98cbc96767b3ec2f68e500c5342"}, + {file = "casadi-3.6.3-cp36-none-manylinux2014_aarch64.whl", hash = "sha256:692c284d838d11052c08fc8b38357858607f701ea6495bf9e22a0f2033811325"}, + {file = "casadi-3.6.3-cp36-none-manylinux2014_i686.whl", hash = "sha256:e5bb5ae64de87568c5c3895ca649b209283e2ad61ddeaef1685c7e856e4044e0"}, + {file = "casadi-3.6.3-cp36-none-manylinux2014_x86_64.whl", hash = "sha256:6b15e0bf452f7b3dface72e7d75ef2fe8566574abec51313540a6a251243e099"}, + {file = "casadi-3.6.3-cp36-none-win_amd64.whl", hash = "sha256:b7148bc8b107b1bae4f66fe5b815edb69aca69de8ead2ee25e1b059781654550"}, + {file = "casadi-3.6.3-cp37-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:3a184a4930a046ee788923a129032372f043027df7a2e08d25fd1fe72d1da8bd"}, + {file = "casadi-3.6.3-cp37-none-manylinux2014_aarch64.whl", hash = "sha256:44ceea824512a594b286fff7646470d7ac58e363bcb40ce23a6ec0577e6af3f9"}, + {file = "casadi-3.6.3-cp37-none-manylinux2014_i686.whl", hash = "sha256:ce42fb3df4e795bbb2178c4f9929bd8d0d359890ae1225c64a0c2795374fda25"}, + {file = "casadi-3.6.3-cp37-none-manylinux2014_x86_64.whl", hash = "sha256:39a73fa7383862abae27ebe4158d23d588286b22d74505af8569f4b91acc51d7"}, + {file = "casadi-3.6.3-cp37-none-win_amd64.whl", hash = "sha256:bede02743ac570b6289e8496563aa326dd4fdecc8c3e4f817b909cf6bc1b42cc"}, + {file = "casadi-3.6.3-cp38-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:7e80e8eee3bb174f941d592dbc440f57c76c5d81bad59f3a361dbf47e6175a43"}, + {file = "casadi-3.6.3-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:c0bcd64e8bb0db6e6dcc24d1ee10ef7f2305ed2e9d2786c1dcae43e7fcd05943"}, + {file = "casadi-3.6.3-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:012970fec19a88326e010b8e8ef4d569e32246dabb8cad86c485263e3961f3b1"}, + {file = "casadi-3.6.3-cp38-none-manylinux2014_i686.whl", hash = "sha256:4c718672247d12b437f5b8b2f62eae5c6541bd9125f233ae4977fbf3b2ff2de7"}, + {file = "casadi-3.6.3-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:3a3685525a278f5b450b8be9ab14bb367f2d0fc97f8400021ba406f63fab7795"}, + {file = "casadi-3.6.3-cp38-none-win_amd64.whl", hash = "sha256:0e6f2ae61abb3969ccf9503e2ee576a4f5b437ba40a082e26660a5d9513c839c"}, + {file = "casadi-3.6.3-cp39-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:7093cb7182db81b6184148121d61a68876ff9be55fb73c456927a344ff5983a2"}, + {file = "casadi-3.6.3-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:0a5383b295a499312657eb5c99a0e8f22e8bb06da774663bae69b322ec34dd6d"}, + {file = "casadi-3.6.3-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:6632b30197aa0d491afb7f6d30980636342eaf8a88fe7680c5eb3a37d4013c01"}, + {file = "casadi-3.6.3-cp39-none-manylinux2014_i686.whl", hash = "sha256:5990366a5266dda537d45c6df583001a1519b86db00e1725e2d17304500e511d"}, + {file = "casadi-3.6.3-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:5177b2592f4c147fec882fad92b7282262979bdf8ad5a337f6531464eee19226"}, + {file = "casadi-3.6.3-cp39-none-win_amd64.whl", hash = "sha256:d5b917443733123c634dbde7e5f971ffb87994af01c0d0d528f6d10ac78d97f7"}, + {file = "casadi-3.6.3.tar.gz", hash = "sha256:2a953bd001327c9ae79018a1efa455852c8a4b4f47f5bdda5f0a07ec820d1880"}, ] certifi = [ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, @@ -7052,27 +7166,37 @@ omegaconf = [ {file = "omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7"}, ] onnx = [ - {file = "onnx-1.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bdbd2578424c70836f4d0f9dda16c21868ddb07cc8192f9e8a176908b43d694b"}, - {file = "onnx-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213e73610173f6b2e99f99a4b0636f80b379c417312079d603806e48ada4ca8b"}, - {file = "onnx-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fd2f4e23078df197bb76a59b9cd8f5a43a6ad2edc035edb3ecfb9042093e05a"}, - {file = "onnx-1.12.0-cp310-cp310-win32.whl", hash = "sha256:23781594bb8b7ee985de1005b3c601648d5b0568a81e01365c48f91d1f5648e4"}, - {file = "onnx-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:81a3555fd67be2518bf86096299b48fb9154652596219890abfe90bd43a9ec13"}, - {file = "onnx-1.12.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:5578b93dc6c918cec4dee7fb7d9dd3b09d338301ee64ca8b4f28bc217ed42dca"}, - {file = "onnx-1.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c11162ffc487167da140f1112f49c4f82d815824f06e58bc3095407699f05863"}, - {file = "onnx-1.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341c7016e23273e9ffa9b6e301eee95b8c37d0f04df7cedbdb169d2c39524c96"}, - {file = "onnx-1.12.0-cp37-cp37m-win32.whl", hash = "sha256:3c6e6bcffc3f5c1e148df3837dc667fa4c51999788c1b76b0b8fbba607e02da8"}, - {file = "onnx-1.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8a7aa61aea339bd28f310f4af4f52ce6c4b876386228760b16308efd58f95059"}, - {file = "onnx-1.12.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:56ceb7e094c43882b723cfaa107d85ad673cfdf91faeb28d7dcadacca4f43a07"}, - {file = "onnx-1.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3629e8258db15d4e2c9b7f1be91a3186719dd94661c218c6f5fde3cc7de3d4d"}, - {file = "onnx-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d9a7db54e75529160337232282a4816cc50667dc7dc34be178fd6f6b79d4705"}, - {file = "onnx-1.12.0-cp38-cp38-win32.whl", hash = "sha256:fea5156a03398fe0e23248042d8651c1eaac5f6637d4dd683b4c1f1320b9f7b4"}, - {file = "onnx-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:f66d2996e65f490a57b3ae952e4e9189b53cc9fe3f75e601d50d4db2dc1b1cd9"}, - {file = "onnx-1.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c39a7a0352c856f1df30dccf527eb6cb4909052e5eaf6fa2772a637324c526aa"}, - {file = "onnx-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab13feb4d94342aae6d357d480f2e47d41b9f4e584367542b21ca6defda9e0a"}, - {file = "onnx-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7a9b3ea02c30efc1d2662337e280266aca491a8e86be0d8a657f874b7cccd1e"}, - {file = "onnx-1.12.0-cp39-cp39-win32.whl", hash = "sha256:f8800f28c746ab06e51ef8449fd1215621f4ddba91be3ffc264658937d38a2af"}, - {file = "onnx-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:af90427ca04c6b7b8107c2021e1273227a3ef1a7a01f3073039cae7855a59833"}, - {file = "onnx-1.12.0.tar.gz", hash = "sha256:13b3e77d27523b9dbf4f30dfc9c959455859d5e34e921c44f712d69b8369eff9"}, + {file = "onnx-1.14.0-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:fb35c2c347486416f87f41557242c05d7ee804d3676c6c8c98eef6f5b1889e7b"}, + {file = "onnx-1.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd683d4aa6d55365582055a6c1e10a55d6c08a59e9216cbb67e37ad3a5b2b980"}, + {file = "onnx-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00b0d2620c10dcb9ec33441e807dc5851d2843d445e0faab5e22c8ad6874a67a"}, + {file = "onnx-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01893a4a2d70b68e8ee20269ccde4069a6fd243dc9e296643e2afeb0050527bc"}, + {file = "onnx-1.14.0-cp310-cp310-win32.whl", hash = "sha256:0753b0f118be71ff109dd994a3d6769e5871e9feaddfada77931c63f9de534b3"}, + {file = "onnx-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8c3a2354d9d997c7a4a5e467b5373c98dc549d4a33c77d5723e1eda7e87559c"}, + {file = "onnx-1.14.0-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:5e780fd1ed25493596a141e93303d0b2897acb9ebfdee7047a916d8f8e525ab3"}, + {file = "onnx-1.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9d28d64cbac3ebdc0c9761a300340c60ec60316099906e354e5059e90335fb3b"}, + {file = "onnx-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba92fed1aa27cba385bc3890fbbe6484603e837e67c957b22899f93c70990cc4"}, + {file = "onnx-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fab7e6e1c2d9d6479edad8e9088cdfd87ea293cb08f31565adabfb33c6e5789"}, + {file = "onnx-1.14.0-cp311-cp311-win32.whl", hash = "sha256:6e966f5ef38a0521595cad6a1d14d9ae205c593d2824d8c1fa044fa5ba15370d"}, + {file = "onnx-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:1fe8ba794d261d722018bd1385f02f966aace0fcb5448881ab5dd55ab0ebb81b"}, + {file = "onnx-1.14.0-cp37-cp37m-macosx_10_12_universal2.whl", hash = "sha256:c16dacf577700ff9cb076c61c880d1a4bc612eed96280396a54ee1e1bd7e2d68"}, + {file = "onnx-1.14.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:bbdca51da9fa9ec43eebd8c640bf71c05daa2afbeaa2c6478466470e28e41111"}, + {file = "onnx-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3315c304d23a06ebd07fffe2456ab7f1e0a8dba317393d5c17a671ae2da6645e"}, + {file = "onnx-1.14.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1545159f2e7fbc5b4a3ae032cd4d9ddeafc62c4f27fe22cbc3ecff49338992"}, + {file = "onnx-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:18cd98f7e234e268cb60c47a1f8ea5f6ffba50fe11de924b17498b1571d0cd2c"}, + {file = "onnx-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a8f7454acded506b6359ee0837c8527c64964973d7d25ed6b16b7d4314599502"}, + {file = "onnx-1.14.0-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a9702e7dd120bca421a820020151cbb1003077e17ded29cc8d44ff32a9a57ad8"}, + {file = "onnx-1.14.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:369c3ecace7e8c7df6efbcbc712b262626796ae4a83decd29111afafa025a30c"}, + {file = "onnx-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fbcdc1a0c1057785bc5f7254aca0cf0b49d19c74696f1ade107638054157315"}, + {file = "onnx-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed099fbdada4accead109a4479d5f73fb974566cce8d3c6fca94774f9645934c"}, + {file = "onnx-1.14.0-cp38-cp38-win32.whl", hash = "sha256:296e689aa54a9ae4e560b2bb149a64e96775699a0624af5f631665b9cda90482"}, + {file = "onnx-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:e1607f97007515df303c1f40b77363545af99a1f32d2f73240c8aa526cdbd109"}, + {file = "onnx-1.14.0-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:7800b6ec74b1fe3fbb3bf4a2380e2f4007c1a7f2d6927599ad40eead6eae5e19"}, + {file = "onnx-1.14.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:45d3effe59e20d0a9fdc51f5bb8f38299086c79576b894ed945e6a058c4b210a"}, + {file = "onnx-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a593b46015326feb949781d030cb1d0d5d388cca52bff2e2995badf55d56b38d"}, + {file = "onnx-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54614942574415ef3f0bce0800c6f41ecea8201f8042754e204ee8c0a8e473e1"}, + {file = "onnx-1.14.0-cp39-cp39-win32.whl", hash = "sha256:dcfaeb2d15e93c456003fac13ffa35144ba9d2666a83e2cef650dd5c90a2b768"}, + {file = "onnx-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:0639427ac61e5a0181f4f7c89f9fc82b3c9715c95071f9c3de79bbe303a4ae65"}, + {file = "onnx-1.14.0.tar.gz", hash = "sha256:43b85087c6b919de66872a043c7f4899fe6f840e11ffca7e662b2ce9e4cc2927"}, ] onnx2torch = [ {file = "onnx2torch-1.5.4-py3-none-any.whl", hash = "sha256:fd1a0fe05072bfb9f3d86d9330299b130b41f11bd4ae634db17078974e711725"}, @@ -7091,14 +7215,14 @@ onnxoptimizer = [ {file = "onnxoptimizer-0.3.1.tar.gz", hash = "sha256:0aa2e873a49f3762822e4400e1e8886236156f9d1dbf20319e2c18f7ebfb6a1d"}, ] onnxruntime-gpu = [ - {file = "onnxruntime_gpu-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b0393c5122ed90fa2eb76192a486261d86e9526ccb78b2a98923c22791d2d1"}, - {file = "onnxruntime_gpu-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:ecfe97335027e569d4f46725ba89316041e562b8c499690e25e11cfee4601cd1"}, - {file = "onnxruntime_gpu-1.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2be6f7f5a1ce0bc8471ce42e10eab92cfb19d0748b857edcb5320b5e98311b7"}, - {file = "onnxruntime_gpu-1.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d73204323aefebe4eecab9fcf76e22b1a00394e3d838c2962a28a27301186b73"}, - {file = "onnxruntime_gpu-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37872527d03d3df10756408ca44014bd6ac354a044ab1c4286cd42dc138e518"}, - {file = "onnxruntime_gpu-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:296bd9733986cb7517d15bef5535c555d3f863963a71e6575e92d2a854aee61d"}, - {file = "onnxruntime_gpu-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e46d0724ce54c5908c5760037b78de741fbd48962b370c29ebc20e608b30eda"}, - {file = "onnxruntime_gpu-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:fd919373be35b9ba54210688265df38ad5e19a530449385c40dab51da407345d"}, + {file = "onnxruntime_gpu-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0cd0d67a4402222dacd02558e261890724f38e3dfcffedacc67d4575ebcdac"}, + {file = "onnxruntime_gpu-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:5e48ae99d3692b71cd95f75eb0c556bf196b708cb0dd760dbbb96720d81a00a2"}, + {file = "onnxruntime_gpu-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0a1af33d6539a2dae9c7a8473988a1358e036b3c9dea09ff5b61f5bc2eb603"}, + {file = "onnxruntime_gpu-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:53bba07b335082ff26c834bb43bd12ed772221aa69e0d454b750c0df0d0ea719"}, + {file = "onnxruntime_gpu-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:134c83e21f02f0629a7e0b2eb400a2cc16d088ac332822e04bafc57f84bfc77c"}, + {file = "onnxruntime_gpu-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:faeae730818546833fb2589feed03176f5469ef6cd06a0c5e5f5b3eec7ac84ec"}, + {file = "onnxruntime_gpu-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58773200fbe3a840cab163fc9f196a3de376a2902d633a714ef13501910c4dfc"}, + {file = "onnxruntime_gpu-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:b9fb31cb7608b4a0e84ba3325860f0b5159ccea5fe43e74eecf6f360021b987f"}, ] opencv-python-headless = [] osmium = [ @@ -7361,30 +7485,28 @@ prompt-toolkit = [ {file = "prompt_toolkit-3.0.31.tar.gz", hash = "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148"}, ] protobuf = [ - {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, - {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, - {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, - {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, - {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, - {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, - {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, - {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, - {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, - {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, - {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, - {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, - {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, - {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, - {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, - {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, + {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, + {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, + {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, + {file = "protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"}, + {file = "protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"}, + {file = "protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"}, + {file = "protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"}, + {file = "protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"}, + {file = "protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"}, + {file = "protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"}, + {file = "protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"}, + {file = "protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"}, + {file = "protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"}, + {file = "protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"}, + {file = "protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"}, + {file = "protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"}, + {file = "protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"}, + {file = "protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"}, + {file = "protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"}, + {file = "protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"}, + {file = "protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"}, + {file = "protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"}, ] psutil = [ {file = "psutil-5.9.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71"}, @@ -8011,6 +8133,96 @@ redis = [ {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, ] +regex = [ + {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"}, + {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"}, + {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"}, + {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"}, + {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"}, + {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"}, + {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"}, + {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"}, + {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"}, + {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"}, + {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"}, + {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"}, + {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"}, + {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"}, + {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"}, + {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"}, + {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"}, +] requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, @@ -8077,6 +8289,48 @@ s2sphere = [ {file = "s2sphere-0.2.5-py2.py3-none-any.whl", hash = "sha256:d2340c9cf458ddc9a89afd1d8048a4195ce6fa6b0095ab900d4be5271e537401"}, {file = "s2sphere-0.2.5.tar.gz", hash = "sha256:c2478c1ff7c601a59a7151a57b605435897514578fa6bdb8730721c182adbbaf"}, ] +safetensors = [ + {file = "safetensors-0.3.1-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:2ae9b7dd268b4bae6624729dac86deb82104820e9786429b0583e5168db2f770"}, + {file = "safetensors-0.3.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:08c85c1934682f1e2cd904d38433b53cd2a98245a7cc31f5689f9322a2320bbf"}, + {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba625c7af9e1c5d0d91cb83d2fba97d29ea69d4db2015d9714d24c7f6d488e15"}, + {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b57d5890c619ec10d9f1b6426b8690d0c9c2868a90dc52f13fae6f6407ac141f"}, + {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c9f562ea696d50b95cadbeb1716dc476714a87792ffe374280c0835312cbfe2"}, + {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c115951b3a865ece8d98ee43882f2fd0a999c0200d6e6fec24134715ebe3b57"}, + {file = "safetensors-0.3.1-cp310-cp310-win32.whl", hash = "sha256:118f8f7503ea312fc7af27e934088a1b589fb1eff5a7dea2cd1de6c71ee33391"}, + {file = "safetensors-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:54846eaae25fded28a7bebbb66be563cad221b4c80daee39e2f55df5e5e0266f"}, + {file = "safetensors-0.3.1-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:5af82e10946c4822506db0f29269f43147e889054704dde994d4e22f0c37377b"}, + {file = "safetensors-0.3.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:626c86dd1d930963c8ea7f953a3787ae85322551e3a5203ac731d6e6f3e18f44"}, + {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12e30677e6af1f4cc4f2832546e91dbb3b0aa7d575bfa473d2899d524e1ace08"}, + {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d534b80bc8d39945bb902f34b0454773971fe9e5e1f2142af451759d7e52b356"}, + {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ddd0ddd502cf219666e7d30f23f196cb87e829439b52b39f3e7da7918c3416df"}, + {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997a2cc14023713f423e6d16536d55cb16a3d72850f142e05f82f0d4c76d383b"}, + {file = "safetensors-0.3.1-cp311-cp311-win32.whl", hash = "sha256:6ae9ca63d9e22f71ec40550207bd284a60a6b4916ae6ca12c85a8d86bf49e0c3"}, + {file = "safetensors-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:62aa7421ca455418423e35029524489480adda53e3f702453580180ecfebe476"}, + {file = "safetensors-0.3.1-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:6d54b3ed367b6898baab75dfd057c24f36ec64d3938ffff2af981d56bfba2f42"}, + {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:262423aeda91117010f8c607889066028f680fbb667f50cfe6eae96f22f9d150"}, + {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10efe2513a8327fd628cea13167089588acc23093ba132aecfc536eb9a4560fe"}, + {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:689b3d6a7ebce70ee9438267ee55ea89b575c19923876645e927d08757b552fe"}, + {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14cd9a87bc73ce06903e9f8ee8b05b056af6f3c9f37a6bd74997a16ed36ff5f4"}, + {file = "safetensors-0.3.1-cp37-cp37m-win32.whl", hash = "sha256:a77cb39624480d5f143c1cc272184f65a296f573d61629eff5d495d2e0541d3e"}, + {file = "safetensors-0.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9eff3190bfbbb52eef729911345c643f875ca4dbb374aa6c559675cfd0ab73db"}, + {file = "safetensors-0.3.1-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:05cbfef76e4daa14796db1bbb52072d4b72a44050c368b2b1f6fd3e610669a89"}, + {file = "safetensors-0.3.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:c49061461f4a81e5ec3415070a3f135530834c89cbd6a7db7cd49e3cb9d9864b"}, + {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cf7e73ca42974f098ce0cf4dd8918983700b6b07a4c6827d50c8daefca776e"}, + {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04f909442d6223ff0016cd2e1b2a95ef8039b92a558014627363a2e267213f62"}, + {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c573c5a0d5d45791ae8c179e26d74aff86e719056591aa7edb3ca7be55bc961"}, + {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6994043b12e717cf2a6ba69077ac41f0d3675b2819734f07f61819e854c622c7"}, + {file = "safetensors-0.3.1-cp38-cp38-win32.whl", hash = "sha256:158ede81694180a0dbba59422bc304a78c054b305df993c0c6e39c6330fa9348"}, + {file = "safetensors-0.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:afdc725beff7121ea8d39a7339f5a6abcb01daa189ea56290b67fe262d56e20f"}, + {file = "safetensors-0.3.1-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:cba910fcc9e5e64d32d62b837388721165e9c7e45d23bc3a38ad57694b77f40d"}, + {file = "safetensors-0.3.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a4f7dbfe7285573cdaddd85ef6fa84ebbed995d3703ab72d71257944e384612f"}, + {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54aed0802f9eaa83ca7b1cbb986bfb90b8e2c67b6a4bcfe245627e17dad565d4"}, + {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34b75a766f3cfc99fd4c33e329b76deae63f5f388e455d863a5d6e99472fca8e"}, + {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a0f31904f35dc14919a145b2d7a2d8842a43a18a629affe678233c4ea90b4af"}, + {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcf527ecc5f58907fd9031510378105487f318cc91ecdc5aee3c7cc8f46030a8"}, + {file = "safetensors-0.3.1-cp39-cp39-win32.whl", hash = "sha256:e2f083112cf97aa9611e2a05cc170a2795eccec5f6ff837f4565f950670a9d83"}, + {file = "safetensors-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f4f614b8e8161cd8a9ca19c765d176a82b122fa3d3387b77862145bfe9b4e93"}, + {file = "safetensors-0.3.1.tar.gz", hash = "sha256:571da56ff8d0bec8ae54923b621cda98d36dcef10feb36fd492c4d0c2cd0e869"}, +] scikit-image = [ {file = "scikit-image-0.19.3.tar.gz", hash = "sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450"}, {file = "scikit_image-0.19.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8"}, @@ -8530,6 +8784,48 @@ tinycss2 = [ {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, ] +tokenizers = [ + {file = "tokenizers-0.13.3-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:f3835c5be51de8c0a092058a4d4380cb9244fb34681fd0a295fbf0a52a5fdf33"}, + {file = "tokenizers-0.13.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4ef4c3e821730f2692489e926b184321e887f34fb8a6b80b8096b966ba663d07"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5fd1a6a25353e9aa762e2aae5a1e63883cad9f4e997c447ec39d071020459bc"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee0b1b311d65beab83d7a41c56a1e46ab732a9eed4460648e8eb0bd69fc2d059"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ef4215284df1277dadbcc5e17d4882bda19f770d02348e73523f7e7d8b8d396"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d53976079cff8a033f778fb9adca2d9d69d009c02fa2d71a878b5f3963ed30"}, + {file = "tokenizers-0.13.3-cp310-cp310-win32.whl", hash = "sha256:1f0e3b4c2ea2cd13238ce43548959c118069db7579e5d40ec270ad77da5833ce"}, + {file = "tokenizers-0.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:89649c00d0d7211e8186f7a75dfa1db6996f65edce4b84821817eadcc2d3c79e"}, + {file = "tokenizers-0.13.3-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:56b726e0d2bbc9243872b0144515ba684af5b8d8cd112fb83ee1365e26ec74c8"}, + {file = "tokenizers-0.13.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc5c022ce692e1f499d745af293ab9ee6f5d92538ed2faf73f9708c89ee59ce6"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55c981ac44ba87c93e847c333e58c12abcbb377a0c2f2ef96e1a266e4184ff2"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f247eae99800ef821a91f47c5280e9e9afaeed9980fc444208d5aa6ba69ff148"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b3e3215d048e94f40f1c95802e45dcc37c5b05eb46280fc2ccc8cd351bff839"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ba2b0bf01777c9b9bc94b53764d6684554ce98551fec496f71bc5be3a03e98b"}, + {file = "tokenizers-0.13.3-cp311-cp311-win32.whl", hash = "sha256:cc78d77f597d1c458bf0ea7c2a64b6aa06941c7a99cb135b5969b0278824d808"}, + {file = "tokenizers-0.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:ecf182bf59bd541a8876deccf0360f5ae60496fd50b58510048020751cf1724c"}, + {file = "tokenizers-0.13.3-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:0527dc5436a1f6bf2c0327da3145687d3bcfbeab91fed8458920093de3901b44"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cbb2c307627dc99b44b22ef05ff4473aa7c7cc1fec8f0a8b37d8a64b1a16d2"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4560dbdeaae5b7ee0d4e493027e3de6d53c991b5002d7ff95083c99e11dd5ac0"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64064bd0322405c9374305ab9b4c07152a1474370327499911937fd4a76d004b"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8c6e2ab0f2e3d939ca66aa1d596602105fe33b505cd2854a4c1717f704c51de"}, + {file = "tokenizers-0.13.3-cp37-cp37m-win32.whl", hash = "sha256:6cc29d410768f960db8677221e497226e545eaaea01aa3613fa0fdf2cc96cff4"}, + {file = "tokenizers-0.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fc2a7fdf864554a0dacf09d32e17c0caa9afe72baf9dd7ddedc61973bae352d8"}, + {file = "tokenizers-0.13.3-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:8791dedba834c1fc55e5f1521be325ea3dafb381964be20684b92fdac95d79b7"}, + {file = "tokenizers-0.13.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:d607a6a13718aeb20507bdf2b96162ead5145bbbfa26788d6b833f98b31b26e1"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3791338f809cd1bf8e4fee6b540b36822434d0c6c6bc47162448deee3f77d425"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2f35f30e39e6aab8716f07790f646bdc6e4a853816cc49a95ef2a9016bf9ce6"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310204dfed5aa797128b65d63538a9837cbdd15da2a29a77d67eefa489edda26"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0f9b92ea052305166559f38498b3b0cae159caea712646648aaa272f7160963"}, + {file = "tokenizers-0.13.3-cp38-cp38-win32.whl", hash = "sha256:9a3fa134896c3c1f0da6e762d15141fbff30d094067c8f1157b9fdca593b5806"}, + {file = "tokenizers-0.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:8e7b0cdeace87fa9e760e6a605e0ae8fc14b7d72e9fc19c578116f7287bb873d"}, + {file = "tokenizers-0.13.3-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:00cee1e0859d55507e693a48fa4aef07060c4bb6bd93d80120e18fea9371c66d"}, + {file = "tokenizers-0.13.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a23ff602d0797cea1d0506ce69b27523b07e70f6dda982ab8cf82402de839088"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ce07445050b537d2696022dafb115307abdffd2a5c106f029490f84501ef97"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:280ffe95f50eaaf655b3a1dc7ff1d9cf4777029dbbc3e63a74e65a056594abc3"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97acfcec592f7e9de8cadcdcda50a7134423ac8455c0166b28c9ff04d227b371"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7730c98a3010cd4f523465867ff95cd9d6430db46676ce79358f65ae39797b"}, + {file = "tokenizers-0.13.3-cp39-cp39-win32.whl", hash = "sha256:48625a108029cb1ddf42e17a81b5a3230ba6888a70c9dc14e81bc319e812652d"}, + {file = "tokenizers-0.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:bc0a6f1ba036e482db6453571c9e3e60ecd5489980ffd95d11dc9f960483d783"}, + {file = "tokenizers-0.13.3.tar.gz", hash = "sha256:2e546dbb68b623008a5442353137fbb0123d311a6d7ba52f2667c8862a75af2e"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -8569,6 +8865,10 @@ traitlets = [ {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, ] +transformers = [ + {file = "transformers-4.30.2-py3-none-any.whl", hash = "sha256:c332e3a3097f9ed89ce556b403251235931c00237b8bc2d7adaa19d226c13f1d"}, + {file = "transformers-4.30.2.tar.gz", hash = "sha256:f4a8aac4e1baffab4033f4a345b0d7dc7957d12a4f1ba969afea08205a513045"}, +] triton = [ {file = "triton-2.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38806ee9663f4b0f7cd64790e96c579374089e58f49aac4a6608121aa55e2505"}, {file = "triton-2.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:226941c7b8595219ddef59a1fdb821e8c744289a132415ddd584facedeb475b1"}, diff --git a/pyproject.toml b/pyproject.toml index 8877a262ea..4f48c67b6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ documentation = "https://docs.comma.ai" [tool.poetry.dependencies] python = "~3.8" atomicwrites = "^1.4.0" -casadi = { version = "==3.5.5", platform = "linux" } +casadi = "==3.6.3" cffi = "^1.15.1" crcmod = "^1.7" cryptography = "^37.0.4" @@ -28,11 +28,11 @@ json-rpc = "^1.13.0" libusb1 = "^3.0.0" nose = "^1.3.7" numpy = "^1.23.0" -onnx = "^1.12.0" -onnxruntime-gpu = { version = "^1.11.1", platform = "linux", markers = "platform_machine == 'x86_64'" } +onnx = "^1.14.0" +onnxruntime-gpu = { version = "^1.15.1", platform = "linux", markers = "platform_machine == 'x86_64'" } pillow = "^9.2.0" poetry = "==1.2.2" -protobuf = "==3.20.1" +protobuf = "==3.20.3" psutil = "^5.9.1" pycapnp = "==1.1.0" pycryptodome = "^3.15.0" @@ -178,6 +178,7 @@ omegaconf = "^2.3.0" osmnx = "==1.2.2" tritonclient = {version = "2.28.0", extras = ["http"]} tensorrt = "^8.6.0" +transformers = "^4.29.2" [build-system] diff --git a/release/files_common b/release/files_common index e90a8e083b..1c4955b96f 100644 --- a/release/files_common +++ b/release/files_common @@ -290,6 +290,7 @@ selfdrive/thermald/power_monitoring.py selfdrive/thermald/fan_controller.py selfdrive/test/__init__.py +selfdrive/test/fuzzy_generation.py selfdrive/test/helpers.py selfdrive/test/setup_device_ci.sh selfdrive/test/test_onroad.py @@ -438,6 +439,7 @@ third_party/libyuv/larch64/** third_party/snpe/include/** third_party/snpe/dsp** +third_party/acados/.gitignore third_party/acados/x86_64/** third_party/acados/larch64/** third_party/acados/include/** diff --git a/selfdrive/assets/navigation/icon_directions.svg b/selfdrive/assets/navigation/icon_directions.svg new file mode 100644 index 0000000000..66009ac43b --- /dev/null +++ b/selfdrive/assets/navigation/icon_directions.svg @@ -0,0 +1 @@ + diff --git a/selfdrive/assets/navigation/icon_directions_outlined.svg b/selfdrive/assets/navigation/icon_directions_outlined.svg new file mode 100644 index 0000000000..4d31bfd93c --- /dev/null +++ b/selfdrive/assets/navigation/icon_directions_outlined.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_favorite.svg b/selfdrive/assets/navigation/icon_favorite.svg new file mode 100644 index 0000000000..ba64df4ab9 --- /dev/null +++ b/selfdrive/assets/navigation/icon_favorite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_home.svg b/selfdrive/assets/navigation/icon_home.svg new file mode 100644 index 0000000000..cb87011090 --- /dev/null +++ b/selfdrive/assets/navigation/icon_home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_recent.svg b/selfdrive/assets/navigation/icon_recent.svg new file mode 100644 index 0000000000..668aa38209 --- /dev/null +++ b/selfdrive/assets/navigation/icon_recent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_settings.svg b/selfdrive/assets/navigation/icon_settings.svg new file mode 100644 index 0000000000..134cc0f31f --- /dev/null +++ b/selfdrive/assets/navigation/icon_settings.svg @@ -0,0 +1 @@ + diff --git a/selfdrive/assets/navigation/icon_work.svg b/selfdrive/assets/navigation/icon_work.svg new file mode 100644 index 0000000000..c1ea6c5e3b --- /dev/null +++ b/selfdrive/assets/navigation/icon_work.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/screenshot.png b/selfdrive/assets/navigation/screenshot.png deleted file mode 100644 index 3e89c04759..0000000000 Binary files a/selfdrive/assets/navigation/screenshot.png and /dev/null differ diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 948fcc07a5..8bd52d3772 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -159,7 +159,7 @@ class TestAthenadMethods(unittest.TestCase): resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {}) self.assertEqual(resp['enqueued'], 1) self.assertNotIn('failed', resp) - self.assertDictContainsSubset({"path": fn, "url": f"{host}/qlog.bz2", "headers": {}}, resp['items'][0]) + self.assertLessEqual({"path": fn, "url": f"{host}/qlog.bz2", "headers": {}}.items(), resp['items'][0].items()) self.assertIsNotNone(resp['items'][0].get('id')) self.assertEqual(athenad.upload_queue.qsize(), 1) diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index 859a30a1bc..6ab6d05b08 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -10,6 +10,7 @@ from functools import cmp_to_key from panda import Panda, PandaDFU, FW_PATH from common.basedir import BASEDIR from common.params import Params +from selfdrive.boardd.set_time import set_time from system.hardware import HARDWARE from system.swaglog import cloudlog @@ -133,6 +134,11 @@ def main() -> NoReturn: cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) if first_run: + if panda.is_internal(): + # update time from RTC + set_time(cloudlog) + + # reset panda to ensure we're in a good state cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") if panda.is_internal(): HARDWARE.reset_internal_panda() diff --git a/selfdrive/boardd/spi.cc b/selfdrive/boardd/spi.cc index e10fd744d5..1c4369a8ff 100644 --- a/selfdrive/boardd/spi.cc +++ b/selfdrive/boardd/spi.cc @@ -220,21 +220,31 @@ bool check_checksum(uint8_t *data, int data_len) { int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout) { int ret; - int count = 0; + int nack_count = 0; + int timeout_count = 0; bool timed_out = false; double start_time = millis_since_boot(); + do { ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len, timeout); if (ret < 0) { - timed_out = (timeout != 0) && (count > 5); - count += ret == SpiError::ACK_TIMEOUT; + timed_out = (timeout != 0) && (timeout_count > 5); + timeout_count += ret == SpiError::ACK_TIMEOUT; + + // give other threads a chance to run std::this_thread::yield(); + + if (ret == SpiError::NACK) { + // prevent busy wait while the panda is NACK'ing + nack_count += 1; + usleep(std::clamp(nack_count*10, 200, 2000)); + } } } while (ret < 0 && connected && !timed_out); if (ret < 0) { - LOGE("transfer failed, after %d tries, %.2fms", count, millis_since_boot() - start_time); + LOGE("transfer failed, after %d tries, %.2fms", timeout_count, millis_since_boot() - start_time); } return ret; @@ -250,7 +260,7 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout) spi_ioc_transfer transfer = { .tx_buf = (uint64_t)tx_buf, .rx_buf = (uint64_t)rx_buf, - .delay_usecs = 5, + .delay_usecs = 10, .len = 1 }; tx_buf[0] = tx; @@ -274,6 +284,9 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout) LOGD("SPI: timed out waiting for ACK"); return SpiError::ACK_TIMEOUT; } + + // backoff + transfer.delay_usecs = std::clamp(transfer.delay_usecs*2, 10, 250); } return 0; diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 3d8ea22ef2..ce7d797443 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -79,6 +79,7 @@ interfaces = load_interfaces(interface_names) def fingerprint(logcan, sendcan, num_pandas): fixed_fingerprint = os.environ.get('FINGERPRINT', "") skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) + disable_fw_cache = os.environ.get('DISABLE_FW_CACHE', False) ecu_rx_addrs = set() params = Params() @@ -92,7 +93,8 @@ def fingerprint(logcan, sendcan, num_pandas): if cached_params.carName == "mock": cached_params = None - if cached_params is not None and len(cached_params.carFw) > 0 and cached_params.carVin is not VIN_UNKNOWN: + if cached_params is not None and len(cached_params.carFw) > 0 and \ + cached_params.carVin is not VIN_UNKNOWN and not disable_fw_cache: cloudlog.warning("Using cached CarParams") vin, vin_rx_addr = cached_params.carVin, 0 car_fw = list(cached_params.carFw) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index e86807bf55..084aef2aa9 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -167,7 +167,7 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [CHRYSLER_VERSION_REQUEST], [CHRYSLER_VERSION_RESPONSE], - whitelist_ecus=[Ecu.abs, Ecu.hcp, Ecu.engine, Ecu.transmission], + whitelist_ecus=[Ecu.abs, Ecu.hybrid, Ecu.engine, Ecu.transmission], bus=0, ), Request( @@ -178,8 +178,8 @@ FW_QUERY_CONFIG = FwQueryConfig( ), ], extra_ecus=[ - (Ecu.hcp, 0x7e2, None), # manages transmission on hybrids - (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids + (Ecu.hybrid, 0x7e2, None), # manages transmission on hybrids + (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids ], ) diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index 30a53597d6..ca95ac0a0d 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -13,15 +13,15 @@ class CanBus(CanBusBase): return self.offset @property - def radar(self): + def radar(self) -> int: return self.offset + 1 @property - def camera(self): + def camera(self) -> int: return self.offset + 2 -def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray): +def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray) -> int: curvature = (dat[2] << 3) | ((dat[3]) >> 5) curvature_rate = (dat[6] << 3) | ((dat[7]) >> 5) path_angle = ((dat[3] & 0x1F) << 6) | ((dat[4]) >> 2) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index fef7fcefc8..a7d1b37505 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -16,11 +16,6 @@ class CarInterface(CarInterfaceBase): def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "ford" - # These cars are dashcam only for lack of test coverage. - # Once a user confirms each car works and a test route is - # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.FOCUS_MK4} - ret.radarUnavailable = True ret.steerControlType = car.CarParams.SteerControlType.angle ret.steerActuatorDelay = 0.2 diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index a2e9ea2442..925628c8d8 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,10 +1,12 @@ from collections import defaultdict from dataclasses import dataclass +from enum import Enum from typing import Dict, List, Set, Union from cereal import car from selfdrive.car import AngleRateLimit, dbc_dict -from selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts, Device +from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ + Device from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -57,6 +59,14 @@ class RADAR: DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR)) +class Footnote(Enum): + FOCUS = CarFootnote( + "Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in " + + "North and South America/Southeast Asia.", + Column.MODEL, + ) + + @dataclass class FordCarInfo(CarInfo): package: str = "Co-Pilot360 Assist+" @@ -77,7 +87,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { FordCarInfo("Ford Explorer 2020-22"), FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), ], - CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2018", "Adaptive Cruise Control with Lane Centering"), + CAR.FOCUS_MK4: FordCarInfo("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]), CAR.MAVERICK_MK1: FordCarInfo("Ford Maverick 2022-23", "Co-Pilot360 Assist"), } @@ -119,6 +129,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7E0, None): [ b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.ESCAPE_MK4: { @@ -141,6 +152,7 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7E0, None): [ b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6A-14C204-BJX\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MX6A-14C204-BEJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index 7ae9bee404..f9f8e30a68 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -3,7 +3,7 @@ import capnp import copy from dataclasses import dataclass, field import struct -from typing import Dict, List, Optional, Tuple +from typing import Callable, Dict, List, Optional, Set, Tuple import panda.python.uds as uds @@ -74,6 +74,9 @@ class FwQueryConfig: non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict) # Ecus added for data collection, not to be fingerprinted on extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list) + # Function a brand can implement to provide better fuzzy matching. Takes in FW versions, + # returns set of candidates. Only will match if one candidate is returned + match_fw_to_car_fuzzy: Optional[Callable[[Dict[Tuple[int, Optional[int]], Set[bytes]]], Set[str]]] = None def __post_init__(self): for i in range(len(self.requests)): diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index ce7aeb3a1e..5a7a2174c7 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -54,7 +54,7 @@ def get_brand_addrs() -> Dict[str, Set[Tuple[int, Optional[int]]]]: return dict(brand_addrs) -def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): +def match_fw_to_car_fuzzy(live_fw_versions, log=True, exclude=None): """Do a fuzzy FW match. This function will return a match, and the number of firmware version that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars the match is rejected.""" @@ -77,7 +77,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): matched_ecus = set() candidate = None - for addr, versions in fw_versions_dict.items(): + for addr, versions in live_fw_versions.items(): ecu_key = (addr[0], addr[1]) for version in versions: # All cars that have this FW response on the specified address @@ -101,7 +101,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): return set() -def match_fw_to_car_exact(fw_versions_dict, log=True) -> Set[str]: +def match_fw_to_car_exact(live_fw_versions, log=True) -> Set[str]: """Do an exact FW match. Returns all cars that match the given FW versions for a list of "essential" ECUs. If an ECU is not considered essential the FW version can be missing to get a fingerprint, but if it's present it @@ -115,7 +115,7 @@ def match_fw_to_car_exact(fw_versions_dict, log=True) -> Set[str]: ecu_type = ecu[0] addr = ecu[1:] - found_versions = fw_versions_dict.get(addr, set()) + found_versions = live_fw_versions.get(addr, set()) if not len(found_versions): # Some models can sometimes miss an ecu, or show on two different addresses if candidate in config.non_essential_ecus.get(ecu_type, []): @@ -151,6 +151,11 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True): fw_versions_dict = build_fw_dict(fw_versions, filter_brand=brand) matches |= match_func(fw_versions_dict, log=log) + # If specified and no matches so far, fall back to brand's fuzzy fingerprinting function + config = FW_QUERY_CONFIGS[brand] + if not exact_match and not len(matches) and config.match_fw_to_car_fuzzy is not None: + matches |= config.match_fw_to_car_fuzzy(fw_versions_dict) + if len(matches): return exact_match, matches @@ -250,10 +255,6 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, versions = VERSIONS.copy() params = Params() - # Each brand can define extra ECUs to query for data collection - for brand, config in FW_QUERY_CONFIGS.items(): - versions[brand]["debug"] = {ecu: [] for ecu in config.extra_ecus} - if query_brand is not None: versions = {query_brand: versions[query_brand]} @@ -267,8 +268,10 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, ecu_types = {} for brand, brand_versions in versions.items(): + config = FW_QUERY_CONFIGS[brand] for ecu in brand_versions.values(): - for ecu_type, addr, sub_addr in ecu.keys(): + # Each brand can define extra ECUs to query for data collection + for ecu_type, addr, sub_addr in list(ecu) + config.extra_ecus: a = (brand, addr, sub_addr) if a not in ecu_types: ecu_types[a] = ecu_type diff --git a/selfdrive/car/gm/tests/__init__.py b/selfdrive/car/gm/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/car/gm/tests/test_gm.py b/selfdrive/car/gm/tests/test_gm.py new file mode 100755 index 0000000000..1fc8a25611 --- /dev/null +++ b/selfdrive/car/gm/tests/test_gm.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +from parameterized import parameterized +import unittest + +from selfdrive.car.gm.values import CAMERA_ACC_CAR, CAR, FINGERPRINTS, GM_RX_OFFSET + +CAMERA_DIAGNOSTIC_ADDRESS = 0x24b + + +class TestGMFingerprint(unittest.TestCase): + @parameterized.expand(FINGERPRINTS.items()) + def test_can_fingerprints(self, car_model, fingerprints): + self.assertGreater(len(fingerprints), 0) + + # Trailblazer is in dashcam + if car_model != CAR.TRAILBLAZER: + self.assertTrue(all(len(finger) for finger in fingerprints)) + + # The camera can sometimes be communicating on startup + if car_model in CAMERA_ACC_CAR - {CAR.TRAILBLAZER}: + for finger in fingerprints: + for required_addr in (CAMERA_DIAGNOSTIC_ADDRESS, CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET): + self.assertEqual(finger.get(required_addr), 8, required_addr) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index b21c303d85..4919a13288 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -199,24 +199,26 @@ FINGERPRINTS = { }], CAR.BOLT_EUV: [ { - 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7 + 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], CAR.SILVERADO: [ { - 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7 + 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], CAR.EQUINOX: [ { - 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7 + 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], # Trailblazer also matches as a Silverado, so comment out to avoid conflicts. # TODO: split with FW versions CAR.TRAILBLAZER: [ { - # 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8 + # 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1609: 8, 1611: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8 }], } +GM_RX_OFFSET = 0x400 + DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} diff --git a/selfdrive/car/hyundai/tests/print_platform_codes.py b/selfdrive/car/hyundai/tests/print_platform_codes.py new file mode 100755 index 0000000000..1bc8a4e366 --- /dev/null +++ b/selfdrive/car/hyundai/tests/print_platform_codes.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +from cereal import car +from selfdrive.car.hyundai.values import FW_VERSIONS, PLATFORM_CODE_ECUS, get_platform_codes + +Ecu = car.CarParams.Ecu +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} + +if __name__ == "__main__": + for car_model, ecus in FW_VERSIONS.items(): + print() + print(car_model) + for ecu in sorted(ecus, key=lambda x: int(x[0])): + if ecu[0] not in PLATFORM_CODE_ECUS: + continue + + platform_codes = get_platform_codes(ecus[ecu]) + codes = {code for code, _ in platform_codes} + dates = {date for _, date in platform_codes if date is not None} + print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') + print(f' Codes: {codes}') + print(f' Dates: {dates}') diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index 77aed9c979..36d2b3dc4c 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -2,13 +2,38 @@ import unittest from cereal import car -from selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, FW_QUERY_CONFIG, \ - FW_VERSIONS, LEGACY_SAFETY_MODE_CAR, PART_NUMBER_FW_PATTERN, PLATFORM_CODE_ECUS, \ - get_platform_codes +from selfdrive.car.fw_versions import build_fw_dict +from selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \ + EV_CAR, FW_QUERY_CONFIG, FW_VERSIONS, LEGACY_SAFETY_MODE_CAR, \ + PLATFORM_CODE_ECUS, get_platform_codes Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} +# Some platforms have date codes in a different format we don't yet parse (or are missing). +# For now, assert list of expected missing date cars +NO_DATES_PLATFORMS = { + # CAN FD + CAR.KIA_SPORTAGE_5TH_GEN, + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, + CAR.SANTA_CRUZ_1ST_GEN, + CAR.TUCSON_4TH_GEN, + CAR.TUCSON_HYBRID_4TH_GEN, + # CAN + CAR.ELANTRA, + CAR.KIA_CEED, + CAR.KIA_FORTE, + CAR.KIA_OPTIMA_G4, + CAR.KIA_OPTIMA_G4_FL, + CAR.KIA_SORENTO, + CAR.KONA, + CAR.KONA_EV, + CAR.KONA_EV_2022, + CAR.KONA_HEV, + CAR.SONATA_LF, + CAR.VELOSTER, +} + class TestHyundaiFingerprint(unittest.TestCase): def test_canfd_not_in_can_features(self): @@ -62,48 +87,76 @@ class TestHyundaiFingerprint(unittest.TestCase): self.assertEqual(1, len(result), f"Unable to parse FW: {fw}") codes |= result - # Some newer platforms have date codes in a different format we don't yet parse, - # for now assert we can parse all FW or none - self.assertEqual(len({b"-" in code for code in codes}), 1, - "Not all FW dates are parsable") + if ecu[0] not in DATE_FW_ECUS or car_model in NO_DATES_PLATFORMS: + self.assertTrue(all({date is None for _, date in codes})) + else: + self.assertTrue(all({date is not None for _, date in codes})) if car_model == CAR.HYUNDAI_GENESIS: raise unittest.SkipTest("No part numbers for car model") # Hyundai places the ECU part number in their FW versions, assert all parsable # Some examples of valid formats: b"56310-L0010", b"56310L0010", b"56310/M6300" - for fw in fws: - match = PART_NUMBER_FW_PATTERN.search(fw) - self.assertIsNotNone(match, fw) + self.assertTrue(all({b"-" in code for code, _ in codes}), + f"FW does not have part number: {fw}") def test_platform_codes_spot_check(self): # Asserts basic platform code parsing behavior for a few cases - codes = get_platform_codes([b"\xf1\x00DH LKAS 1.1 -150210"]) - self.assertEqual(codes, {b"DH-1502"}) + results = get_platform_codes([b"\xf1\x00DH LKAS 1.1 -150210"]) + self.assertEqual(results, {(b"DH", b"150210")}) # Some cameras and all radars do not have dates - codes = get_platform_codes([b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 "]) - self.assertEqual(codes, {b"AEhe"}) + results = get_platform_codes([b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 "]) + self.assertEqual(results, {(b"AEhe-G2000", None)}) - codes = get_platform_codes([b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 "]) - self.assertEqual(codes, {b"CV1"}) + results = get_platform_codes([b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 "]) + self.assertEqual(results, {(b"CV1-CV000", None)}) - codes = get_platform_codes([ + results = get_platform_codes([ b"\xf1\x00DH LKAS 1.1 -150210", b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ", b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ", ]) - self.assertEqual(codes, {b"DH-1502", b"AEhe", b"CV1"}) + self.assertEqual(results, {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)}) - # Returned platform codes must inclusively contain start/end dates - codes = get_platform_codes([ + results = get_platform_codes([ b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 220222", b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 211103", b"\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 190405", b"\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 190720", ]) - self.assertEqual(codes, {b"LX2-2111", b"LX2-2112", b"LX2-2201", b"LX2-2202", - b"ON-1904", b"ON-1905", b"ON-1906", b"ON-1907"}) + self.assertEqual(results, {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"), + (b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")}) + + def test_fuzzy_excluded_platforms(self): + # Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared. + # This list can be shrunk as we combine platforms and detect features + excluded_platforms = { + CAR.GENESIS_G70, # shared platform code, part number, and date + CAR.GENESIS_G70_2020, + CAR.TUCSON_4TH_GEN, # shared platform code and part number + CAR.TUCSON_HYBRID_4TH_GEN, + } + excluded_platforms |= CANFD_CAR - EV_CAR # shared platform codes + excluded_platforms |= NO_DATES_PLATFORMS # date codes are required to match + + platforms_with_shared_codes = set() + for platform, fw_by_addr in FW_VERSIONS.items(): + car_fw = [] + for ecu, fw_versions in fw_by_addr.items(): + ecu_name, addr, sub_addr = ecu + for fw in fw_versions: + car_fw.append({"ecu": ecu_name, "fwVersion": fw, "address": addr, + "subAddress": 0 if sub_addr is None else sub_addr}) + + CP = car.CarParams.new_message(carFw=car_fw) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw)) + if len(matches) == 1: + self.assertEqual(list(matches)[0], platform) + else: + platforms_with_shared_codes.add(platform) + + self.assertEqual(platforms_with_shared_codes, excluded_platforms) if __name__ == "__main__": diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index c0a8f8fae6..1d05730853 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,10 +1,7 @@ import re -from datetime import datetime -from dateutil import rrule -from collections import defaultdict from dataclasses import dataclass from enum import Enum, IntFlag -from typing import DefaultDict, Dict, List, Optional, Set, Union, cast +from typing import Dict, List, Optional, Set, Tuple, Union from cereal import car from panda.python import uds @@ -12,7 +9,6 @@ from common.conversions import Conversions as CV from selfdrive.car import dbc_dict from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 -from system.swaglog import cloudlog Ecu = car.CarParams.Ecu @@ -348,35 +344,73 @@ FINGERPRINTS = { } -def get_platform_codes(fw_versions: List[bytes]) -> Set[bytes]: - codes: DefaultDict[bytes, Set[Optional[bytes]]] = defaultdict(set) +def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[bytes]]]: + # Returns unique, platform-specific identification codes for a set of versions + codes = set() # (code-Optional[part], date) for fw in fw_versions: - code_match, date_match = (PLATFORM_CODE_FW_PATTERN.search(fw), - DATE_FW_PATTERN.search(fw)) + code_match = PLATFORM_CODE_FW_PATTERN.search(fw) + part_match = PART_NUMBER_FW_PATTERN.search(fw) + date_match = DATE_FW_PATTERN.search(fw) if code_match is not None: - code = code_match.group() + code: bytes = code_match.group() + part = part_match.group() if part_match else None date = date_match.group() if date_match else None - codes[code].add(date) + if part is not None: + # part number starts with generic ECU part type, add what is specific to platform + code += b"-" + part[-5:] - # Create platform codes for all dates inclusive if ECU has FW dates - final_codes = set() - for code, dates in codes.items(): - # Radar and some cameras don't have FW dates - if None in dates: - final_codes.add(code) - continue + codes.add((code, date)) + return codes - try: - parsed = {datetime.strptime(cast(bytes, date).decode()[:4], '%y%m') for date in dates} - except ValueError: - cloudlog.exception(f'Error parsing date in FW versions: {code!r}, {dates}') - final_codes.add(code) - continue - for monthly in rrule.rrule(rrule.MONTHLY, dtstart=min(parsed), until=max(parsed)): - final_codes.add(code + b'-' + monthly.strftime('%y%m').encode()) +def match_fw_to_car_fuzzy(live_fw_versions) -> Set[str]: + # Non-electric CAN FD platforms often do not have platform code specifiers needed + # to distinguish between hybrid and ICE. All EVs so far are either exclusively + # electric or specify electric in the platform code. + fuzzy_platform_blacklist = set(CANFD_CAR - EV_CAR) + candidates = set() - return final_codes + for candidate, fws in FW_VERSIONS.items(): + # Keep track of ECUs which pass all checks (platform codes, within date range) + valid_found_ecus = set() + valid_expected_ecus = {ecu[1:] for ecu in fws if ecu[0] in PLATFORM_CODE_ECUS} + for ecu, expected_versions in fws.items(): + addr = ecu[1:] + # Only check ECUs expected to have platform codes + if ecu[0] not in PLATFORM_CODE_ECUS: + continue + + # Expected platform codes & dates + codes = get_platform_codes(expected_versions) + expected_platform_codes = {code for code, _ in codes} + expected_dates = {date for _, date in codes if date is not None} + + # Found platform codes & dates + codes = get_platform_codes(live_fw_versions.get(addr, set())) + found_platform_codes = {code for code, _ in codes} + found_dates = {date for _, date in codes if date is not None} + + # Check platform code + part number matches for any found versions + if not any(found_platform_code in expected_platform_codes for found_platform_code in found_platform_codes): + break + + if ecu[0] in DATE_FW_ECUS: + # If ECU can have a FW date, require it to exist + # (this excludes candidates in the database without dates) + if not len(expected_dates) or not len(found_dates): + break + + # Check any date within range in the database, format is %y%m%d + if not any(min(expected_dates) <= found_date <= max(expected_dates) for found_date in found_dates): + break + + valid_found_ecus.add(addr) + + # If all live ECUs pass all checks for candidate, add it as a match + if valid_expected_ecus.issubset(valid_found_ecus): + candidates.add(candidate) + + return candidates - fuzzy_platform_blacklist HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ @@ -401,6 +435,8 @@ PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z] # List of ECUs expected to have platform codes, camera and radar should exist on all cars # TODO: use abs, it has the platform code and part number on many platforms PLATFORM_CODE_ECUS = [Ecu.fwdRadar, Ecu.fwdCamera, Ecu.eps] +# So far we've only seen dates in fwdCamera +DATE_FW_ECUS = [Ecu.fwdCamera] FW_QUERY_CONFIG = FwQueryConfig( requests=[ @@ -458,6 +494,8 @@ FW_QUERY_CONFIG = FwQueryConfig( (Ecu.hvac, 0x7b3, None), # HVAC Control Assembly (Ecu.cornerRadar, 0x7b7, None), ], + # Custom fuzzy fingerprinting function using platform codes, part numbers + FW dates: + match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) FW_VERSIONS = { diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index 944d79809b..af88308954 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -38,8 +38,8 @@ class CarState(CarStateBase): ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) ret.genericToggle = bool(cp.vl["BLINK_INFO"]["HIGH_BEAMS"]) - ret.leftBlindspot = cp.vl["BSM"]["LEFT_BS1"] == 1 - ret.rightBlindspot = cp.vl["BSM"]["RIGHT_BS1"] == 1 + ret.leftBlindspot = cp.vl["BSM"]["LEFT_BS_STATUS"] != 0 + ret.rightBlindspot = cp.vl["BSM"]["RIGHT_BS_STATUS"] != 0 ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(40, cp.vl["BLINK_INFO"]["LEFT_BLINK"] == 1, cp.vl["BLINK_INFO"]["RIGHT_BLINK"] == 1) @@ -151,8 +151,8 @@ class CarState(CarStateBase): ("PEDAL_GAS", "ENGINE_DATA"), ("SPEED", "ENGINE_DATA"), ("CTR", "CRZ_BTNS"), - ("LEFT_BS1", "BSM"), - ("RIGHT_BS1", "BSM"), + ("LEFT_BS_STATUS", "BSM"), + ("RIGHT_BS_STATUS", "BSM"), ] checks += [ diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 568c33630b..8d0728c33d 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -79,25 +79,35 @@ FINGERPRINTS = { ] } -NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0xc0]) -NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, 0xc0]) +NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL]) +NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40]) NISSAN_VERSION_REQUEST_KWP = b'\x21\x83' NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83' NISSAN_RX_OFFSET = 0x20 +# Try diagnostic sessions: default, standby, extended, Nissan-specific +NISSAN_DIAGNOSTIC_SESSION_TYPES = (0x81, 0x89, 0x92, 0xc0) +NISSAN_DEFAULT_DIAGNOSTIC_SESSION_TYPE = 0xc0 + FW_QUERY_CONFIG = FwQueryConfig( requests=[ - Request( - [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP], - [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP], - ), - Request( - [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP], - [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP], - rx_offset=NISSAN_RX_OFFSET, - ), + *[ + Request( + [NISSAN_DIAGNOSTIC_REQUEST_KWP + bytes([subfunction]), NISSAN_VERSION_REQUEST_KWP], + [NISSAN_DIAGNOSTIC_RESPONSE_KWP + bytes([subfunction]), NISSAN_VERSION_RESPONSE_KWP], + logging=subfunction != NISSAN_DEFAULT_DIAGNOSTIC_SESSION_TYPE, + ) for subfunction in NISSAN_DIAGNOSTIC_SESSION_TYPES + ], + *[ + Request( + [NISSAN_DIAGNOSTIC_REQUEST_KWP + bytes([subfunction]), NISSAN_VERSION_REQUEST_KWP], + [NISSAN_DIAGNOSTIC_RESPONSE_KWP + bytes([subfunction]), NISSAN_VERSION_RESPONSE_KWP], + rx_offset=NISSAN_RX_OFFSET, + logging=subfunction != NISSAN_DEFAULT_DIAGNOSTIC_SESSION_TYPE, + ) for subfunction in NISSAN_DIAGNOSTIC_SESSION_TYPES + ], Request( [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index c6ef0f11da..b37c88797a 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -1,7 +1,7 @@ from opendbc.can.packer import CANPacker from selfdrive.car import apply_driver_steer_torque_limits from selfdrive.car.subaru import subarucan -from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CarControllerParams, SubaruFlags +from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, CarControllerParams, SubaruFlags class CarController: @@ -44,7 +44,6 @@ class CarController: # *** alerts and pcm cancel *** - if self.CP.carFingerprint in PREGLOBAL_CARS: if self.frame % 5 == 0: # 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep @@ -66,7 +65,7 @@ class CarController: else: if pcm_cancel_cmd and (self.frame - self.last_cancel_frame) > 0.2: - bus = 1 if self.CP.carFingerprint in GLOBAL_GEN2 else 0 + bus = CanBus.alt if self.CP.carFingerprint in GLOBAL_GEN2 else CanBus.main can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, bus, pcm_cancel_cmd)) self.last_cancel_frame = self.frame @@ -78,7 +77,7 @@ class CarController: hud_control.leftLaneDepart, hud_control.rightLaneDepart)) if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: - can_sends.append(subarucan.create_infotainmentstatus(self.packer, CS.es_infotainmentstatus_msg, hud_control.visualAlert)) + can_sends.append(subarucan.create_es_infotainment(self.packer, CS.es_infotainment_msg, hud_control.visualAlert)) new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 8ce31b1842..189c244ca8 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -4,7 +4,7 @@ from opendbc.can.can_define import CANDefine from common.conversions import Conversions as CV from selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, PREGLOBAL_CARS, SubaruFlags +from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, SubaruFlags class CarState(CarStateBase): @@ -69,6 +69,7 @@ class CarState(CarStateBase): cp.vl["BodyInfo"]["DOOR_OPEN_FL"]]) ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1 + cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam if self.car_fingerprint in PREGLOBAL_CARS: self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"] self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"] @@ -76,19 +77,22 @@ class CarState(CarStateBase): ret.steerFaultTemporary = cp.vl["Steering_Torque"]["Steer_Warning"] == 1 ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1 ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3 - ret.stockFcw = cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2 + ret.stockFcw = (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 1) or \ + (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2) + # 8 is known AEB, there are a few other values related to AEB we ignore + ret.stockAeb = (cp_es_distance.vl["ES_Brake"]["AEB_Status"] == 8) and \ + (cp_es_distance.vl["ES_Brake"]["Brake_Pressure"] != 0) self.es_lkas_state_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) - cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"]) if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: - self.es_infotainmentstatus_msg = copy.copy(cp_cam.vl["INFOTAINMENT_STATUS"]) + self.es_infotainment_msg = copy.copy(cp_cam.vl["ES_Infotainment"]) return ret @staticmethod - def get_common_global_signals(): + def get_common_global_body_signals(): signals = [ ("Cruise_On", "CruiseControl"), ("Cruise_Activated", "CruiseControl"), @@ -107,8 +111,10 @@ class CarState(CarStateBase): return signals, checks @staticmethod - def get_global_es_distance_signals(): + def get_common_global_es_signals(): signals = [ + ("AEB_Status", "ES_Brake"), + ("Brake_Pressure", "ES_Brake"), ("COUNTER", "ES_Distance"), ("CHECKSUM", "ES_Distance"), ("Signal1", "ES_Distance"), @@ -130,7 +136,9 @@ class CarState(CarStateBase): ("Cruise_Resume", "ES_Distance"), ("Signal6", "ES_Distance"), ] + checks = [ + ("ES_Brake", 20), ("ES_Distance", 20), ] @@ -177,8 +185,8 @@ class CarState(CarStateBase): if CP.carFingerprint not in PREGLOBAL_CARS: if CP.carFingerprint not in GLOBAL_GEN2: - signals += CarState.get_common_global_signals()[0] - checks += CarState.get_common_global_signals()[1] + signals += CarState.get_common_global_body_signals()[0] + checks += CarState.get_common_global_body_signals()[1] signals += [ ("Steer_Warning", "Steering_Torque"), @@ -217,7 +225,7 @@ class CarState(CarStateBase): ("CruiseControl", 50), ] - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.main) @staticmethod def get_cam_can_parser(CP): @@ -303,28 +311,28 @@ class CarState(CarStateBase): ] if CP.carFingerprint not in GLOBAL_GEN2: - signals += CarState.get_global_es_distance_signals()[0] - checks += CarState.get_global_es_distance_signals()[1] + signals += CarState.get_common_global_es_signals()[0] + checks += CarState.get_common_global_es_signals()[1] if CP.flags & SubaruFlags.SEND_INFOTAINMENT: signals += [ - ("COUNTER", "INFOTAINMENT_STATUS"), - ("CHECKSUM", "INFOTAINMENT_STATUS"), - ("LKAS_State_Infotainment", "INFOTAINMENT_STATUS"), - ("LKAS_Blue_Lines", "INFOTAINMENT_STATUS"), - ("Signal1", "INFOTAINMENT_STATUS"), - ("Signal2", "INFOTAINMENT_STATUS"), + ("COUNTER", "ES_Infotainment"), + ("CHECKSUM", "ES_Infotainment"), + ("LKAS_State_Infotainment", "ES_Infotainment"), + ("LKAS_Blue_Lines", "ES_Infotainment"), + ("Signal1", "ES_Infotainment"), + ("Signal2", "ES_Infotainment"), ] - checks.append(("INFOTAINMENT_STATUS", 10)) + checks.append(("ES_Infotainment", 10)) - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.camera) @staticmethod def get_body_can_parser(CP): if CP.carFingerprint in GLOBAL_GEN2: - signals, checks = CarState.get_common_global_signals() - signals += CarState.get_global_es_distance_signals()[0] - checks += CarState.get_global_es_distance_signals()[1] - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 1) + signals, checks = CarState.get_common_global_body_signals() + signals += CarState.get_common_global_es_signals()[0] + checks += CarState.get_common_global_es_signals()[1] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.alt) return None diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index 033ba7f76b..0c32a150d8 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -1,4 +1,5 @@ from cereal import car +from selfdrive.car.subaru.values import CanBus VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -102,7 +103,7 @@ def create_es_lkas_state(packer, es_lkas_state_msg, enabled, visual_alert, left_ values["LKAS_Left_Line_Visible"] = int(left_line) values["LKAS_Right_Line_Visible"] = int(right_line) - return packer.make_can_msg("ES_LKAS_State", 0, values) + return packer.make_can_msg("ES_LKAS_State", CanBus.main, values) def create_es_dashstatus(packer, dashstatus_msg): @@ -140,12 +141,12 @@ def create_es_dashstatus(packer, dashstatus_msg): if values["LKAS_State_Msg"] in (2, 3): values["LKAS_State_Msg"] = 0 - return packer.make_can_msg("ES_DashStatus", 0, values) + return packer.make_can_msg("ES_DashStatus", CanBus.main, values) -def create_infotainmentstatus(packer, infotainmentstatus_msg, visual_alert): +def create_es_infotainment(packer, es_infotainment_msg, visual_alert): # Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts - values = {s: infotainmentstatus_msg[s] for s in [ + values = {s: es_infotainment_msg[s] for s in [ "CHECKSUM", "COUNTER", "LKAS_State_Infotainment", @@ -164,7 +165,7 @@ def create_infotainmentstatus(packer, infotainmentstatus_msg, visual_alert): if visual_alert == VisualAlert.fcw: values["LKAS_State_Infotainment"] = 2 - return packer.make_can_msg("INFOTAINMENT_STATUS", 0, values) + return packer.make_can_msg("ES_Infotainment", CanBus.main, values) # *** Subaru Pre-global *** @@ -181,7 +182,7 @@ def create_preglobal_steering_control(packer, apply_steer, steer_req): } values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_LKAS") - return packer.make_can_msg("ES_LKAS", 0, values) + return packer.make_can_msg("ES_LKAS", CanBus.main, values) def create_preglobal_es_distance(packer, cruise_button, es_distance_msg): @@ -208,4 +209,4 @@ def create_preglobal_es_distance(packer, cruise_button, es_distance_msg): values["Cruise_Button"] = cruise_button values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_Distance") - return packer.make_can_msg("ES_Distance", 0, values) + return packer.make_can_msg("ES_Distance", CanBus.main, values) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 12367d9b5a..8bd66c30f9 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -34,6 +34,12 @@ class SubaruFlags(IntFlag): SEND_INFOTAINMENT = 1 +class CanBus: + main = 0 + alt = 1 + camera = 2 + + class CAR: # Global platform ASCENT = "SUBARU ASCENT LIMITED 2019" diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 74834065fb..275907f06e 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -16,7 +16,6 @@ from selfdrive.car.body.values import CAR as COMMA # TODO: add routes for these cars non_tested_cars = [ - FORD.FOCUS_MK4, GM.CADILLAC_ATS, GM.HOLDEN_ASTRA, GM.MALIBU, @@ -47,6 +46,7 @@ routes = [ CarTestRoute("54827bf84c38b14f|2023-01-25--14-14-11", FORD.BRONCO_SPORT_MK1), CarTestRoute("f8eaaccd2a90aef8|2023-05-04--15-10-09", FORD.ESCAPE_MK4), CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6), + CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FOCUS_MK4), CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.MAVERICK_MK1), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 7198218d6a..918d2cf873 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import math import unittest +from hypothesis import given, settings import importlib from parameterized import parameterized @@ -8,12 +9,15 @@ from cereal import car from selfdrive.car import gen_empty_fingerprint from selfdrive.car.car_helpers import interfaces from selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS, all_known_cars +from selfdrive.test.fuzzy_generation import get_random_msg class TestCarInterfaces(unittest.TestCase): @parameterized.expand([(car,) for car in all_known_cars()]) - def test_car_interfaces(self, car_name): + @settings(max_examples=5) + @given(cc_msg=get_random_msg(car.CarControl, real_floats=True)) + def test_car_interfaces(self, car_name, cc_msg): if car_name in FINGERPRINTS: fingerprint = FINGERPRINTS[car_name][0] else: @@ -57,13 +61,13 @@ class TestCarInterfaces(unittest.TestCase): self.assertTrue(len(tune.indi.outerLoopGainV)) # Run car interface - CC = car.CarControl.new_message() + CC = car.CarControl.new_message(**cc_msg) for _ in range(10): car_interface.update(CC, []) car_interface.apply(CC, 0) car_interface.apply(CC, 0) - CC = car.CarControl.new_message() + CC = car.CarControl.new_message(**cc_msg) CC.enabled = True for _ in range(10): car_interface.update(CC, []) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 0f52e12625..fd9030c7db 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -10,7 +10,8 @@ from cereal import car from common.params import Params from selfdrive.car.car_helpers import interfaces from selfdrive.car.fingerprints import FW_VERSIONS -from selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, match_fw_to_car, get_fw_versions +from selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, match_fw_to_car, get_fw_versions, get_present_ecus +from selfdrive.car.vin import get_vin CarFw = car.CarParams.CarFw Ecu = car.CarParams.Ecu @@ -45,6 +46,28 @@ class TestFwFingerprint(unittest.TestCase): _, matches = match_fw_to_car(CP.carFw, allow_fuzzy=False) self.assertFingerprints(matches, car_model) + @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) + def test_custom_fuzzy_match(self, brand, car_model, ecus): + # Assert brand-specific fuzzy fingerprinting function doesn't disagree with standard fuzzy function + config = FW_QUERY_CONFIGS[brand] + if config.match_fw_to_car_fuzzy is None: + raise unittest.SkipTest("Brand does not implement custom fuzzy fingerprinting function") + + CP = car.CarParams.new_message() + for _ in range(5): + fw = [] + for ecu, fw_versions in ecus.items(): + ecu_name, addr, sub_addr = ecu + fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, + "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) + CP.carFw = fw + _, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False) + brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw)) + + # If both have matches, they must agree + if len(matches) == 1 and len(brand_matches) == 1: + self.assertEqual(matches, brand_matches) + @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) def test_fuzzy_match_ecu_count(self, brand, car_model, ecus): # Asserts that fuzzy matching does not count matching FW, but ECU address keys @@ -135,36 +158,62 @@ class TestFwFingerprint(unittest.TestCase): ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted]) self.assertFalse(len(whitelisted_ecus) and len(ecus_not_whitelisted), - f'{brand.title()}: FW query whitelist missing ecus: {ecu_strings}') + f'{brand.title()}: ECUs not in any FW query whitelists: {ecu_strings}') class TestFwFingerprintTiming(unittest.TestCase): + N: int = 5 + TOL: float = 0.1 + @staticmethod - def _benchmark(brand, num_pandas, n): + def _run_thread(thread: threading.Thread) -> float: params = Params() + params.put_bool("ObdMultiplexingEnabled", True) + thread.start() + t = time.perf_counter() + while thread.is_alive(): + time.sleep(0.02) + if not params.get_bool("ObdMultiplexingChanged"): + params.put_bool("ObdMultiplexingChanged", True) + return time.perf_counter() - t + + def _benchmark_brand(self, brand, num_pandas): fake_socket = FakeSocket() + brand_time = 0 + for _ in range(self.N): + thread = threading.Thread(target=get_fw_versions, args=(fake_socket, fake_socket, brand), + kwargs=dict(num_pandas=num_pandas)) + brand_time += self._run_thread(thread) + + return round(brand_time / self.N, 2) - times = [] - for _ in range(n): - params.put_bool("ObdMultiplexingEnabled", True) - thread = threading.Thread(target=get_fw_versions, args=(fake_socket, fake_socket, brand), kwargs=dict(num_pandas=num_pandas)) - thread.start() - t = time.perf_counter() - while thread.is_alive(): - time.sleep(0.02) - if not params.get_bool("ObdMultiplexingChanged"): - params.put_bool("ObdMultiplexingChanged", True) - times.append(time.perf_counter() - t) + def _assert_timing(self, avg_time, ref_time): + self.assertLess(avg_time, ref_time + self.TOL) + self.assertGreater(avg_time, ref_time - self.TOL, "Performance seems to have improved, update test refs.") - return round(sum(times) / len(times), 2) + def test_startup_timing(self): + # Tests worse-case VIN query time and typical present ECU query time + vin_ref_time = 1.0 + present_ecu_ref_time = 0.8 - def _assert_timing(self, avg_time, ref_time, tol): - self.assertLess(avg_time, ref_time + tol) - self.assertGreater(avg_time, ref_time - tol, "Performance seems to have improved, update test refs.") + fake_socket = FakeSocket() + present_ecu_time = 0.0 + for _ in range(self.N): + thread = threading.Thread(target=get_present_ecus, args=(fake_socket, fake_socket), + kwargs=dict(num_pandas=2)) + present_ecu_time += self._run_thread(thread) + self._assert_timing(present_ecu_time / self.N, present_ecu_ref_time) + print(f'get_present_ecus, query time={present_ecu_time / self.N} seconds') + + vin_time = 0.0 + for _ in range(self.N): + thread = threading.Thread(target=get_vin, args=(fake_socket, fake_socket, 1)) + vin_time += self._run_thread(thread) + self._assert_timing(vin_time / self.N, vin_ref_time) + print(f'get_vin, query time={vin_time / self.N} seconds') def test_fw_query_timing(self): - tol = 0.1 - total_ref_time = 4.6 + total_ref_time = 6.1 brand_ref_times = { 1: { 'body': 0.1, @@ -173,10 +222,10 @@ class TestFwFingerprintTiming(unittest.TestCase): 'honda': 0.5, 'hyundai': 0.7, 'mazda': 0.1, - 'nissan': 0.3, + 'nissan': 0.9, 'subaru': 0.1, 'tesla': 0.2, - 'toyota': 0.7, + 'toyota': 1.6, 'volkswagen': 0.2, }, 2: { @@ -192,13 +241,13 @@ class TestFwFingerprintTiming(unittest.TestCase): if not len(multi_panda_requests) and num_pandas > 1: raise unittest.SkipTest("No multi-panda FW queries") - avg_time = self._benchmark(brand, num_pandas, 5) + avg_time = self._benchmark_brand(brand, num_pandas) total_time += avg_time - self._assert_timing(avg_time, brand_ref_times[num_pandas][brand], tol) + self._assert_timing(avg_time, brand_ref_times[num_pandas][brand]) print(f'{brand=}, {num_pandas=}, {len(config.requests)=}, avg FW query time={avg_time} seconds') with self.subTest(brand='all_brands'): - self._assert_timing(total_time, total_ref_time, tol) + self._assert_timing(total_time, total_ref_time) print(f'all brands, total FW query time={total_time} seconds') diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index afd4624a81..00dba96abc 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -29,7 +29,7 @@ COMMA BODY: [.nan, 1000, .nan] # Totally new cars RAM 1500 5TH GEN: [2.0, 2.0, 0.05] RAM HD 5TH GEN: [1.4, 1.4, 0.05] -SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] +SUBARU OUTBACK 6TH GEN: [2.0, 2.0, 0.2] CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694] CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 83ca8a0316..b355968c84 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -207,6 +207,10 @@ class CarInterface(CarInterfaceBase): if 0x2FF in fingerprint[0]: ret.flags |= ToyotaFlags.SMART_DSU.value + # No radar dbc for cars without DSU which are not TSS 2.0 + # TODO: make an adas dbc file for dsu-less models + ret.radarUnavailable = DBC[candidate]['radar'] is None or candidate in (NO_DSU_CAR - TSS2_CAR) + # In TSS2 cars, the camera does long control found_ecus = [fw.ecu for fw in car_fw] ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) and not (ret.flags & ToyotaFlags.SMART_DSU) diff --git a/selfdrive/car/toyota/radar_interface.py b/selfdrive/car/toyota/radar_interface.py index f4c7191297..64906b34be 100755 --- a/selfdrive/car/toyota/radar_interface.py +++ b/selfdrive/car/toyota/radar_interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from opendbc.can.parser import CANParser from cereal import car -from selfdrive.car.toyota.values import NO_DSU_CAR, DBC, TSS2_CAR +from selfdrive.car.toyota.values import DBC, TSS2_CAR from selfdrive.car.interfaces import RadarInterfaceBase @@ -43,12 +43,8 @@ class RadarInterface(RadarInterfaceBase): self.trigger_msg = self.RADAR_B_MSGS[-1] self.updated_messages = set() - # No radar dbc for cars without DSU which are not TSS 2.0 - # TODO: make a adas dbc file for dsu-less models - self.no_radar = CP.carFingerprint in NO_DSU_CAR and CP.carFingerprint not in TSS2_CAR - def update(self, can_strings): - if self.no_radar or self.rcp is None: + if self.rcp is None: return super().update(None) vls = self.rcp.update_strings(can_strings) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 422c7560fe..6c0b2f34ff 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -215,27 +215,31 @@ STATIC_DSU_MSGS = [ (0x4CB, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'), ] -TOYOTA_VERSION_REQUEST = b'\x1a\x88\x01' -TOYOTA_VERSION_RESPONSE = b'\x5a\x88\x01' +TOYOTA_VERSION_REQUEST_KWP = b'\x1a\x88\x01' +TOYOTA_VERSION_RESPONSE_KWP = b'\x5a\x88\x01' FW_QUERY_CONFIG = FwQueryConfig( + # TODO: look at data to whitelist new ECUs effectively requests=[ Request( - [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST], - [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps], + [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST_KWP], + [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE_KWP], + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps, Ecu.epb, Ecu.telematics, + Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, Ecu.gateway, Ecu.hvac], bus=0, ), Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, StdQueries.OBD_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, StdQueries.OBD_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine], + whitelist_ecus=[Ecu.engine, Ecu.epb, Ecu.telematics, Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, + Ecu.gateway, Ecu.hvac], bus=0, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.EXTENDED_DIAGNOSTIC_REQUEST, StdQueries.UDS_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.EXTENDED_DIAGNOSTIC_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps], + whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps, Ecu.epb, Ecu.telematics, + Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, Ecu.gateway, Ecu.hvac], bus=0, ), ], @@ -244,7 +248,38 @@ FW_QUERY_CONFIG = FwQueryConfig( Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS], # On some models, the engine can show on two different addresses Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, CAR.LEXUS_RC], - } + }, + extra_ecus=[ + # All known ECUs on a late-model Toyota vehicle not queried here: + # Responds to UDS: + # - HV Battery (0x713, 0x747) + # - Motor Generator (0x716, 0x724) + # - 2nd ABS "Brake/EPB" (0x730) + # Responds to KWP: + # - Steering Angle Sensor (0x7b3) + # - EPS/EMPS (0x7a0, 0x7a1) + + # Hybrid control computer can be on one of two addresses + (Ecu.hybrid, 0x7e2, None), # Hybrid Control Assembly & Computer + (Ecu.hybrid, 0x7d2, None), # Hybrid Control Assembly & Computer + # TODO: if these duplicate ECUs always exist together, remove one + (Ecu.srs, 0x780, None), # SRS Airbag + (Ecu.srs, 0x784, None), # SRS Airbag 2 + # Likely only exists on cars where EPB isn't standard (e.g. Camry, Avalon (/Hybrid)) + # On some cars, EPB is controlled by the ABS module + (Ecu.epb, 0x750, 0x2c), # Electronic Parking Brake + # This isn't accessible on all cars + (Ecu.gateway, 0x750, 0x5f), + # On some cars, this only responds to b'\x1a\x88\x81', which is reflected by the b'\x1a\x88\x00' query + (Ecu.telematics, 0x750, 0xc7), + # Transmission is combined with engine on some platforms, such as TSS-P RAV4 + (Ecu.transmission, 0x701, None), + # A few platforms have a tester present response on this address, add to log + (Ecu.transmission, 0x7e1, None), + # On some cars, this only responds to b'\x1a\x88\x80' + (Ecu.combinationMeter, 0x7c0, None), + (Ecu.hvac, 0x7c4, None), + ], ) FW_VERSIONS = { @@ -875,7 +910,6 @@ FW_VERSIONS = { b'\x01F152612B81\x00\x00\x00\x00\x00\x00', b'\x01F152612B90\x00\x00\x00\x00\x00\x00', b'\x01F152612C00\x00\x00\x00\x00\x00\x00', - b'F152602191\x00\x00\x00\x00\x00\x00', b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612B91\x00\x00\x00\x00\x00\x00', b'\x01F15260A070\x00\x00\x00\x00\x00\x00', @@ -1491,6 +1525,7 @@ FW_VERSIONS = { b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896634A88100\x00\x00\x00\x00', b'\x01896634AJ2000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -1836,9 +1871,11 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\x0237887000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02378A0000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02378F4000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152678210\x00\x00\x00\x00\x00\x00', + b'F152678211\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B78120\x00\x00\x00\x00\x00\x00', @@ -1849,6 +1886,7 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F7803100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, CAR.LEXUS_NXH: { diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index 3b897c0c16..4db5606217 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -21,8 +21,9 @@ class CarController: self.apply_steer_last = 0 self.gra_acc_counter_last = None self.frame = 0 - self.hcaSameTorqueCount = 0 - self.hcaEnabledFrameCount = 0 + self.eps_timer_soft_disable_alert = False + self.hca_frame_timer_running = 0 + self.hca_frame_same_torque = 0 def update(self, CC, CS, ext_bus, now_nanos): actuators = CC.actuators @@ -38,36 +39,31 @@ class CarController: # * Don't send > 3.00 Newton-meters torque # * Don't send the same torque for > 6 seconds # * Don't send uninterrupted steering for > 360 seconds - # One frame of HCA disabled is enough to reset the timer, without zeroing the - # torque value. Do that anytime we happen to have 0 torque, or failing that, - # when exceeding ~1/3 the 360 second timer. + # MQB racks reset the uninterrupted steering timer after a single frame + # of HCA disabled; this is done whenever output happens to be zero. if CC.latActive: new_steer = int(round(actuators.steer * self.CCP.STEER_MAX)) apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.CCP) - if apply_steer == 0: - hcaEnabled = False - self.hcaEnabledFrameCount = 0 + self.hca_frame_timer_running += self.CCP.STEER_STEP + if self.apply_steer_last == apply_steer: + self.hca_frame_same_torque += self.CCP.STEER_STEP + if self.hca_frame_same_torque > self.CCP.STEER_TIME_STUCK_TORQUE / DT_CTRL: + apply_steer -= (1, -1)[apply_steer < 0] + self.hca_frame_same_torque = 0 else: - self.hcaEnabledFrameCount += 1 - if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.STEER_STEP): # 118s - hcaEnabled = False - self.hcaEnabledFrameCount = 0 - else: - hcaEnabled = True - if self.apply_steer_last == apply_steer: - self.hcaSameTorqueCount += 1 - if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.STEER_STEP): # 1.9s - apply_steer -= (1, -1)[apply_steer < 0] - self.hcaSameTorqueCount = 0 - else: - self.hcaSameTorqueCount = 0 + self.hca_frame_same_torque = 0 + hca_enabled = abs(apply_steer) > 0 else: - hcaEnabled = False + hca_enabled = False apply_steer = 0 + if not hca_enabled: + self.hca_frame_timer_running = 0 + + self.eps_timer_soft_disable_alert = self.hca_frame_timer_running > self.CCP.STEER_TIME_ALERT / DT_CTRL self.apply_steer_last = apply_steer - can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, hcaEnabled)) + can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, hca_enabled)) # **** Acceleration Controls ******************************************** # @@ -110,4 +106,4 @@ class CarController: self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"] self.frame += 1 - return new_actuators, can_sends + return new_actuators, can_sends, self.eps_timer_soft_disable_alert diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 874b85e68e..2b0603f162 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -20,6 +20,8 @@ class CarInterface(CarInterfaceBase): self.ext_bus = CANBUS.cam self.cp_ext = self.cp_cam + self.eps_timer_soft_disable_alert = False + @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "volkswagen" @@ -90,6 +92,7 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.startingState = True ret.startAccel = 1.0 + ret.stopAccel = -0.55 ret.vEgoStarting = 1.0 ret.vEgoStopping = 1.0 ret.longitudinalTuning.kpV = [0.1] @@ -242,9 +245,13 @@ class CarInterface(CarInterfaceBase): if c.enabled and ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) + if self.eps_timer_soft_disable_alert: + events.add(EventName.steerTimeLimit) + ret.events = events.to_msg() return ret def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, self.ext_bus, now_nanos) + new_actuators, can_sends, self.eps_timer_soft_disable_alert = self.CC.update(c, self.CS, self.ext_bus, now_nanos) + return new_actuators, can_sends diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 0f211546bd..b8fb173cfe 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -19,20 +19,25 @@ Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class CarControllerParams: - STEER_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz - ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz + STEER_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz + ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz - ACCEL_MAX = 2.0 # 2.0 m/s max acceleration - ACCEL_MIN = -3.5 # 3.5 m/s max deceleration + # Documented lateral limits: 3.00 Nm max, rate of change 5.00 Nm/sec. + # MQB vs PQ maximums are shared, but rate-of-change limited differently + # based on safety requirements driven by lateral accel testing. - def __init__(self, CP): - # Documented lateral limits: 3.00 Nm max, rate of change 5.00 Nm/sec. - # MQB vs PQ maximums are shared, but rate-of-change limited differently - # based on safety requirements driven by lateral accel testing. - self.STEER_MAX = 300 # Max heading control assist torque 3.00 Nm - self.STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily - self.STEER_DRIVER_FACTOR = 1 # from dbc + STEER_MAX = 300 # Max heading control assist torque 3.00 Nm + STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily + STEER_DRIVER_FACTOR = 1 # from dbc + + STEER_TIME_MAX = 360 # Max time that EPS allows uninterrupted HCA steering control + STEER_TIME_ALERT = STEER_TIME_MAX - 10 # If mitigation fails, time to soft disengage before EPS timer expires + STEER_TIME_STUCK_TORQUE = 1.9 # EPS limits same torque to 6 seconds, reset timer 3x within that period + ACCEL_MAX = 2.0 # 2.0 m/s max acceleration + ACCEL_MIN = -3.5 # 3.5 m/s max deceleration + + def __init__(self, CP): can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) if CP.carFingerprint in PQ_CARS: @@ -231,7 +236,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Volkswagen Sharan 2018-22"), VWCarInfo("SEAT Alhambra 2018-20"), ], - CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022"), + CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022-23"), CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), CAR.TIGUAN_MK2: [ VWCarInfo("Volkswagen Tiguan 2018-23"), @@ -404,6 +409,7 @@ FW_VERSIONS = { CAR.GOLF_MK7: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906016A \xf1\x897697', + b'\xf1\x8704E906016N \xf1\x899105', b'\xf1\x8704E906016AD\xf1\x895758', b'\xf1\x8704E906016CE\xf1\x899096', b'\xf1\x8704E906016CH\xf1\x899226', @@ -546,6 +552,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00504A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A07A02A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00407A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1', @@ -744,9 +751,11 @@ FW_VERSIONS = { CAR.TAOS_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027NJ\xf1\x891445', + b'\xf1\x8704E906027NP\xf1\x891286', b'\xf1\x8705E906013E \xf1\x891624', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158EM\xf1\x893812', b'\xf1\x8709S927158BL\xf1\x893791', b'\xf1\x8709S927158FF\xf1\x893876', ], @@ -755,10 +764,12 @@ FW_VERSIONS = { b'\xf1\x875Q0959655CE\xf1\x890421\xf1\x82\x1311110011333300314240021350139333613100', ], (Ecu.eps, 0x712, None): [ + b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x001O06081OOM', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060405A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060605A1', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572T \xf1\x890383', ], }, @@ -876,24 +887,28 @@ FW_VERSIONS = { b'\xf1\x8704L906056AL\xf1\x899970', b'\xf1\x8704L906057AP\xf1\x891186', b'\xf1\x8704L906057N \xf1\x890413', + b'\xf1\x8705L906023E \xf1\x891352', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870BT300012G \xf1\x893102', b'\xf1\x870BT300012E \xf1\x893105', b'\xf1\x870BT300046R \xf1\x893102', + b'\xf1\x870DV300012B \xf1\x893701', ], (Ecu.srs, 0x715, None): [ b'\xf1\x872Q0959655AE\xf1\x890506\xf1\x82\x1316170411110411--04041704161611152S1411', b'\xf1\x872Q0959655AE\xf1\x890506\xf1\x82\x1316170411110411--04041704171711152S1411', b'\xf1\x872Q0959655AF\xf1\x890506\xf1\x82\x1316171111110411--04041711121211152S1413', + b'\xf1\x872Q0959655AQ\xf1\x890511\xf1\x82\x1316170411110411--0404170426261215391421', ], (Ecu.eps, 0x712, None): [ b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x0532380518A2', b'\xf1\x877LA909144G \xf1\x897160\xf1\x82\x05333A5519A2', - b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\005323A5519A2', + b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x05323A5519A2', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572AA\xf1\x890396', ], }, CAR.TROC_MK1: { @@ -1064,6 +1079,7 @@ FW_VERSIONS = { b'\xf1\x8704L906021EL\xf1\x897542', b'\xf1\x8704L906026BP\xf1\x891198', b'\xf1\x8704L906026BP\xf1\x897608', + b'\xf1\x8704L906056CR\xf1\x892181', b'\xf1\x8704L906056CR\xf1\x892797', b'\xf1\x8705E906018AS\xf1\x899596', b'\xf1\x878V0906264H \xf1\x890005', @@ -1096,6 +1112,7 @@ FW_VERSIONS = { b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x875Q0907572R \xf1\x890771', ], }, CAR.SKODA_FABIA_MK4: { diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index f2f9cef862..b0721266a4 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -373,7 +373,7 @@ class Controls: else: self.logged_comm_issue = None - if not self.sm['liveParameters'].valid and not TESTING_CLOSET: + if not self.sm['liveParameters'].valid and not TESTING_CLOSET and not SIMULATION: self.events.add(EventName.vehicleModelInvalid) if not self.sm['lateralPlan'].mpcSolutionValid: self.events.add(EventName.plannerError) diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index cb878483de..f32e838333 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -16,10 +16,8 @@ with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) a def set_offroad_alert(alert: str, show_alert: bool, extra_text: Optional[str] = None) -> None: if show_alert: - a = OFFROAD_ALERTS[alert] - if extra_text is not None: - a = copy.copy(OFFROAD_ALERTS[alert]) - a['text'] += extra_text + a = copy.copy(OFFROAD_ALERTS[alert]) + a['extra'] = extra_text or '' Params().put(alert, json.dumps(a)) else: Params().remove(alert) diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json index 9226c94d85..35446ca22b 100644 --- a/selfdrive/controls/lib/alerts_offroad.json +++ b/selfdrive/controls/lib/alerts_offroad.json @@ -1,21 +1,21 @@ { "Offroad_TemperatureTooHigh": { - "text": "Device temperature too high. System won't start.", + "text": "Device temperature too high. System cooling down before starting. Current internal component temperature: %1", "severity": 1 }, "Offroad_ConnectivityNeededPrompt": { - "text": "Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in ", + "text": "Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1", "severity": 0, - "_comment": "Append the number of days at the end of the text" + "_comment": "Set extra field to number of days" }, "Offroad_ConnectivityNeeded": { "text": "Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates.", "severity": 1 }, "Offroad_UpdateFailed": { - "text": "Unable to download updates\n", + "text": "Unable to download updates\n%1", "severity": 1, - "_comment": "Append the command and error to the text." + "_comment": "Set extra field to the failed reason." }, "Offroad_InvalidTime": { "text": "Invalid date and time settings, system won't start. Connect to internet to set time.", diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 0593becba5..5914312420 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -25,7 +25,7 @@ STEERING_RATE_COST = 700.0 class LateralPlanner: - def __init__(self, CP): + def __init__(self, CP, debug=False): self.DH = DesireHelper() # Vehicle model parameters used to calculate lateral movement of car @@ -45,6 +45,8 @@ class LateralPlanner: self.l_lane_change_prob = 0.0 self.r_lane_change_prob = 0.0 + self.debug_mode = debug + self.lat_mpc = LateralMpc() self.reset_mpc(np.zeros(4)) @@ -132,6 +134,11 @@ class LateralPlanner: lateralPlan.mpcSolutionValid = bool(plan_solution_valid) lateralPlan.solverExecutionTime = self.lat_mpc.solve_time + if self.debug_mode: + lateralPlan.solverCost = self.lat_mpc.cost + lateralPlan.solverState = log.LateralPlan.SolverState.new_message() + lateralPlan.solverState.x = self.lat_mpc.x_sol.tolist() + lateralPlan.solverState.u = self.lat_mpc.u_sol.flatten().tolist() lateralPlan.desire = self.DH.desire lateralPlan.useLaneLines = False diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 23fb1b7790..b5998a2564 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -357,7 +357,7 @@ class LongitudinalMpc: v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_lower, v_upper) - cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, get_T_FOLLOW()) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, get_T_FOLLOW(personality)) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) self.source = SOURCES[np.argmin(x_obstacles[0])] diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 14934254af..2ae629a6c3 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import os import numpy as np from cereal import car from common.params import Params @@ -34,8 +35,10 @@ def plannerd_thread(sm=None, pm=None): CP = car.CarParams.from_bytes(params.get("CarParams", block=True)) cloudlog.info("plannerd got CarParams: %s", CP.carName) + debug_mode = bool(int(os.getenv("DEBUG", "0"))) + longitudinal_planner = LongitudinalPlanner(CP) - lateral_planner = LateralPlanner(CP) + lateral_planner = LateralPlanner(CP, debug=debug_mode) if sm is None: sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2'], diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py index 60c080163f..c0d70b3b6f 100755 --- a/selfdrive/controls/tests/test_alerts.py +++ b/selfdrive/controls/tests/test_alerts.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import copy import json import os import unittest @@ -76,9 +77,10 @@ class TestAlerts(unittest.TestCase): break font = fonts[alert.alert_size][i] - w, _ = draw.textsize(txt, font) + left, _, right, _ = draw.textbbox((0, 0), txt, font) + width = right - left msg = f"type: {alert.alert_type} msg: {txt}" - self.assertLessEqual(w, max_text_width, msg=msg) + self.assertLessEqual(width, max_text_width, msg=msg) def test_alert_sanity_check(self): for event_types in EVENTS.values(): @@ -108,8 +110,9 @@ class TestAlerts(unittest.TestCase): params = Params() for a in self.offroad_alerts: # set the alert - alert = self.offroad_alerts[a] + alert = copy.copy(self.offroad_alerts[a]) set_offroad_alert(a, True) + alert['extra'] = '' self.assertTrue(json.dumps(alert) == params.get(a, encoding='utf8')) # then delete it @@ -124,9 +127,9 @@ class TestAlerts(unittest.TestCase): alert = self.offroad_alerts[a] set_offroad_alert(a, True, extra_text="a"*i) - expected_txt = alert['text'] + "a"*i - written_txt = json.loads(params.get(a, encoding='utf8'))['text'] - self.assertTrue(expected_txt == written_txt) + written_alert = json.loads(params.get(a, encoding='utf8')) + self.assertTrue("a"*i == written_alert['extra']) + self.assertTrue(alert["text"] == written_alert['text']) if __name__ == "__main__": unittest.main() diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py index b83116af45..6d11c30ab2 100755 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 import numpy as np -from parameterized import parameterized_class import unittest +from parameterized import parameterized_class +from cereal import log +from common.params import Params from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT from cereal import car from common.conversions import Conversions as CV @@ -31,13 +33,19 @@ def run_cruise_simulation(cruise, e2e, t_end=20.): class TestCruiseSpeed(unittest.TestCase): def test_cruise_speed(self): - for e2e in [False, True]: - for speed in np.arange(5, 40, 5): - print(f'Testing {speed} m/s') - cruise_speed = float(speed) - - simulation_steady_state = run_cruise_simulation(cruise_speed, e2e) - self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s') + params = Params() + personalities = [log.LongitudinalPersonality.relaxed, + log.LongitudinalPersonality.standard, + log.LongitudinalPersonality.aggressive] + for personality in personalities: + params.put("LongitudinalPersonality", str(personality)) + for e2e in [False, True]: + for speed in [5,35]: + print(f'Testing {speed} m/s') + cruise_speed = float(speed) + + simulation_steady_state = run_cruise_simulation(cruise_speed, e2e) + self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s') # TODO: test pcmCruise diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 5185867d2d..9ee7bdfb3b 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import unittest -import numpy as np +from common.params import Params +from cereal import log -from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance +from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance, get_T_FOLLOW from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver @@ -24,14 +25,20 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): class TestFollowingDistance(unittest.TestCase): def test_following_distance(self): - for e2e in [False, True]: - for speed in np.arange(0, 40, 5): - print(f'Testing {speed} m/s') - v_lead = float(speed) - simulation_steady_state = run_following_distance_simulation(v_lead, e2e=e2e) - correct_steady_state = desired_follow_distance(v_lead, v_lead) - err_ratio = 0.2 if e2e else 0.1 - self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5)) + params = Params() + personalities = [log.LongitudinalPersonality.relaxed, + log.LongitudinalPersonality.standard, + log.LongitudinalPersonality.aggressive] + for personality in personalities: + params.put("LongitudinalPersonality", str(personality)) + for e2e in [False, True]: + for speed in [0,10,35]: + print(f'Testing {speed} m/s') + v_lead = float(speed) + simulation_steady_state = run_following_distance_simulation(v_lead, e2e=e2e) + correct_steady_state = desired_follow_distance(v_lead, v_lead, get_T_FOLLOW(personality)) + err_ratio = 0.2 if e2e else 0.1 + self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5)) if __name__ == "__main__": diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index 3a0ff33cb6..ac3651bf2f 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -59,6 +59,9 @@ SUPPORTED_FW_VERSIONS = { b"TM__ SCC F-CUP 1.00 1.00 99110-S1210\x19\x01%\x168 ": ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), + b"TM__ SCC F-CUP 1.00 1.02 99110-S2000\x18\x07\x08\x18W ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), } if __name__ == "__main__": diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 836de1696d..ed922f89c3 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -196,11 +196,11 @@ class Laikad: try: constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) good_constellation = constellation_id in [ConstellationId.GPS, ConstellationId.SBAS, ConstellationId.GLONASS] + report_time = self.gps_time_from_qcom_report(gnss_msg) except NotImplementedError: - good_constellation = False + return False # Garbage timestamps with week > 32767 are sometimes sent by module. # This is an issue with gpsTime and GLONASS time. - report_time = self.gps_time_from_qcom_report(gnss_msg) good_week = report_time.week < np.iinfo(np.int16).max return good_constellation and good_week elif gnss_msg.which() == 'measurementReport' and not self.use_qcom: diff --git a/selfdrive/locationd/models/loc_kf.py b/selfdrive/locationd/models/loc_kf.py index 4c947422b1..1486023a0b 100755 --- a/selfdrive/locationd/models/loc_kf.py +++ b/selfdrive/locationd/models/loc_kf.py @@ -334,12 +334,13 @@ class LocKalman(): # process noise - clock_error_drift = 100.0 if erratic_clock else 0.1 + q_clock_error = 100.0 if erratic_clock else 0.1 + q_clock_error_rate = 10 if erratic_clock else 0.0 self.Q = np.diag([0.03**2, 0.03**2, 0.03**2, 0.0**2, 0.0**2, 0.0**2, 0.0**2, 0.0**2, 0.0**2, 0.1**2, 0.1**2, 0.1**2, - (clock_error_drift)**2, (0)**2, + (q_clock_error)**2, (q_clock_error_rate)**2, (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, (0.02 / 100)**2, 3**2, 3**2, 3**2, diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 7e30b1e3a7..1fd6fc6cee 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 +import os import math import json import numpy as np import cereal.messaging as messaging from cereal import car +from cereal import log from common.params import Params, put_nonblocking from common.realtime import config_realtime_process, DT_MDL from common.numpy_fast import clip @@ -16,8 +18,11 @@ from system.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s ROLL_MAX_DELTA = math.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10) +ROLL_LOWERED_MAX = math.radians(8) ROLL_STD_MAX = math.radians(1.5) LATERAL_ACC_SENSOR_THRESHOLD = 4.0 +OFFSET_MAX = 10.0 +OFFSET_LOWERED_MAX = 8.0 class ParamsLearner: @@ -102,9 +107,20 @@ class ParamsLearner: self.kf.filter.reset_rewind() +def check_valid_with_hysteresis(current_valid: bool, val: float, threshold: float, lowered_threshold: float): + if current_valid: + current_valid = abs(val) < threshold + else: + current_valid = abs(val) < lowered_threshold + return current_valid + + def main(sm=None, pm=None): config_realtime_process([0, 1, 2, 3], 5) + DEBUG = bool(int(os.getenv("DEBUG", "0"))) + REPLAY = bool(int(os.getenv("REPLAY", "0"))) + if sm is None: sm = messaging.SubMaster(['liveLocationKalman', 'carState'], poll=['liveLocationKalman']) if pm is None: @@ -130,10 +146,8 @@ def main(sm=None, pm=None): # Check if starting values are sane if params is not None: try: - angle_offset_sane = abs(params.get('angleOffsetAverageDeg')) < 10.0 steer_ratio_sane = min_sr <= params['steerRatio'] <= max_sr - params_sane = angle_offset_sane and steer_ratio_sane - if not params_sane: + if not steer_ratio_sane: cloudlog.info(f"Invalid starting values found {params}") params = None except Exception as e: @@ -150,13 +164,22 @@ def main(sm=None, pm=None): } cloudlog.info("Parameter learner resetting to default values") - # When driving in wet conditions the stiffness can go down, and then be too low on the next drive - # Without a way to detect this we have to reset the stiffness every drive - params['stiffnessFactor'] = 1.0 - learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg'])) + if not REPLAY: + # When driving in wet conditions the stiffness can go down, and then be too low on the next drive + # Without a way to detect this we have to reset the stiffness every drive + params['stiffnessFactor'] = 1.0 + + pInitial = None + if DEBUG: + pInitial = np.array(params['filterState']['std']) if 'filterState' in params else None + + learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg']), pInitial) angle_offset_average = params['angleOffsetAverageDeg'] angle_offset = angle_offset_average roll = 0.0 + avg_offset_valid = True + total_offset_valid = True + roll_valid = True while True: sm.update() @@ -180,6 +203,9 @@ def main(sm=None, pm=None): roll_std = float(P[States.ROAD_ROLL]) # Account for the opposite signs of the yaw rates sensors_valid = bool(abs(learner.speed * (x[States.YAW_RATE] + learner.yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD) + avg_offset_valid = check_valid_with_hysteresis(avg_offset_valid, angle_offset_average, OFFSET_MAX, OFFSET_LOWERED_MAX) + total_offset_valid = check_valid_with_hysteresis(total_offset_valid, angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX) + roll_valid = check_valid_with_hysteresis(roll_valid, roll, ROLL_MAX, ROLL_LOWERED_MAX) msg = messaging.new_message('liveParameters') @@ -192,9 +218,9 @@ def main(sm=None, pm=None): liveParameters.angleOffsetAverageDeg = angle_offset_average liveParameters.angleOffsetDeg = angle_offset liveParameters.valid = all(( - abs(liveParameters.angleOffsetAverageDeg) < 10.0, - abs(liveParameters.angleOffsetDeg) < 10.0, - abs(liveParameters.roll) < ROLL_MAX, + avg_offset_valid, + total_offset_valid, + roll_valid, roll_std < ROLL_STD_MAX, 0.2 <= liveParameters.stiffnessFactor <= 5.0, min_sr <= liveParameters.steerRatio <= max_sr, @@ -203,6 +229,11 @@ def main(sm=None, pm=None): liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS]) liveParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET]) liveParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST]) + if DEBUG: + liveParameters.filterState = log.LiveLocationKalman.Measurement.new_message() + liveParameters.filterState.value = x.tolist() + liveParameters.filterState.std = P.tolist() + liveParameters.filterState.valid = True msg.valid = sm.all_checks() diff --git a/selfdrive/locationd/test/_test_locationd_lib.py b/selfdrive/locationd/test/_test_locationd_lib.py index 819bb1570e..bec086a767 100755 --- a/selfdrive/locationd/test/_test_locationd_lib.py +++ b/selfdrive/locationd/test/_test_locationd_lib.py @@ -10,10 +10,12 @@ from cffi import FFI import cereal.messaging as messaging from cereal import log +from common.ffi_wrapper import suffix + SENSOR_DECIMATION = 1 VISION_DECIMATION = 1 -LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd.so')) +LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd' + suffix())) class TestLocationdLib(unittest.TestCase): diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index f4d2b39841..2355524d19 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -100,8 +100,9 @@ class ManagerProcess(ABC): try: fn = WATCHDOG_FN + str(self.proc.pid) - # TODO: why can't pylint find struct.unpack? - self.last_watchdog_time = struct.unpack('Q', open(fn, "rb").read())[0] # pylint: disable=no-member + with open(fn, "rb") as f: + # TODO: why can't pylint find struct.unpack? + self.last_watchdog_time = struct.unpack('Q', f.read())[0] # pylint: disable=no-member except Exception: pass diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index ee8d079bdf..8945093584 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -43,8 +43,8 @@ procs = [ NativeProcess("encoderd", "system/loggerd", ["./encoderd"]), NativeProcess("loggerd", "system/loggerd", ["./loggerd"], onroad=False, callback=logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), - NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"], enabled=False), - NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"], enabled=False), + NativeProcess("mapsd", "selfdrive/navd", ["./mapsd"]), + NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"]), NativeProcess("sensord", "system/sensord", ["./sensord"], enabled=not PC), NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)), NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"]), diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index 265bd4071b..15d33f3be9 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -53,15 +53,14 @@ mat3 update_calibration(Eigen::Vector3d device_from_calib_euler, bool wide_camer for (int i=0; i<3*3; i++) { transform.v[i] = warp_matrix(i / 3, i % 3); } - static const mat3 yuv_transform = get_model_yuv_transform(); - return matmul3(yuv_transform, transform); + return transform; } void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcClient &vipc_client_extra, bool main_wide_camera, bool use_extra_client) { // messaging PubMaster pm({"modelV2", "cameraOdometry"}); - SubMaster sm({"lateralPlan", "roadCameraState", "liveCalibration", "driverMonitoringState"}); + SubMaster sm({"lateralPlan", "roadCameraState", "liveCalibration", "driverMonitoringState", "mapRenderState", "navInstruction", "navModel"}); // setup filter to track dropped frames FirstOrderFilter frame_dropped_filter(0., 10., 1. / MODEL_FREQ); @@ -72,6 +71,7 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl mat3 model_transform_main = {}; mat3 model_transform_extra = {}; + bool nav_enabled = false; bool live_calib_seen = false; float driving_style[DRIVING_STYLE_LEN] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; float nav_features[NAV_FEATURE_LEN] = {0}; @@ -137,6 +137,25 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl vec_desire[desire] = 1.0; } + // Enable/disable nav features + double tsm = (float)(nanos_since_boot() - sm["mapRenderState"].getMapRenderState().getLocationMonoTime()) / 1e6; + bool route_valid = sm["navInstruction"].getValid() && (tsm < 1000); + bool render_valid = sm["mapRenderState"].getValid() && (sm["navModel"].getNavModel().getFrameId() == sm["mapRenderState"].getMapRenderState().getFrameId()); + bool nav_valid = route_valid && render_valid; + if (!nav_enabled && nav_valid) { + nav_enabled = true; + } else if (nav_enabled && !nav_valid) { + memset(nav_features, 0, sizeof(float)*NAV_FEATURE_LEN); + nav_enabled = false; + } + + if (nav_enabled && sm.updated("navModel")) { + auto nav_model_features = sm["navModel"].getNavModel().getFeatures(); + for (int i=0; i(model.output.data(), model.output.size()), live_calib_seen); + model_publish(&model, pm, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, *model_output, meta_main.timestamp_eof, model_execution_time, + nav_enabled, live_calib_seen); posenet_publish(pm, meta_main.frame_id, vipc_dropped_frames, *model_output, meta_main.timestamp_eof, live_calib_seen); } diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index 6b0e626c5c..087a4acfa6 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -199,6 +199,44 @@ void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const ModelOutputMet meta.setHardBrakePredicted(above_fcw_threshold); } +void fill_confidence(ModelState* s, cereal::ModelDataV2::Builder &framed) { + if (framed.getFrameId() % (2*MODEL_FREQ) == 0) { + // update every 2s to match predictions interval + auto dbps = framed.getMeta().getDisengagePredictions().getBrakeDisengageProbs(); + auto dgps = framed.getMeta().getDisengagePredictions().getGasDisengageProbs(); + auto dsps = framed.getMeta().getDisengagePredictions().getSteerOverrideProbs(); + + float any_dp[DISENGAGE_LEN]; + float dp_ind[DISENGAGE_LEN]; + + for (int i = 0; i < DISENGAGE_LEN; i++) { + any_dp[i] = 1 - ((1-dbps[i])*(1-dgps[i])*(1-dsps[i])); // any disengage prob + } + + dp_ind[0] = any_dp[0]; + for (int i = 0; i < DISENGAGE_LEN-1; i++) { + dp_ind[i+1] = (any_dp[i+1] - any_dp[i]) / (1 - any_dp[i]); // independent disengage prob for each 2s slice + } + + // rolling buf for 2, 4, 6, 8, 10s + std::memmove(&s->disengage_buffer[0], &s->disengage_buffer[DISENGAGE_LEN], sizeof(float) * DISENGAGE_LEN * (DISENGAGE_LEN-1)); + std::memcpy(&s->disengage_buffer[DISENGAGE_LEN * (DISENGAGE_LEN-1)], &dp_ind[0], sizeof(float) * DISENGAGE_LEN); + } + + float score = 0; + for (int i = 0; i < DISENGAGE_LEN; i++) { + score += s->disengage_buffer[i*DISENGAGE_LEN+DISENGAGE_LEN-1-i] / DISENGAGE_LEN; + } + + if (score < RYG_GREEN) { + framed.setConfidence(cereal::ModelDataV2::ConfidenceClass::GREEN); + } else if (score < RYG_YELLOW) { + framed.setConfidence(cereal::ModelDataV2::ConfidenceClass::YELLOW); + } else { + framed.setConfidence(cereal::ModelDataV2::ConfidenceClass::RED); + } +} + template void fill_xyzt(cereal::XYZTData::Builder xyzt, const std::array &t, const std::array &x, const std::array &y, const std::array &z) { @@ -313,7 +351,7 @@ void fill_road_edges(cereal::ModelDataV2::Builder &framed, const std::array plan_t; std::fill_n(plan_t.data(), plan_t.size(), NAN); @@ -343,6 +381,9 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_out // meta fill_meta(framed.initMeta(), net_outputs.meta); + // confidence + fill_confidence(s, framed); + // leads auto leads = framed.initLeadsV3(LEAD_MHP_SELECTION); std::array t_offsets = {0.0, 2.0, 4.0}; @@ -362,9 +403,9 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_out temporal_pose.setRotStd({exp(r_std.x), exp(r_std.y), exp(r_std.z)}); } -void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, +void model_publish(ModelState* s, PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, const ModelOutput &net_outputs, uint64_t timestamp_eof, - float model_execution_time, kj::ArrayPtr raw_pred, const bool valid) { + float model_execution_time, const bool nav_enabled, const bool valid) { const uint32_t frame_age = (frame_id > vipc_frame_id) ? (frame_id - vipc_frame_id) : 0; MessageBuilder msg; auto framed = msg.initEvent(valid).initModelV2(); @@ -374,10 +415,11 @@ void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id framed.setFrameDropPerc(frame_drop * 100); framed.setTimestampEof(timestamp_eof); framed.setModelExecutionTime(model_execution_time); + framed.setNavEnabled(nav_enabled); if (send_raw_pred) { - framed.setRawPredictions(raw_pred.asBytes()); + framed.setRawPredictions((kj::ArrayPtr(s->output.data(), s->output.size())).asBytes()); } - fill_model(framed, net_outputs); + fill_model(s, framed, net_outputs); pm.send("modelV2", msg); } diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index 1214c30062..ae55e9c8a0 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -262,6 +262,7 @@ struct ModelState { ModelFrame *frame = nullptr; ModelFrame *wide_frame = nullptr; std::array feature_buffer = {}; + std::array disengage_buffer = {}; std::array output = {}; std::unique_ptr m; #ifdef DESIRE @@ -283,8 +284,8 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context); ModelOutput *model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* buf_wide, const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, float *driving_style, float *nav_features, bool prepare_only); void model_free(ModelState* s); -void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, +void model_publish(ModelState* s, PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, const ModelOutput &net_outputs, uint64_t timestamp_eof, - float model_execution_time, kj::ArrayPtr raw_pred, const bool valid); + float model_execution_time, const bool nav_enabled, const bool valid); void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, const ModelOutput &net_outputs, uint64_t timestamp_eof, const bool valid); diff --git a/selfdrive/modeld/models/navmodel.onnx b/selfdrive/modeld/models/navmodel.onnx index 397b8d90e2..851e1f30b3 100644 --- a/selfdrive/modeld/models/navmodel.onnx +++ b/selfdrive/modeld/models/navmodel.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9028b36a591763724e95205b48f37f09260b4434fb1f3c6f228db1710a401aa8 -size 12258591 +oid sha256:05596296cdc1955e2c32a1d83fe82473c3e40c6e74ee545e3915fc59220a4088 +size 12281735 diff --git a/selfdrive/modeld/models/navmodel_q.dlc b/selfdrive/modeld/models/navmodel_q.dlc index 984cf0a553..d9155c592a 100644 --- a/selfdrive/modeld/models/navmodel_q.dlc +++ b/selfdrive/modeld/models/navmodel_q.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc7ade56bb4f9525c84a46df22252ea1e321a0518cbcef8bdfc76ccd8ad10b41 -size 3154304 +oid sha256:f81eeb842a0ab9a444a4399bdd881db834035d43e16cd11411e245e0f28964de +size 3166978 diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 53c1224ae8..d339e4ad55 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4aa77af40335462062c6eb06632bb84cbc8e5c9e3ad759f66862711620e2a840 +oid sha256:9a9a9ded468e0a5255b88d42f25075c63babfac9c8acab72259c1d5bffc1516b size 46117948 diff --git a/selfdrive/modeld/tests/test_modeld.py b/selfdrive/modeld/tests/test_modeld.py index 2fcff785a9..758948811e 100755 --- a/selfdrive/modeld/tests/test_modeld.py +++ b/selfdrive/modeld/tests/test_modeld.py @@ -9,10 +9,7 @@ from cereal.visionipc import VisionIpcServer, VisionStreamType from common.transformations.camera import tici_f_frame_size from common.realtime import DT_MDL from selfdrive.manager.process_config import managed_processes - - -VIPC_STREAM = {"roadCameraState": VisionStreamType.VISION_STREAM_ROAD, "driverCameraState": VisionStreamType.VISION_STREAM_DRIVER, - "wideRoadCameraState": VisionStreamType.VISION_STREAM_WIDE_ROAD} +from selfdrive.test.process_replay.vision_meta import meta_from_camera_state IMG = np.zeros(int(tici_f_frame_size[0]*tici_f_frame_size[1]*(3/2)), dtype=np.uint8) IMG_BYTES = IMG.flatten().tobytes() @@ -48,9 +45,10 @@ class TestModeld(unittest.TestCase): cs.frameId = frame_id cs.timestampSof = int((frame_id * DT_MDL) * 1e9) cs.timestampEof = int(cs.timestampSof + (DT_MDL * 1e9)) + cam_meta = meta_from_camera_state(cam) self.pm.send(msg.which(), msg) - self.vipc_server.send(VIPC_STREAM[msg.which()], IMG_BYTES, cs.frameId, + self.vipc_server.send(cam_meta.stream, IMG_BYTES, cs.frameId, cs.timestampSof, cs.timestampEof) return cs diff --git a/selfdrive/navd/.gitignore b/selfdrive/navd/.gitignore index a070fe32bb..4801d60a2c 100644 --- a/selfdrive/navd/.gitignore +++ b/selfdrive/navd/.gitignore @@ -1,5 +1,6 @@ moc_* *.moc +mapsd map_renderer libmap_renderer.so diff --git a/selfdrive/navd/README.md b/selfdrive/navd/README.md index 6c7f7eabe3..3047b7f8eb 100644 --- a/selfdrive/navd/README.md +++ b/selfdrive/navd/README.md @@ -1,6 +1,6 @@ # navigation -This directory contains two daemons, `navd` and `map_renderer`, which support navigation in the openpilot stack. +This directory contains two daemons, `navd` and `mapsd`, which support navigation in the openpilot stack. ### navd diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index 23b36adc0a..6c0026fe03 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -17,4 +17,4 @@ if arch in ['larch64', 'x86_64']: map_env["RPATH"].append(Dir('.').abspath) map_env["LIBPATH"].append(Dir('.').abspath) maplib = map_env.SharedLibrary("maprender", ["map_renderer.cc"], LIBS=libs) - map_env.Program("map_renderer", ["main.cc", ], LIBS=[maplib[0].get_path(), ] + libs) + map_env.Program("mapsd", ["main.cc", ], LIBS=[maplib[0].get_path(), ] + libs) diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index 5c7e17cd51..7dd3f04a04 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -101,8 +101,7 @@ void MapRenderer::msgUpdate() { auto pos = location.getPositionGeodetic(); auto orientation = location.getCalibratedOrientationNED(); - bool localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && pos.getValid(); - if (localizer_valid && (sm->rcv_frame("liveLocationKalman") % LLK_DECIMATION) == 0) { + if ((sm->rcv_frame("liveLocationKalman") % LLK_DECIMATION) == 0) { float bearing = RAD2DEG(orientation.getValue()[2]); updatePosition(get_point_along_line(pos.getValue()[0], pos.getValue()[1], bearing, MAP_OFFSET), bearing); @@ -116,6 +115,7 @@ void MapRenderer::msgUpdate() { if (!loaded()) { LOGE("failed to render map after retry"); + publish(0, false); } } } @@ -161,7 +161,7 @@ void MapRenderer::update() { double end_t = millis_since_boot(); if ((vipc_server != nullptr) && loaded()) { - publish((end_t - start_t) / 1000.0); + publish((end_t - start_t) / 1000.0, true); } } @@ -174,7 +174,7 @@ void MapRenderer::sendThumbnail(const uint64_t ts, const kj::Array pm->send("navThumbnail", msg); } -void MapRenderer::publish(const double render_time) { +void MapRenderer::publish(const double render_time, const bool loaded) { QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); uint64_t ts = nanos_since_boot(); VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_MAP); @@ -213,8 +213,13 @@ void MapRenderer::publish(const double render_time) { } // Send state msg + auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); + bool localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && location.getPositionGeodetic().getValid(); + MessageBuilder msg; - auto state = msg.initEvent().initMapRenderState(); + auto evt = msg.initEvent(); + auto state = evt.initMapRenderState(); + evt.setValid(loaded && localizer_valid); state.setLocationMonoTime((*sm)["liveLocationKalman"].getLogMonoTime()); state.setRenderTime(render_time); state.setFrameId(frame_id); diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index 0019030b6d..41489d41da 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -34,7 +34,7 @@ private: std::unique_ptr vipc_server; std::unique_ptr pm; std::unique_ptr sm; - void publish(const double render_time); + void publish(const double render_time, const bool loaded); void sendThumbnail(const uint64_t ts, const kj::Array &buf); QMapboxGLSettings m_settings; diff --git a/selfdrive/test/fuzzy_generation.py b/selfdrive/test/fuzzy_generation.py new file mode 100644 index 0000000000..3dc43e7347 --- /dev/null +++ b/selfdrive/test/fuzzy_generation.py @@ -0,0 +1,69 @@ +import hypothesis.strategies as st +import random + +class FuzzyGenerator: + def __init__(self, real_floats): + self.real_floats=real_floats + + def generate_native_type(self, field): + def floats(**kwargs): + allow_nan = not self.real_floats + allow_infinity = not self.real_floats + return st.floats(**kwargs, allow_nan=allow_nan, allow_infinity=allow_infinity) + + if field == 'bool': + return st.booleans() + elif field == 'int8': + return st.integers(min_value=-2**7, max_value=2**7-1) + elif field == 'int16': + return st.integers(min_value=-2**15, max_value=2**15-1) + elif field == 'int32': + return st.integers(min_value=-2**31, max_value=2**31-1) + elif field == 'int64': + return st.integers(min_value=-2**63, max_value=2**63-1) + elif field == 'uint8': + return st.integers(min_value=0, max_value=2**8-1) + elif field == 'uint16': + return st.integers(min_value=0, max_value=2**16-1) + elif field == 'uint32': + return st.integers(min_value=0, max_value=2**32-1) + elif field == 'uint64': + return st.integers(min_value=0, max_value=2**64-1) + elif field == 'float32': + return floats(width=32) + elif field == 'float64': + return floats(width=64) + elif field == 'text': + return st.text(max_size=1000) + elif field == 'data': + return st.text(max_size=1000) + elif field == 'anyPointer': + return st.text() + else: + raise NotImplementedError(f'Invalid type : {field}') + + def generate_field(self, field): + def rec(field_type): + if field_type.which() == 'struct': + return self.generate_struct(field.schema.elementType if base_type == 'list' else field.schema) + elif field_type.which() == 'list': + return st.lists(rec(field_type.list.elementType)) + elif field_type.which() == 'enum': + schema = field.schema.elementType if base_type == 'list' else field.schema + return st.sampled_from(list(schema.enumerants.keys())) + else: + return self.generate_native_type(field_type.which()) + + if 'slot' in field.proto.to_dict(): + base_type = field.proto.slot.type.which() + return rec(field.proto.slot.type) + else: + return self.generate_struct(field.schema) + + def generate_struct(self, schema): + full_fill = list(schema.non_union_fields) if schema.non_union_fields else [] + single_fill = [random.choice(schema.union_fields)] if schema.union_fields else [] + return st.fixed_dictionaries(dict((field, self.generate_field(schema.fields[field])) for field in full_fill + single_fill)) + +def get_random_msg(struct, real_floats=False): + return FuzzyGenerator(real_floats=real_floats).generate_struct(struct.schema) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 31b3acde56..6c8055e3e4 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -1,13 +1,57 @@ +from collections import defaultdict + from cereal import messaging +from selfdrive.test.process_replay.vision_meta import meta_from_encode_index -def migrate_all(lr, old_logtime=False): +def migrate_all(lr, old_logtime=False, camera_states=False): msgs = migrate_sensorEvents(lr, old_logtime) msgs = migrate_carParams(msgs, old_logtime) + if camera_states: + msgs = migrate_cameraStates(msgs) return msgs +def migrate_cameraStates(lr): + all_msgs = [] + frame_to_encode_id = defaultdict(dict) + + for msg in lr: + if msg.which() not in ["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx"]: + continue + + encode_index = getattr(msg, msg.which()) + meta = meta_from_encode_index(msg.which()) + + assert encode_index.segmentId < 1200, f"Encoder index segmentId greater that 1200: {msg.which()} {encode_index.segmentId}" + frame_to_encode_id[meta.camera_state][encode_index.frameId] = encode_index.segmentId + + for msg in lr: + if msg.which() not in ["roadCameraState", "wideRoadCameraState", "driverCameraState"]: + all_msgs.append(msg) + continue + + camera_state = getattr(msg, msg.which()) + encode_id = frame_to_encode_id[msg.which()].get(camera_state.frameId) + if encode_id is None: + print(f"Missing encoded frame for camera feed {msg.which()} with frameId: {camera_state.frameId}") + continue + + new_msg = messaging.new_message(msg.which()) + new_camera_state = getattr(new_msg, new_msg.which()) + new_camera_state.frameId = encode_id + new_camera_state.encodeId = encode_id + new_camera_state.timestampSof = camera_state.timestampSof + new_camera_state.timestampEof = camera_state.timestampEof + new_msg.logMonoTime = msg.logMonoTime + new_msg.valid = msg.valid + + all_msgs.append(new_msg.as_reader()) + + return all_msgs + + def migrate_carParams(lr, old_logtime=False): all_msgs = [] for msg in lr: diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 55d95a6a6a..e9789489e7 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -4,18 +4,15 @@ import sys import time from collections import defaultdict from typing import Any -from itertools import zip_longest import cereal.messaging as messaging -from cereal.visionipc import VisionIpcServer, VisionStreamType from common.spinner import Spinner -from common.timeout import Timeout -from common.transformations.camera import tici_f_frame_size, tici_d_frame_size from system.hardware import PC from selfdrive.manager.process_config import managed_processes from selfdrive.test.openpilotci import BASE_URL, get_url from selfdrive.test.process_replay.compare_logs import compare_logs from selfdrive.test.process_replay.test_processes import format_diff +from selfdrive.test.process_replay.process_replay import get_process_config, replay_process from system.version import get_commit from tools.lib.framereader import FrameReader from tools.lib.logreader import LogReader @@ -27,20 +24,30 @@ MAX_FRAMES = 100 if PC else 600 NAV_FRAMES = 50 NO_NAV = "NO_NAV" in os.environ -SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0")) +SEND_EXTRA_INPUTS = bool(int(os.getenv("SEND_EXTRA_INPUTS", "0"))) -VIPC_STREAM = {"roadCameraState": VisionStreamType.VISION_STREAM_ROAD, "driverCameraState": VisionStreamType.VISION_STREAM_DRIVER, - "wideRoadCameraState": VisionStreamType.VISION_STREAM_WIDE_ROAD} def get_log_fn(ref_commit, test_route): return f"{test_route}_model_tici_{ref_commit}.bz2" -def replace_calib(msg, calib): - msg = msg.as_builder() - if calib is not None: - msg.liveCalibration.rpyCalib = calib.tolist() - return msg +def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types): + all_msgs = [] + cam_state_counts = defaultdict(int) + # keep adding messages until cam states are equal to MAX_FRAMES + for msg in sorted(logs, key=lambda m: m.logMonoTime): + all_msgs.append(msg) + if msg.which() in frs_types: + cam_state_counts[msg.which()] += 1 + + if all(cam_state_counts[state] == max_frames for state in frs_types): + break + + if len(include_all_types) != 0: + other_msgs = [m for m in logs if m.which() in include_all_types] + all_msgs.extend(other_msgs) + + return all_msgs def nav_model_replay(lr): @@ -102,102 +109,38 @@ def model_replay(lr, frs): else: spinner = None - vipc_server = VisionIpcServer("camerad") - vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, *(tici_f_frame_size)) - vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *(tici_d_frame_size)) - vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *(tici_f_frame_size)) - vipc_server.start_listener() + log_msgs = [] - sm = messaging.SubMaster(['modelV2', 'driverStateV2']) - pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'driverCameraState', 'liveCalibration', 'lateralPlan']) + # modeld is using frame pairs + modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, {"roadEncodeIdx", "wideRoadEncodeIdx"}) + dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx"}) + if not SEND_EXTRA_INPUTS: + modeld_logs = [msg for msg in modeld_logs if msg.which() not in ["liveCalibration", "lateralPlan"]] + dmodeld_logs = [msg for msg in dmodeld_logs if msg.which() not in ["liveCalibration", "lateralPlan"]] + # initial calibration + cal_msg = next(msg for msg in lr if msg.which() == "liveCalibration").as_builder() + cal_msg.logMonoTime = lr[0].logMonoTime + modeld_logs.insert(0, cal_msg.as_reader()) + dmodeld_logs.insert(0, cal_msg.as_reader()) + + modeld = get_process_config("modeld") + dmonitoringmodeld = get_process_config("dmonitoringmodeld") try: - managed_processes['modeld'].start() - managed_processes['dmonitoringmodeld'].start() - time.sleep(5) - sm.update(1000) - - log_msgs = [] - last_desire = None - recv_cnt = defaultdict(int) - frame_idxs = defaultdict(int) - - # init modeld with valid calibration - cal_msgs = [msg for msg in lr if msg.which() == "liveCalibration"] - for _ in range(5): - pm.send(cal_msgs[0].which(), cal_msgs[0].as_builder()) - time.sleep(0.1) - - msgs = defaultdict(list) - for msg in lr: - msgs[msg.which()].append(msg) - - for cam_msgs in zip_longest(msgs['roadCameraState'], msgs['wideRoadCameraState'], msgs['driverCameraState']): - # need a pair of road/wide msgs - if None in (cam_msgs[0], cam_msgs[1]): - break - - for msg in cam_msgs: - if msg is None: - continue - - if SEND_EXTRA_INPUTS: - if msg.which() == "liveCalibration": - last_calib = list(msg.liveCalibration.rpyCalib) - pm.send(msg.which(), replace_calib(msg, last_calib)) - elif msg.which() == "lateralPlan": - last_desire = msg.lateralPlan.desire - dat = messaging.new_message('lateralPlan') - dat.lateralPlan.desire = last_desire - pm.send('lateralPlan', dat) - - if msg.which() in VIPC_STREAM: - msg = msg.as_builder() - camera_state = getattr(msg, msg.which()) - img = frs[msg.which()].get(frame_idxs[msg.which()], pix_fmt="nv12")[0] - frame_idxs[msg.which()] += 1 - - # send camera state and frame - camera_state.frameId = frame_idxs[msg.which()] - pm.send(msg.which(), msg) - vipc_server.send(VIPC_STREAM[msg.which()], img.flatten().tobytes(), camera_state.frameId, - camera_state.timestampSof, camera_state.timestampEof) - - recv = None - if msg.which() in ('roadCameraState', 'wideRoadCameraState'): - if min(frame_idxs['roadCameraState'], frame_idxs['wideRoadCameraState']) > recv_cnt['modelV2']: - recv = "modelV2" - elif msg.which() == 'driverCameraState': - recv = "driverStateV2" - - # wait for a response - with Timeout(15, f"timed out waiting for {recv}"): - if recv: - recv_cnt[recv] += 1 - log_msgs.append(messaging.recv_one(sm.sock[recv])) - - if spinner: - spinner.update("replaying models: road %d/%d, driver %d/%d" % (frame_idxs['roadCameraState'], - frs['roadCameraState'].frame_count, frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count)) - - - if any(frame_idxs[c] >= frs[c].frame_count for c in frame_idxs.keys()) or frame_idxs['roadCameraState'] == MAX_FRAMES: - break - else: - print(f'Received {frame_idxs["roadCameraState"]} frames') - + if spinner: + spinner.update("running model replay") + modeld_msgs = replay_process(modeld, modeld_logs, frs) + dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs) + log_msgs.extend([m for m in modeld_msgs if m.which() == "modelV2"]) + log_msgs.extend([m for m in dmonitoringmodeld_msgs if m.which() == "driverStateV2"]) finally: if spinner: spinner.close() - managed_processes['modeld'].stop() - managed_processes['dmonitoringmodeld'].stop() - return log_msgs if __name__ == "__main__": - update = "--update" in sys.argv replay_dir = os.path.dirname(os.path.abspath(__file__)) ref_commit_fn = os.path.join(replay_dir, "model_replay_ref_commit") @@ -247,10 +190,18 @@ if __name__ == "__main__": ref_commit = f.read().strip() log_fn = get_log_fn(ref_commit, TEST_ROUTE) try: - expected_msgs = 2*MAX_FRAMES + all_logs = list(LogReader(BASE_URL + log_fn)) + cmp_log = [] + + # logs are ordered based on type: modelV2, driverStateV2, nav messages (navThumbnail, mapRenderState, navModel) + model_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "modelV2") + cmp_log += all_logs[model_start_index:model_start_index + MAX_FRAMES] + dmon_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "driverStateV2") + cmp_log += all_logs[dmon_start_index:dmon_start_index + MAX_FRAMES] if not NO_NAV: - expected_msgs += NAV_FRAMES*3 - cmp_log = list(LogReader(BASE_URL + log_fn))[:expected_msgs] + nav_start_index = next(i for i, m in enumerate(all_logs) if m.which() in ["navThumbnail", "mapRenderState", "navModel"]) + nav_logs = all_logs[nav_start_index:nav_start_index + NAV_FRAMES*3] + cmp_log += nav_logs ignore = [ 'logMonoTime', diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index b89468eebb..19c316b604 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -69270554b3c3ae574b9f357f2473395edf7db8af +3257b354b31c3d42c85a86fc4883f29c47ef56cb diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 13dfb661b7..78907ecc18 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -5,12 +5,14 @@ import signal import platform from collections import OrderedDict from dataclasses import dataclass, field -from typing import Dict, List, Optional, Callable +from typing import Dict, List, Optional, Callable, Union, Any from tqdm import tqdm +import capnp import cereal.messaging as messaging from cereal import car from cereal.services import service_list +from cereal.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name from common.params import Params from common.timeout import Timeout from common.realtime import DT_CTRL @@ -18,7 +20,9 @@ from panda.python import ALTERNATIVE_EXPERIENCE from selfdrive.car.car_helpers import get_car, interfaces from selfdrive.manager.process_config import managed_processes from selfdrive.test.process_replay.helpers import OpenpilotPrefix +from selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from selfdrive.test.process_replay.migration import migrate_all +from tools.lib.logreader import LogReader # Numpy gives different results based on CPU features after version 19 NUMPY_TOLERANCE = 1e-7 @@ -27,24 +31,24 @@ TIMEOUT = 15 PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__)) FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/") - class ReplayContext: def __init__(self, cfg): self.proc_name = cfg.proc_name self.pubs = cfg.pubs - self.drained_pub = cfg.drained_pub - assert(len(self.pubs) != 0 or self.drained_pub is not None) + self.main_pub = cfg.main_pub + self.main_pub_drained = cfg.main_pub_drained + assert(len(self.pubs) != 0 or self.main_pub is not None) def __enter__(self): messaging.toggle_fake_events(True) messaging.set_fake_prefix(self.proc_name) - if self.drained_pub is None: + if self.main_pub is None: self.events = OrderedDict() for pub in self.pubs: self.events[pub] = messaging.fake_event_handle(pub, enable=True) else: - self.events = {self.drained_pub: messaging.fake_event_handle(self.drained_pub, enable=True)} + self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)} return self @@ -81,7 +85,7 @@ class ReplayContext: def wait_for_next_recv(self, trigger_empty_recv): index = messaging.wait_for_one_event(self.all_recv_called_events) - if self.drained_pub is not None and trigger_empty_recv: + if self.main_pub is not None and self.main_pub_drained and trigger_empty_recv: self.all_recv_called_events[index].clear() self.all_recv_ready_events[index].set() self.all_recv_called_events[index].wait() @@ -93,16 +97,19 @@ class ProcessConfig: pubs: List[str] subs: List[str] ignore: List[str] - config_callback: Optional[Callable] - init_callback: Optional[Callable] - should_recv_callback: Optional[Callable] + config_callback: Optional[Callable] = None + init_callback: Optional[Callable] = None + should_recv_callback: Optional[Callable] = None tolerance: Optional[float] = None environ: Dict[str, str] = field(default_factory=dict) subtest_name: str = "" field_tolerances: Dict[str, float] = field(default_factory=dict) timeout: int = 30 simulation: bool = True - drained_pub: Optional[str] = None + main_pub: Optional[str] = None + main_pub_drained: bool = True + vision_pubs: List[str] = field(default_factory=list) + ignore_alive_pubs: List[str] = field(default_factory=list) class DummySocket: @@ -141,6 +148,7 @@ def controlsd_fingerprint_callback(rc, pm, msgs, fingerprint): def get_car_params_callback(rc, pm, msgs, fingerprint): + params = Params() if fingerprint: CarInterface, _, _ = interfaces[fingerprint] CP = CarInterface.get_non_essential_params(fingerprint) @@ -149,12 +157,15 @@ def get_car_params_callback(rc, pm, msgs, fingerprint): sendcan = DummySocket() canmsgs = [msg for msg in msgs if msg.which() == "can"] - assert len(canmsgs) != 0, "CAN messages are required for carParams initialization" + has_cached_cp = params.get("CarParamsCache") is not None + assert len(canmsgs) != 0, "CAN messages are required for fingerprinting" + assert os.environ.get("SKIP_FW_QUERY", False) or has_cached_cp, "CarParamsCache is required for fingerprinting. Make sure to keep carParams msgs in the logs." for m in canmsgs[:300]: can.send(m.as_builder().to_bytes()) _, CP = get_car(can, sendcan, Params().get_bool("ExperimentalLongitudinalEnabled")) - Params().put("CarParams", CP.to_bytes()) + params.put("CarParams", CP.to_bytes()) + return CP def controlsd_rcv_callback(msg, cfg, frame): @@ -171,10 +182,6 @@ def controlsd_rcv_callback(msg, cfg, frame): return len(socks) > 0 -def radar_rcv_callback(msg, cfg, frame): - return msg.which() == "can" - - def calibration_rcv_callback(msg, cfg, frame): # calibrationd publishes 1 calibrationData every 5 cameraOdometry packets. # should_recv always true to increment frame @@ -186,6 +193,42 @@ def torqued_rcv_callback(msg, cfg, frame): return (frame - 1) == 0 or msg.which() == 'liveLocationKalman' +def dmonitoringmodeld_rcv_callback(msg, cfg, frame): + return msg.which() == "driverCameraState" + + +class ModeldCameraSyncRcvCallback: + def __init__(self): + self.road_present = False + self.wide_road_present = False + self.is_dual_camera = True + + def __call__(self, msg, cfg, frame): + if msg.which() == "initData": + self.is_dual_camera = msg.initData.deviceType in ["tici", "tizi"] + elif msg.which() == "roadCameraState": + self.road_present = True + elif msg.which() == "wideRoadCameraState": + self.wide_road_present = True + + if self.road_present and self.wide_road_present: + self.road_present, self.wide_road_present = False, False + return True + elif self.road_present and not self.is_dual_camera: + self.road_present = False + return True + else: + return False + + +class MessageBasedRcvCallback: + def __init__(self, trigger_msg_type): + self.trigger_msg_type = trigger_msg_type + + def __call__(self, msg, cfg, frame): + return msg.which() == self.trigger_msg_type + + class FrequencyBasedRcvCallback: def __init__(self, trigger_msg_type): self.trigger_msg_type = trigger_msg_type @@ -203,17 +246,17 @@ class FrequencyBasedRcvCallback: def laikad_config_pubsub_callback(params, cfg): ublox = params.get_bool("UbloxAvailable") - drained_key = "ubloxGnss" if ublox else "qcomGnss" + main_key = "ubloxGnss" if ublox else "qcomGnss" sub_keys = ({"qcomGnss", } if ublox else {"ubloxGnss", }) - return set(cfg.pubs) - sub_keys, drained_key + return set(cfg.pubs) - sub_keys, main_key, True def locationd_config_pubsub_callback(params, cfg): ublox = params.get_bool("UbloxAvailable") sub_keys = ({"gpsLocation", } if ublox else {"gpsLocationExternal", }) - return set(cfg.pubs) - sub_keys, None + return set(cfg.pubs) - sub_keys, None, False CONFIGS = [ @@ -227,29 +270,26 @@ CONFIGS = [ ], subs=["controlsState", "carState", "carControl", "sendcan", "carEvents", "carParams"], ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"], - config_callback=None, init_callback=controlsd_fingerprint_callback, should_recv_callback=controlsd_rcv_callback, tolerance=NUMPY_TOLERANCE, simulation=False, - drained_pub="can", + main_pub="can", ), ProcessConfig( proc_name="radard", pubs=["can", "carState", "modelV2"], subs=["radarState", "liveTracks"], ignore=["logMonoTime", "valid", "radarState.cumLagMs"], - config_callback=None, init_callback=get_car_params_callback, - should_recv_callback=radar_rcv_callback, - drained_pub="can", + should_recv_callback=MessageBasedRcvCallback("can"), + main_pub="can", ), ProcessConfig( proc_name="plannerd", pubs=["modelV2", "carControl", "carState", "controlsState", "radarState"], subs=["lateralPlan", "longitudinalPlan", "uiPlan"], ignore=["logMonoTime", "valid", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"], - config_callback=None, init_callback=get_car_params_callback, should_recv_callback=FrequencyBasedRcvCallback("modelV2"), tolerance=NUMPY_TOLERANCE, @@ -259,8 +299,6 @@ CONFIGS = [ pubs=["carState", "cameraOdometry", "carParams"], subs=["liveCalibration"], ignore=["logMonoTime", "valid"], - config_callback=None, - init_callback=None, should_recv_callback=calibration_rcv_callback, ), ProcessConfig( @@ -268,8 +306,6 @@ CONFIGS = [ pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "controlsState"], subs=["driverMonitoringState"], ignore=["logMonoTime", "valid"], - config_callback=None, - init_callback=None, should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"), tolerance=NUMPY_TOLERANCE, ), @@ -282,8 +318,6 @@ CONFIGS = [ subs=["liveLocationKalman"], ignore=["logMonoTime", "valid"], config_callback=locationd_config_pubsub_callback, - init_callback=None, - should_recv_callback=None, tolerance=NUMPY_TOLERANCE, ), ProcessConfig( @@ -291,7 +325,6 @@ CONFIGS = [ pubs=["liveLocationKalman", "carState"], subs=["liveParameters"], ignore=["logMonoTime", "valid"], - config_callback=None, init_callback=get_car_params_callback, should_recv_callback=FrequencyBasedRcvCallback("liveLocationKalman"), tolerance=NUMPY_TOLERANCE, @@ -301,9 +334,6 @@ CONFIGS = [ pubs=["ubloxRaw"], subs=["ubloxGnss", "gpsLocationExternal"], ignore=["logMonoTime"], - config_callback=None, - init_callback=None, - should_recv_callback=None, ), ProcessConfig( proc_name="laikad", @@ -311,22 +341,43 @@ CONFIGS = [ subs=["gnssMeasurements"], ignore=["logMonoTime"], config_callback=laikad_config_pubsub_callback, - init_callback=None, - should_recv_callback=None, tolerance=NUMPY_TOLERANCE, timeout=60*10, # first messages are blocked on internet assistance - drained_pub="ubloxGnss", # config_callback will switch this to qcom if needed + main_pub="ubloxGnss", # config_callback will switch this to qcom if needed ), ProcessConfig( proc_name="torqued", pubs=["liveLocationKalman", "carState", "carControl"], subs=["liveTorqueParameters"], ignore=["logMonoTime"], - config_callback=None, init_callback=get_car_params_callback, should_recv_callback=torqued_rcv_callback, tolerance=NUMPY_TOLERANCE, ), + ProcessConfig( + proc_name="modeld", + pubs=["lateralPlan", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState"], + subs=["modelV2", "cameraOdometry"], + ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime"], + should_recv_callback=ModeldCameraSyncRcvCallback(), + tolerance=NUMPY_TOLERANCE, + main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("roadCameraState").stream), + main_pub_drained=False, + vision_pubs=["roadCameraState", "wideRoadCameraState"], + ignore_alive_pubs=["wideRoadCameraState"], + ), + ProcessConfig( + proc_name="dmonitoringmodeld", + pubs=["liveCalibration", "driverCameraState"], + subs=["driverStateV2"], + ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.dspExecutionTime"], + should_recv_callback=dmonitoringmodeld_rcv_callback, + tolerance=NUMPY_TOLERANCE, + main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("driverCameraState").stream), + main_pub_drained=False, + vision_pubs=["driverCameraState"], + ignore_alive_pubs=["driverCameraState"], + ), ] @@ -342,9 +393,9 @@ def replay_process_with_name(name, lr, *args, **kwargs): return replay_process(cfg, lr, *args, **kwargs) -def replay_process(cfg, lr, fingerprint=None, return_all_logs=False, disable_progress=False): - all_msgs = migrate_all(lr, old_logtime=True) - process_logs = _replay_single_process(cfg, all_msgs, fingerprint, disable_progress) +def replay_process(cfg, lr, frs=None, fingerprint=None, return_all_logs=False, custom_params=None, disable_progress=False): + all_msgs = migrate_all(lr, old_logtime=True, camera_states=len(cfg.vision_pubs) != 0) + process_logs = _replay_single_process(cfg, all_msgs, frs, fingerprint, custom_params, disable_progress) if return_all_logs: keys = set(cfg.subs) @@ -358,7 +409,10 @@ def replay_process(cfg, lr, fingerprint=None, return_all_logs=False, disable_pro return log_msgs -def _replay_single_process(cfg, lr, fingerprint, disable_progress): +def _replay_single_process( + cfg: ProcessConfig, lr: Union[LogReader, List[capnp._DynamicStructReader]], frs: Optional[Dict[str, Any]], + fingerprint: Optional[str], custom_params: Optional[Dict[str, Any]], disable_progress: bool +): with OpenpilotPrefix(): controlsState = None initialized = False @@ -374,15 +428,15 @@ def _replay_single_process(cfg, lr, fingerprint, disable_progress): assert controlsState is not None and initialized, "controlsState never initialized" if fingerprint is not None: - setup_env(cfg=cfg, controlsState=controlsState, lr=lr, fingerprint=fingerprint) + setup_env(cfg=cfg, controlsState=controlsState, lr=lr, fingerprint=fingerprint, custom_params=custom_params) else: CP = next((m.carParams for m in lr if m.which() == "carParams"), None) assert CP is not None or "carParams" not in cfg.pubs, "carParams are missing and process needs it" - setup_env(CP=CP, cfg=cfg, controlsState=controlsState, lr=lr) + setup_env(cfg=cfg, CP=CP, controlsState=controlsState, lr=lr, custom_params=custom_params) if cfg.config_callback is not None: params = Params() - cfg.pubs, cfg.drained_pub = cfg.config_callback(params, cfg) + cfg.pubs, cfg.main_pub, cfg.main_pub_drained = cfg.config_callback(params, cfg) all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime) pub_msgs = [msg for msg in all_msgs if msg.which() in set(cfg.pubs)] @@ -391,6 +445,13 @@ def _replay_single_process(cfg, lr, fingerprint, disable_progress): pm = messaging.PubMaster(cfg.pubs) sockets = {s: messaging.sub_sock(s, timeout=100) for s in cfg.subs} + vipc_server = None + if len(cfg.vision_pubs) != 0: + assert frs is not None, "frs must be provided when replaying process using vision streams" + assert all(meta_from_camera_state(st) is not None for st in cfg.vision_pubs),f"undefined vision stream spotted, probably misconfigured process: {cfg.vision_pubs}" + assert all(st in frs for st in cfg.vision_pubs), f"frs for this process must contain following vision streams: {cfg.vision_pubs}" + vipc_server = setup_vision_ipc(cfg, lr) + managed_processes[cfg.proc_name].prepare() managed_processes[cfg.proc_name].start() @@ -401,7 +462,7 @@ def _replay_single_process(cfg, lr, fingerprint, disable_progress): try: # Wait for process to startup with Timeout(10, error_msg=f"timed out waiting for process to start: {repr(cfg.proc_name)}"): - while not all(pm.all_readers_updated(s) for s in cfg.pubs): + while not all(pm.all_readers_updated(s) for s in cfg.pubs if s not in cfg.ignore_alive_pubs): time.sleep(0) # Do the replay @@ -423,11 +484,19 @@ def _replay_single_process(cfg, lr, fingerprint, disable_progress): # empty recv on drained pub indicates the end of messages, only do that if there're any trigger_empty_recv = False - if cfg.drained_pub: - trigger_empty_recv = next((True for m in msg_queue if m.which() == cfg.drained_pub), False) + if cfg.main_pub and cfg.main_pub_drained: + trigger_empty_recv = next((True for m in msg_queue if m.which() == cfg.main_pub), False) for m in msg_queue: pm.send(m.which(), m.as_builder()) + # send frames if needed + if vipc_server is not None and m.which() in cfg.vision_pubs: + camera_state = getattr(m, m.which()) + camera_meta = meta_from_camera_state(m.which()) + assert frs is not None + img = frs[m.which()].get(camera_state.frameId, pix_fmt="nv12")[0] + vipc_server.send(camera_meta.stream, img.flatten().tobytes(), + camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) msg_queue = [] rc.unlock_sockets() @@ -440,7 +509,8 @@ def _replay_single_process(cfg, lr, fingerprint, disable_progress): m.logMonoTime = msg.logMonoTime log_msgs.append(m.as_reader()) cnt += 1 - assert(managed_processes[cfg.proc_name].proc.is_alive()) + proc = managed_processes[cfg.proc_name].proc + assert(proc and proc.is_alive()) finally: managed_processes[cfg.proc_name].signal(signal.SIGKILL) managed_processes[cfg.proc_name].stop() @@ -448,7 +518,22 @@ def _replay_single_process(cfg, lr, fingerprint, disable_progress): return log_msgs -def setup_env(CP=None, cfg=None, controlsState=None, lr=None, fingerprint=None, log_dir=None): +def setup_vision_ipc(cfg, lr): + assert len(cfg.vision_pubs) != 0 + + device_type = next(msg.initData.deviceType for msg in lr if msg.which() == "initData") + + vipc_server = VisionIpcServer("camerad") + streams_metas = available_streams(lr) + for meta in streams_metas: + if meta.camera_state in cfg.vision_pubs: + vipc_server.create_buffers(meta.stream, 2, False, *meta.frame_sizes[device_type]) + vipc_server.start_listener() + + return vipc_server + + +def setup_env(cfg=None, CP=None, controlsState=None, lr=None, fingerprint=None, custom_params=None, log_dir=None): if platform.system() != "Darwin": os.environ["PARAMS_ROOT"] = "/dev/shm/params" if log_dir is not None: @@ -460,6 +545,12 @@ def setup_env(CP=None, cfg=None, controlsState=None, lr=None, fingerprint=None, params.put_bool("Passive", False) params.put_bool("DisengageOnAccelerator", True) params.put_bool("DisableLogging", False) + if custom_params is not None: + for k, v in custom_params.items(): + if type(v) == bool: + params.put_bool(k, v) + else: + params.put(k, v) os.environ["NO_RADAR_SLEEP"] = "1" os.environ["REPLAY"] = "1" diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 9e0fba3640..4ff076c644 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -72b5d91c92a72b2a708526dade78960950c5b595 +02c134e5a0d1f707c79f7a0ad6c1a5b6b038ee7c \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 6962e51cb5..1b07081cd8 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -65,6 +65,7 @@ excluded_interfaces = ["mock", "mazda", "tesla"] BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") +EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} def run_test_process(data): @@ -155,7 +156,7 @@ def format_diff(results, log_paths, ref_commit): if __name__ == "__main__": all_cars = {car for car, _ in segments} - all_procs = {cfg.proc_name for cfg in CONFIGS} + all_procs = {cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS} parser = argparse.ArgumentParser(description="Regression test to identify changes in a process's output") parser.add_argument("--whitelist-procs", type=str, nargs="*", default=all_procs, diff --git a/selfdrive/test/process_replay/vision_meta.py b/selfdrive/test/process_replay/vision_meta.py new file mode 100644 index 0000000000..77c6b0345d --- /dev/null +++ b/selfdrive/test/process_replay/vision_meta.py @@ -0,0 +1,43 @@ +from collections import namedtuple +from cereal.visionipc import VisionStreamType +from common.realtime import DT_MDL, DT_DMON +from common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size, eon_f_frame_size, eon_d_frame_size + +VideoStreamMeta = namedtuple("VideoStreamMeta", ["camera_state", "encode_index", "stream", "dt", "frame_sizes"]) +ROAD_CAMERA_FRAME_SIZES = {"tici": tici_f_frame_size, "tizi": tici_f_frame_size, "eon": eon_f_frame_size} +WIDE_ROAD_CAMERA_FRAME_SIZES = {"tici": tici_e_frame_size, "tizi": tici_e_frame_size} +DRIVER_FRAME_SIZES = {"tici": tici_d_frame_size, "tizi": tici_d_frame_size, "eon": eon_d_frame_size} +VIPC_STREAM_METADATA = [ + # metadata: (state_msg_type, encode_msg_type, stream_type, dt, frame_sizes) + ("roadCameraState", "roadEncodeIdx", VisionStreamType.VISION_STREAM_ROAD, DT_MDL, ROAD_CAMERA_FRAME_SIZES), + ("wideRoadCameraState", "wideRoadEncodeIdx", VisionStreamType.VISION_STREAM_WIDE_ROAD, DT_MDL, WIDE_ROAD_CAMERA_FRAME_SIZES), + ("driverCameraState", "driverEncodeIdx", VisionStreamType.VISION_STREAM_DRIVER, DT_DMON, DRIVER_FRAME_SIZES), +] + + +def meta_from_camera_state(state): + meta = next((VideoStreamMeta(*meta) for meta in VIPC_STREAM_METADATA if meta[0] == state), None) + return meta + + +def meta_from_encode_index(encode_index): + meta = next((VideoStreamMeta(*meta) for meta in VIPC_STREAM_METADATA if meta[1] == encode_index), None) + return meta + + +def meta_from_stream_type(stream_type): + meta = next((VideoStreamMeta(*meta) for meta in VIPC_STREAM_METADATA if meta[2] == stream_type), None) + return meta + + +def available_streams(lr=None): + if lr is None: + return [VideoStreamMeta(*meta) for meta in VIPC_STREAM_METADATA] + + result = [] + for meta in VIPC_STREAM_METADATA: + has_cam_state = next((True for m in lr if m.which() == meta[0]), False) + if has_cam_state: + result.append(VideoStreamMeta(*meta)) + + return result diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index b7d586a83b..7137bfad2b 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -33,6 +33,7 @@ echo tici-$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$ sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config sudo systemctl daemon-reload sudo systemctl restart ssh +sudo systemctl restart NetworkManager sudo systemctl disable ssh-param-watcher.path sudo systemctl disable ssh-param-watcher.service sudo mount -o ro,remount / diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index ef6ce65201..72d67b14db 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -30,6 +30,7 @@ PROCS = { "./encoderd": 17.0, "./camerad": 14.5, "./locationd": 11.0, + "./mapsd": 2.0, "selfdrive.controls.plannerd": 16.5, "./_ui": 21.0, "selfdrive.locationd.paramsd": 9.0, @@ -37,6 +38,7 @@ PROCS = { "selfdrive.controls.radard": 4.5, "./_modeld": 4.48, "./_dmonitoringmodeld": 5.0, + "./_navmodeld": 1.0, "selfdrive.thermald.thermald": 3.87, "selfdrive.locationd.calibrationd": 2.0, "selfdrive.locationd.torqued": 5.0, @@ -84,6 +86,8 @@ TIMINGS = { "driverCameraState": [2.5, 0.35], "modelV2": [2.5, 0.35], "driverStateV2": [2.5, 0.40], + "navModel": [2.5, 0.35], + "mapRenderState": [2.5, 0.35], "liveLocationKalman": [2.5, 0.35], "wideRoadCameraState": [1.5, 0.35], } diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 1d0609a5ba..40b259a8d4 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import datetime import os +import json import queue import threading import time @@ -47,7 +48,7 @@ THERMAL_BANDS = OrderedDict({ }) # Override to highest thermal band when offroad and above this temp -OFFROAD_DANGER_TEMP = 79.5 +OFFROAD_DANGER_TEMP = 75 prev_offroad_states: Dict[str, Tuple[bool, Optional[str]]] = {} @@ -166,7 +167,8 @@ def thermald_thread(end_event, hw_queue): off_ts = None started_ts = None started_seen = False - thermal_status = ThermalStatus.green + startup_blocked_ts = None + thermal_status = ThermalStatus.yellow last_hw_state = HardwareState( network_type=NetworkType.none, @@ -178,8 +180,8 @@ def thermald_thread(end_event, hw_queue): modem_temps=[], ) - all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML) - offroad_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML) + all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML, initialized=False) + offroad_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML, initialized=False) should_start_prev = False in_car = False engaged_prev = False @@ -241,7 +243,7 @@ def thermald_thread(end_event, hw_queue): msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness() - # this one is only used for offroad + # this subset is only used for offroad temp_sources = [ msg.deviceState.memoryTempC, max(msg.deviceState.cpuTempC), @@ -252,14 +254,15 @@ def thermald_thread(end_event, hw_queue): # this drives the thermal status while onroad temp_sources.append(max(msg.deviceState.pmicTempC)) all_comp_temp = all_temp_filter.update(max(temp_sources)) + msg.deviceState.maxTempC = all_comp_temp if fan_controller is not None: msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (sec_since_boot() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: - # If device is offroad we want to cool down before going onroad - # since going onroad increases load and can make temps go over 107 + # if device is offroad and already hot without the extra onroad load, + # we want to cool down first before increasing load thermal_status = ThermalStatus.danger else: current_band = THERMAL_BANDS[thermal_status] @@ -279,7 +282,6 @@ def thermald_thread(end_event, hw_queue): startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version - startup_conditions["offroad_min_time"] = (not started_seen) or ((off_ts is not None) and (sec_since_boot() - off_ts) > 5.) # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 @@ -293,7 +295,9 @@ def thermald_thread(end_event, hw_queue): # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger - set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", (not onroad_conditions["device_temp_good"])) + extra_text = f"{offroad_comp_temp:.1f}C" + show_alert = (not onroad_conditions["device_temp_good"] or not startup_conditions["device_temp_engageable"]) and onroad_conditions["ignition"] + set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", show_alert, extra_text=extra_text) # TODO: this should move to TICI.initialize_hardware, but we currently can't import params there if TICI: @@ -338,10 +342,16 @@ def thermald_thread(end_event, hw_queue): if started_ts is None: started_ts = sec_since_boot() started_seen = True + if startup_blocked_ts is not None: + cloudlog.event("Startup after block", block_duration=(sec_since_boot() - startup_blocked_ts), + startup_conditions=startup_conditions, onroad_conditions=onroad_conditions, + startup_conditions_prev=startup_conditions_prev, error=True) + startup_blocked_ts = None else: if onroad_conditions["ignition"] and (startup_conditions != startup_conditions_prev): cloudlog.event("Startup blocked", startup_conditions=startup_conditions, onroad_conditions=onroad_conditions, error=True) startup_conditions_prev = startup_conditions.copy() + startup_blocked_ts = sec_since_boot() started_ts = None if off_ts is None: @@ -375,8 +385,6 @@ def thermald_thread(end_event, hw_queue): msg.deviceState.thermalStatus = thermal_status pm.send("deviceState", msg) - should_start_prev = should_start - # Log to statsd statlog.gauge("free_space_percent", msg.deviceState.freeSpacePercent) statlog.gauge("gpu_usage_percent", msg.deviceState.gpuUsagePercent) @@ -399,15 +407,26 @@ def thermald_thread(end_event, hw_queue): statlog.gauge("screen_brightness_percent", msg.deviceState.screenBrightnessPercent) # report to server once every 10 minutes - if (count % int(600. / DT_TRML)) == 0: - cloudlog.event("STATUS_PACKET", - count=count, - pandaStates=[strip_deprecated_keys(p.to_dict()) for p in pandaStates], - peripheralState=strip_deprecated_keys(peripheralState.to_dict()), - location=(strip_deprecated_keys(sm["gpsLocationExternal"].to_dict()) if sm.alive["gpsLocationExternal"] else None), - deviceState=strip_deprecated_keys(msg.to_dict())) + rising_edge_started = should_start and not should_start_prev + if rising_edge_started or (count % int(600. / DT_TRML)) == 0: + dat = { + 'count': count, + 'pandaStates': [strip_deprecated_keys(p.to_dict()) for p in pandaStates], + 'peripheralState': strip_deprecated_keys(peripheralState.to_dict()), + 'location': (strip_deprecated_keys(sm["gpsLocationExternal"].to_dict()) if sm.alive["gpsLocationExternal"] else None), + 'deviceState': strip_deprecated_keys(msg.to_dict()) + } + cloudlog.event("STATUS_PACKET", **dat) + + # save last one before going onroad + if rising_edge_started: + try: + params.put("LastOffroadStatusPacket", json.dumps(dat)) + except Exception: + cloudlog.exception("failed to save offroad status") count += 1 + should_start_prev = should_start def main(): diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 60eb4b43c7..cb95652191 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -12,3 +12,4 @@ qt/setup/setup qt/setup/reset qt/setup/wifi qt/setup/updater +translations/alerts_generated.h diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index f46e3d5873..97d4060767 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -29,7 +29,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/ qt_env['CPPDEFINES'] = [] if maps: base_libs += ['qmapboxgl'] - widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc"] + widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc"] qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc index 55b9a47474..e2b4358b65 100644 --- a/selfdrive/ui/mui.cc +++ b/selfdrive/ui/mui.cc @@ -110,14 +110,8 @@ int main(int argc, char *argv[]) { if (onroad) { auto cs = sm["controlsState"].getControlsState(); UIStatus status = cs.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; - if (cs.getAlertStatus() == cereal::ControlsState::AlertStatus::USER_PROMPT) { - status = STATUS_WARNING; - } else if (cs.getAlertStatus() == cereal::ControlsState::AlertStatus::CRITICAL) { - status = STATUS_ALERT; - } - auto lp = sm["lateralPlan"].getLateralPlan(); - if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::PRE_LANE_CHANGE || status == STATUS_ALERT) { + if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::PRE_LANE_CHANGE) { status_bar->blinkingColor(bg_colors[status]); } else if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_STARTING || lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_FINISHING) { diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 587e2f445e..85c6c34267 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -7,9 +7,14 @@ #include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/drive_stats.h" #include "selfdrive/ui/qt/widgets/prime.h" +#ifdef ENABLE_MAPS +#include "selfdrive/ui/qt/maps/map_settings.h" +#else +#include "selfdrive/ui/qt/widgets/drive_stats.h" +#endif + // HomeWindow: the container for the offroad and onroad UIs HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { @@ -137,10 +142,15 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { home_layout->setContentsMargins(0, 0, 0, 0); home_layout->setSpacing(30); - // left: DriveStats/PrimeAdWidget + // left: MapSettings/PrimeAdWidget QStackedWidget *left_widget = new QStackedWidget(this); +#ifdef ENABLE_MAPS + left_widget->addWidget(new MapSettings); +#else left_widget->addWidget(new DriveStats); +#endif left_widget->addWidget(new PrimeAdWidget); + left_widget->setStyleSheet("border-radius: 10px;"); left_widget->setCurrentIndex(uiState()->primeType() ? 0 : 1); connect(uiState(), &UIState::primeTypeChanged, [=](int prime_type) { diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index abbf434d69..2ba7ef7af3 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -44,6 +44,29 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), map_eta->move(25, 1080 - h - bdr_s*2); map_eta->setVisible(false); + // Settings button + QSize icon_size(120, 120); + directions_icon = loadPixmap("../assets/navigation/icon_directions_outlined.svg", icon_size); + settings_icon = loadPixmap("../assets/navigation/icon_settings.svg", icon_size); + + settings_btn = new QPushButton(directions_icon, "", this); + settings_btn->setIconSize(icon_size); + settings_btn->setStyleSheet(R"( + QPushButton { + background-color: #96000000; + border-radius: 50px; + padding: 24px; + } + QPushButton:pressed { + background-color: #D9000000; + } + )"); + settings_btn->show(); // force update + settings_btn->move(bdr_s, 1080 - bdr_s*3 - settings_btn->height()); + QObject::connect(settings_btn, &QPushButton::clicked, [=]() { + emit openSettings(); + }); + auto last_gps_position = coordinate_from_param("LastGPSPosition"); if (last_gps_position.has_value()) { last_position = *last_gps_position; @@ -128,7 +151,7 @@ void MapWindow::updateState(const UIState &s) { // Only open the map on setting destination the first time if (allow_open) { - setVisible(true); // Show map on destination set/change + emit requestVisible(true); // Show map on destination set/change allow_open = false; } } @@ -186,6 +209,19 @@ void MapWindow::updateState(const UIState &s) { } else { clearRoute(); } + + // TODO: only move if position should change + // don't move while map isn't visible + if (isVisible()) { + auto pos = 1080 - bdr_s*2 - settings_btn->height() - bdr_s; + if (map_eta->isVisible()) { + settings_btn->move(bdr_s, pos - map_eta->height()); + settings_btn->setIcon(settings_icon); + } else { + settings_btn->move(bdr_s, pos); + settings_btn->setIcon(directions_icon); + } + } } if (sm.rcv_frame("navRoute") != route_rcv_frame) { @@ -220,7 +256,7 @@ void MapWindow::initializeGL() { m_map->setMargins({0, 350, 0, 50}); m_map->setPitch(MIN_PITCH); - m_map->setStyleUrl("mapbox://styles/commaai/ckr64tlwp0azb17nqvr9fj13s"); + m_map->setStyleUrl("mapbox://styles/commaai/clj7g5vrp007b01qzb5ro0i4j"); QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) { @@ -321,7 +357,7 @@ void MapWindow::offroadTransition(bool offroad) { clearRoute(); } else { auto dest = coordinate_from_param("NavDestination"); - setVisible(dest.has_value()); + emit requestVisible(dest.has_value()); } last_bearing = {}; } diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 0d8b93a5f4..658d26ee47 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,8 @@ private: MapInstructions* map_instructions; MapETA* map_eta; + QPushButton *settings_btn; + QPixmap directions_icon, settings_icon; void clearRoute(); void updateDestinationMarker(); @@ -125,5 +128,7 @@ signals: void distanceChanged(float distance); void instructionsChanged(cereal::NavInstruction::Reader instruction); void ETAChanged(float seconds, float seconds_typical, float distance); -}; + void requestVisible(bool visible); + void openSettings(); +}; diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc new file mode 100644 index 0000000000..564012482a --- /dev/null +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -0,0 +1,34 @@ +#include "selfdrive/ui/qt/maps/map_panel.h" + +#include +#include + +#include "selfdrive/ui/qt/maps/map.h" +#include "selfdrive/ui/qt/maps/map_settings.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/ui.h" + +MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : QFrame(parent) { + content_stack = new QStackedLayout(this); + content_stack->setContentsMargins(0, 0, 0, 0); + + auto map = new MapWindow(mapboxSettings); + QObject::connect(uiState(), &UIState::offroadTransition, map, &MapWindow::offroadTransition); + QObject::connect(map, &MapWindow::requestVisible, [=](bool visible) { + setVisible(visible); + }); + QObject::connect(map, &MapWindow::openSettings, [=]() { + content_stack->setCurrentIndex(1); + }); + content_stack->addWidget(map); + + auto settings = new MapSettings(true, parent); + QObject::connect(settings, &MapSettings::closeSettings, [=]() { + content_stack->setCurrentIndex(0); + }); + content_stack->addWidget(settings); +} + +bool MapPanel::isShowingMap() const { + return content_stack->currentIndex() == 0; +} diff --git a/selfdrive/ui/qt/maps/map_panel.h b/selfdrive/ui/qt/maps/map_panel.h new file mode 100644 index 0000000000..abea77483a --- /dev/null +++ b/selfdrive/ui/qt/maps/map_panel.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +class MapPanel : public QFrame { + Q_OBJECT + +public: + explicit MapPanel(const QMapboxGLSettings &settings, QWidget *parent = nullptr); + + bool isShowingMap() const; + +private: + QStackedLayout *content_stack; +}; diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index f626925ad4..e95244c574 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -1,132 +1,95 @@ #include "map_settings.h" #include +#include #include "common/util.h" -#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/request_repeater.h" -#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" static QString shorten(const QString &str, int max_len) { return str.size() > max_len ? str.left(max_len).trimmed() + "…" : str; } -MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { - QStackedLayout *stack = new QStackedLayout(this); +MapSettings::MapSettings(bool closeable, QWidget *parent) + : QFrame(parent), current_destination(nullptr) { + QSize icon_size(100, 100); + close_icon = loadPixmap("../assets/icons/close.svg", icon_size); - QWidget *main_widget = new QWidget; - QVBoxLayout *main_layout = new QVBoxLayout(main_widget); - main_layout->setSpacing(20); + setContentsMargins(0, 0, 0, 0); - // Home & Work layout - QHBoxLayout *home_work_layout = new QHBoxLayout; - { - // Home - home_button = new QPushButton; - home_button->setIconSize(QSize(MAP_PANEL_ICON_SIZE, MAP_PANEL_ICON_SIZE)); - home_address = new QLabel; - home_address->setWordWrap(true); - - QHBoxLayout *home_layout = new QHBoxLayout; - home_layout->addWidget(home_button); - home_layout->addSpacing(30); - home_layout->addWidget(home_address); - home_layout->addStretch(); - - // Work - work_button = new QPushButton; - work_button->setIconSize(QSize(MAP_PANEL_ICON_SIZE, MAP_PANEL_ICON_SIZE)); - work_address = new QLabel; - work_address->setWordWrap(true); - - QHBoxLayout *work_layout = new QHBoxLayout; - work_layout->addWidget(work_button); - work_layout->addSpacing(30); - work_layout->addWidget(work_address); - work_layout->addStretch(); - - home_work_layout->addLayout(home_layout, 1); - home_work_layout->addSpacing(50); - home_work_layout->addLayout(work_layout, 1); - } + auto *frame = new QVBoxLayout(this); + frame->setContentsMargins(40, 40, 40, 0); + frame->setSpacing(0); - main_layout->addLayout(home_work_layout); - main_layout->addWidget(horizontal_line()); - - // Current route + auto *heading_frame = new QHBoxLayout; + heading_frame->setContentsMargins(0, 0, 0, 0); + heading_frame->setSpacing(32); { - current_widget = new QWidget(this); - QVBoxLayout *current_layout = new QVBoxLayout(current_widget); - - QLabel *title = new QLabel(tr("Current Destination")); - title->setStyleSheet("font-size: 55px"); - current_layout->addWidget(title); - - current_route = new ButtonControl("", tr("CLEAR")); - current_route->setStyleSheet("padding-left: 40px;"); - current_layout->addWidget(current_route); - QObject::connect(current_route, &ButtonControl::clicked, [=]() { - params.remove("NavDestination"); - updateCurrentRoute(); - }); - - current_layout->addSpacing(10); - current_layout->addWidget(horizontal_line()); - current_layout->addSpacing(20); - } - main_layout->addWidget(current_widget); - - // Recents - QLabel *recents_title = new QLabel(tr("Recent Destinations")); - recents_title->setStyleSheet("font-size: 55px"); - main_layout->addWidget(recents_title); + if (closeable) { + auto *close_btn = new QPushButton("←"); + close_btn->setStyleSheet(R"( + QPushButton { + color: #FFFFFF; + font-size: 100px; + padding-bottom: 8px; + border 1px grey solid; + border-radius: 70px; + background-color: #292929; + font-weight: 500; + } + QPushButton:pressed { + background-color: #3B3B3B; + } + )"); + close_btn->setFixedSize(140, 140); + QObject::connect(close_btn, &QPushButton::clicked, [=]() { emit closeSettings(); }); + // TODO: read map_on_left from ui state + heading_frame->addWidget(close_btn); + } - recent_layout = new QVBoxLayout; - QWidget *recent_widget = new LayoutWidget(recent_layout, this); - ScrollView *recent_scroller = new ScrollView(recent_widget, this); - main_layout->addWidget(recent_scroller); + auto *heading = new QVBoxLayout; + heading->setContentsMargins(0, 0, 0, 0); + heading->setSpacing(16); + { + auto *title = new QLabel(tr("NAVIGATION"), this); + title->setStyleSheet("color: #FFFFFF; font-size: 54px; font-weight: 600;"); + heading->addWidget(title); - // No prime upsell - QWidget * no_prime_widget = new QWidget; - { - QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget); - QLabel *signup_header = new QLabel(tr("Try the Navigation Beta")); - signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)"); - signup_header->setAlignment(Qt::AlignCenter); - - no_prime_layout->addWidget(signup_header); - no_prime_layout->addSpacing(50); - - QLabel *screenshot = new QLabel; - QPixmap pm = QPixmap("../assets/navigation/screenshot.png"); - screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation)); - no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter); - - QLabel *signup = new QLabel(tr("Get turn-by-turn directions displayed and more with a comma\nprime subscription. Sign up now: https://connect.comma.ai")); - signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)"); - signup->setAlignment(Qt::AlignCenter); - - no_prime_layout->addSpacing(20); - no_prime_layout->addWidget(signup); - no_prime_layout->addStretch(); + auto *subtitle = new QLabel(tr("Manage at connect.comma.ai"), this); + subtitle->setStyleSheet("color: #A0A0A0; font-size: 40px; font-weight: 300;"); + heading->addWidget(subtitle); + } + heading_frame->addLayout(heading, 1); } - - stack->addWidget(main_widget); - stack->addWidget(no_prime_widget); - connect(uiState(), &UIState::primeTypeChanged, [=](int prime_type) { - stack->setCurrentIndex(prime_type ? 0 : 1); + frame->addLayout(heading_frame); + frame->addSpacing(32); + + current_widget = new DestinationWidget(this); + QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() { + if (!current_destination) return; + params.remove("NavDestination"); + updateCurrentRoute(); }); + frame->addWidget(current_widget); + frame->addSpacing(32); + frame->addWidget(horizontal_line()); + QWidget *destinations_container = new QWidget(this); + destinations_layout = new QVBoxLayout(destinations_container); + destinations_layout->setContentsMargins(0, 32, 0, 32); + destinations_layout->setSpacing(20); + ScrollView *destinations_scroller = new ScrollView(destinations_container, this); + frame->addWidget(destinations_scroller); - clear(); + setStyleSheet("MapSettings { background-color: #333333; }"); if (auto dongle_id = getDongleId()) { // Fetch favorite and recent locations { QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); - QObject::connect(repeater, &RequestRepeater::requestDone, this, &MapPanel::parseResponse); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &MapSettings::parseResponse); } // Destination set while offline @@ -147,153 +110,235 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { // Send DELETE to clear destination server side deleter->sendRequest(url, HttpRequest::Method::DELETE); } + + // Update UI (athena can set destination at any time) + updateCurrentRoute(); }); } } } -void MapPanel::showEvent(QShowEvent *event) { +void MapSettings::showEvent(QShowEvent *event) { updateCurrentRoute(); - refresh(); -} - -void MapPanel::clear() { - home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png")); - home_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); - home_address->setText(tr("No home\nlocation set")); - home_button->disconnect(); - - work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png")); - work_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); - work_address->setText(tr("No work\nlocation set")); - work_button->disconnect(); - - clearLayout(recent_layout); } -void MapPanel::updateCurrentRoute() { +void MapSettings::updateCurrentRoute() { auto dest = QString::fromStdString(params.get("NavDestination")); - QJsonDocument doc = QJsonDocument::fromJson(dest.trimmed().toUtf8()); - if (dest.size() && !doc.isNull()) { - auto name = doc["place_name"].toString(); - auto details = doc["place_details"].toString(); - current_route->setTitle(shorten(name + " " + details, 42)); + if (dest.size()) { + QJsonDocument doc = QJsonDocument::fromJson(dest.trimmed().toUtf8()); + if (doc.isNull()) { + qWarning() << "JSON Parse failed on NavDestination" << dest; + return; + } + auto destination = new NavDestination(doc.object()); + if (current_destination && *destination == *current_destination) return; + current_destination = destination; + current_widget->set(current_destination, true); + } else { + current_destination = nullptr; + current_widget->unset("", true); } - current_widget->setVisible(dest.size() && !doc.isNull()); + if (isVisible()) refresh(); } -void MapPanel::parseResponse(const QString &response, bool success) { - if (!success) return; - +void MapSettings::parseResponse(const QString &response, bool success) { + if (!success || response == cur_destinations) return; cur_destinations = response; - if (isVisible()) { - refresh(); - } + refresh(); } -void MapPanel::refresh() { - if (cur_destinations == prev_destinations) return; - - QJsonDocument doc = QJsonDocument::fromJson(cur_destinations.trimmed().toUtf8()); - if (doc.isNull()) { - qDebug() << "JSON Parse failed on navigation locations"; - return; - } +void MapSettings::refresh() { + bool has_home = false, has_work = false; + auto destinations = std::vector(); - prev_destinations = cur_destinations; - clear(); - - // add favorites before recents - bool has_recents = false; - for (auto &save_type: {NAV_TYPE_FAVORITE, NAV_TYPE_RECENT}) { - for (auto location : doc.array()) { - auto obj = location.toObject(); - - auto type = obj["save_type"].toString(); - auto label = obj["label"].toString(); - auto name = obj["place_name"].toString(); - auto details = obj["place_details"].toString(); - - if (type != save_type) continue; - - if (type == NAV_TYPE_FAVORITE && label == NAV_FAVORITE_LABEL_HOME) { - home_address->setText(name); - home_address->setStyleSheet(R"(font-size: 50px; color: white;)"); - home_button->setIcon(QPixmap("../assets/navigation/home.png")); - QObject::connect(home_button, &QPushButton::clicked, [=]() { - navigateTo(obj); - emit closeSettings(); - }); - } else if (type == NAV_TYPE_FAVORITE && label == NAV_FAVORITE_LABEL_WORK) { - work_address->setText(name); - work_address->setStyleSheet(R"(font-size: 50px; color: white;)"); - work_button->setIcon(QPixmap("../assets/navigation/work.png")); - QObject::connect(work_button, &QPushButton::clicked, [=]() { - navigateTo(obj); - emit closeSettings(); - }); - } else { - ClickableWidget *widget = new ClickableWidget; - QHBoxLayout *layout = new QHBoxLayout(widget); - layout->setContentsMargins(15, 14, 40, 14); - - QLabel *star = new QLabel("★"); - auto sp = star->sizePolicy(); - sp.setRetainSizeWhenHidden(true); - star->setSizePolicy(sp); - - star->setVisible(type == NAV_TYPE_FAVORITE); - star->setStyleSheet(R"(font-size: 60px;)"); - layout->addWidget(star); - layout->addSpacing(10); - - - QLabel *recent_label = new QLabel(shorten(name + " " + details, 45)); - recent_label->setStyleSheet(R"(font-size: 50px;)"); - - layout->addWidget(recent_label); - layout->addStretch(); - - QLabel *arrow = new QLabel("→"); - arrow->setStyleSheet(R"(font-size: 60px;)"); - layout->addWidget(arrow); - - widget->setStyleSheet(R"( - .ClickableWidget { - border-radius: 10px; - border-width: 1px; - border-style: solid; - border-color: gray; - } - QWidget { - background-color: #393939; - color: #9c9c9c; - } - )"); + auto destinations_str = cur_destinations.trimmed(); + if (!destinations_str.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(destinations_str.toUtf8()); + if (doc.isNull()) { + qWarning() << "JSON Parse failed on navigation locations" << cur_destinations; + return; + } - QObject::connect(widget, &ClickableWidget::clicked, [=]() { - navigateTo(obj); - emit closeSettings(); - }); + for (auto el : doc.array()) { + auto destination = new NavDestination(el.toObject()); - recent_layout->addWidget(widget); - recent_layout->addSpacing(10); - has_recents = true; + // add home and work later if they are missing + if (destination->isFavorite()) { + if (destination->label() == NAV_FAVORITE_LABEL_HOME) has_home = true; + else if (destination->label() == NAV_FAVORITE_LABEL_WORK) has_work = true; } + + // skip current destination + if (current_destination && *destination == *current_destination) continue; + destinations.push_back(destination); } } - if (!has_recents) { - QLabel *no_recents = new QLabel(tr("no recent destinations")); - no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)"); - recent_layout->addWidget(no_recents); + // TODO: should we build a new layout and swap it in? + clearLayout(destinations_layout); + + // Sort: HOME, WORK, and then descending-alphabetical FAVORITES, RECENTS + std::sort(destinations.begin(), destinations.end(), [](const NavDestination *a, const NavDestination *b) { + if (a->isFavorite() && b->isFavorite()) { + if (a->label() == NAV_FAVORITE_LABEL_HOME) return true; + else if (b->label() == NAV_FAVORITE_LABEL_HOME) return false; + else if (a->label() == NAV_FAVORITE_LABEL_WORK) return true; + else if (b->label() == NAV_FAVORITE_LABEL_WORK) return false; + else if (a->label() != b->label()) return a->label() < b->label(); + } + else if (a->isFavorite()) return true; + else if (b->isFavorite()) return false; + return a->name() < b->name(); + }); + + for (auto destination : destinations) { + auto widget = new DestinationWidget(this); + widget->set(destination, false); + + QObject::connect(widget, &QPushButton::clicked, [=]() { + navigateTo(destination->toJson()); + emit closeSettings(); + }); + + destinations_layout->addWidget(widget); } - recent_layout->addStretch(); - repaint(); + // add home and work if missing + if (!has_home) { + auto widget = new DestinationWidget(this); + widget->unset(NAV_FAVORITE_LABEL_HOME); + destinations_layout->insertWidget(0, widget); + } + if (!has_work) { + auto widget = new DestinationWidget(this); + widget->unset(NAV_FAVORITE_LABEL_WORK); + // TODO: refactor to remove this hack + int index = !has_home || (current_destination && current_destination->isFavorite() && current_destination->label() == NAV_FAVORITE_LABEL_HOME) ? 0 : 1; + destinations_layout->insertWidget(index, widget); + } + + destinations_layout->addStretch(); } -void MapPanel::navigateTo(const QJsonObject &place) { +void MapSettings::navigateTo(const QJsonObject &place) { QJsonDocument doc(place); params.put("NavDestination", doc.toJson().toStdString()); + updateCurrentRoute(); +} + +DestinationWidget::DestinationWidget(QWidget *parent) : QPushButton(parent) { + setContentsMargins(0, 0, 0, 0); + + auto *frame = new QHBoxLayout(this); + frame->setContentsMargins(32, 24, 32, 24); + frame->setSpacing(32); + + icon = new QLabel(this); + icon->setAlignment(Qt::AlignCenter); + icon->setFixedSize(96, 96); + icon->setObjectName("icon"); + frame->addWidget(icon); + + auto *inner_frame = new QVBoxLayout; + inner_frame->setContentsMargins(0, 0, 0, 0); + inner_frame->setSpacing(0); + { + title = new ElidedLabel(this); + title->setAttribute(Qt::WA_TransparentForMouseEvents); + inner_frame->addWidget(title); + + subtitle = new ElidedLabel(this); + subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); + subtitle->setObjectName("subtitle"); + inner_frame->addWidget(subtitle); + } + frame->addLayout(inner_frame, 1); + + action = new QPushButton(this); + action->setFixedSize(96, 96); + action->setObjectName("action"); + action->setStyleSheet("font-size: 65px; font-weight: 600;"); + QObject::connect(action, &QPushButton::clicked, [=]() { emit clicked(); }); + QObject::connect(action, &QPushButton::clicked, [=]() { emit actionClicked(); }); + frame->addWidget(action); + + setFixedHeight(164); + setStyleSheet(R"( + DestinationWidget { background-color: #202123; border-radius: 10px; } + QLabel { color: #FFFFFF; font-size: 48px; font-weight: 400; } + #icon { background-color: #3B4356; border-radius: 48px; } + #subtitle { color: #9BA0A5; } + #action { border: none; border-radius: 48px; color: #FFFFFF; padding-bottom: 4px; } + + /* current destination */ + [current="true"] { background-color: #E8E8E8; } + [current="true"] QLabel { color: #000000; } + [current="true"] #icon { background-color: #42906B; } + [current="true"] #subtitle { color: #333333; } + [current="true"] #action { color: #202123; } + + /* no saved destination */ + [set="false"] QLabel { color: #9BA0A5; } + [current="true"][set="false"] QLabel { color: #A0000000; } + + /* pressed */ + [current="false"]:pressed { background-color: #18191B; } + [current="true"] #action:pressed { background-color: #D6D6D6; } + )"); +} + +void DestinationWidget::set(NavDestination *destination, bool current) { + setProperty("current", current); + setProperty("set", true); + + auto icon_pixmap = current ? icons().directions : icons().recent; + auto title_text = destination->name(); + auto subtitle_text = destination->details(); + + if (destination->isFavorite()) { + if (destination->label() == NAV_FAVORITE_LABEL_HOME) { + icon_pixmap = icons().home; + title_text = tr("Home"); + subtitle_text = destination->name() + ", " + destination->details(); + } else if (destination->label() == NAV_FAVORITE_LABEL_WORK) { + icon_pixmap = icons().work; + title_text = tr("Work"); + subtitle_text = destination->name() + ", " + destination->details(); + } else { + icon_pixmap = icons().favorite; + } + } + + icon->setPixmap(icon_pixmap); + + // TODO: onroad and offroad have different dimensions + title->setText(shorten(title_text, 26)); + subtitle->setText(shorten(subtitle_text, 26)); + subtitle->setVisible(true); + + // TODO: use pixmap + action->setAttribute(Qt::WA_TransparentForMouseEvents, !current); + action->setText(current ? "×" : "→"); + action->setVisible(true); + + setStyleSheet(styleSheet()); +} + +void DestinationWidget::unset(const QString &label, bool current) { + setProperty("current", current); + setProperty("set", false); + + if (label.isEmpty()) { + icon->setPixmap(icons().directions); + title->setText(tr("No destination set")); + } else { + QString title_text = label == NAV_FAVORITE_LABEL_HOME ? tr("home") : tr("work"); + icon->setPixmap(label == NAV_FAVORITE_LABEL_HOME ? icons().home : icons().work); + title->setText(tr("No %1 location set").arg(title_text)); + } + + subtitle->setVisible(false); + action->setVisible(false); + + setStyleSheet(styleSheet()); } diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index 8dd044c374..38c021036c 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -1,46 +1,120 @@ #pragma once + +#include #include #include #include #include #include #include -#include -#include #include "common/params.h" +#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" -const int MAP_PANEL_ICON_SIZE = 200; - const QString NAV_TYPE_FAVORITE = "favorite"; const QString NAV_TYPE_RECENT = "recent"; const QString NAV_FAVORITE_LABEL_HOME = "home"; const QString NAV_FAVORITE_LABEL_WORK = "work"; -class MapPanel : public QWidget { +class NavDestination; +class DestinationWidget; + +class MapSettings : public QFrame { Q_OBJECT public: - explicit MapPanel(QWidget* parent = nullptr); + explicit MapSettings(bool closeable = false, QWidget *parent = nullptr); void navigateTo(const QJsonObject &place); void parseResponse(const QString &response, bool success); void updateCurrentRoute(); - void clear(); private: void showEvent(QShowEvent *event) override; void refresh(); Params params; - QString prev_destinations, cur_destinations; - QPushButton *home_button, *work_button; - QLabel *home_address, *work_address; - QVBoxLayout *recent_layout; - QWidget *current_widget; - ButtonControl *current_route; + QString cur_destinations; + QVBoxLayout *destinations_layout; + NavDestination *current_destination; + DestinationWidget *current_widget; + + QPixmap close_icon; signals: void closeSettings(); }; + +class NavDestination { +public: + explicit NavDestination(const QJsonObject &place) + : type_(place["save_type"].toString()), label_(place["label"].toString()), + name_(place["place_name"].toString()), details_(place["place_details"].toString()), + latitude_(place["latitude"].toDouble()), longitude_(place["longitude"].toDouble()) { + // if details starts with `name, ` remove it + if (details_.startsWith(name_ + ", ")) { + details_ = details_.mid(name_.length() + 2); + } + } + + QString type() const { return type_; } + QString label() const { return label_; } + QString name() const { return name_; } + QString details() const { return details_; } + + bool isFavorite() const { return type_ == NAV_TYPE_FAVORITE; } + bool isRecent() const { return type_ == NAV_TYPE_RECENT; } + + bool operator==(const NavDestination &rhs) { + return type_ == rhs.type_ && label_ == rhs.label_ && name_ == rhs.name_ && + details_ == rhs.details_ && latitude_ == rhs.latitude_ && longitude_ == rhs.longitude_; + } + + QJsonObject toJson() const { + QJsonObject obj; + obj["save_type"] = type_; + obj["label"] = label_; + obj["place_name"] = name_; + obj["place_details"] = details_; + obj["latitude"] = latitude_; + obj["longitude"] = longitude_; + return obj; + } + +private: + QString type_, label_, name_, details_; + double latitude_, longitude_; +}; + +class DestinationWidget : public QPushButton { + Q_OBJECT +public: + explicit DestinationWidget(QWidget *parent = nullptr); + + void set(NavDestination *, bool current = false); + void unset(const QString &label, bool current = false); + +signals: + void actionClicked(); + +private: + struct NavIcons { + QPixmap home, work, favorite, recent, directions; + }; + + static NavIcons icons() { + static NavIcons nav_icons { + loadPixmap("../assets/navigation/icon_home.svg", {48, 48}), + loadPixmap("../assets/navigation/icon_work.svg", {48, 48}), + loadPixmap("../assets/navigation/icon_favorite.svg", {48, 48}), + loadPixmap("../assets/navigation/icon_recent.svg", {48, 48}), + loadPixmap("../assets/navigation/icon_directions.svg", {48, 48}), + }; + return nav_icons; + } + +private: + QLabel *icon, *title, *subtitle; + QPushButton *action; +}; diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index d69d67edeb..cf14e5cb91 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -248,6 +248,9 @@ WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) padding-bottom: 16px; padding-top: 16px; } + #forgetBtn:pressed { + background-color: #828282; + } #connecting { font-size: 32px; font-weight: 600; diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index 72c341fdf9..5bf1b6fc43 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "common/util.h" @@ -21,36 +22,54 @@ void TrainingGuide::mouseReleaseEvent(QMouseEvent *e) { } click_timer.restart(); - if (boundingRect[currentIndex].contains(e->x(), e->y())) { + auto contains = [this](QRect r, const QPoint &pt) { + if (image.size() != image_raw_size) { + QTransform transform; + transform.translate((width()- image.width()) / 2.0, (height()- image.height()) / 2.0); + transform.scale(image.width() / (float)image_raw_size.width(), image.height() / (float)image_raw_size.height()); + r= transform.mapRect(r); + } + return r.contains(pt); + }; + + if (contains(boundingRect[currentIndex], e->pos())) { if (currentIndex == 9) { const QRect yes = QRect(707, 804, 531, 164); - Params().putBool("RecordFront", yes.contains(e->x(), e->y())); + Params().putBool("RecordFront", contains(yes, e->pos())); } currentIndex += 1; - } else if (currentIndex == (boundingRect.size() - 2) && boundingRect.last().contains(e->x(), e->y())) { + } else if (currentIndex == (boundingRect.size() - 2) && contains(boundingRect.last(), e->pos())) { currentIndex = 0; } if (currentIndex >= (boundingRect.size() - 1)) { emit completedTraining(); } else { - image.load(img_path + "step" + QString::number(currentIndex) + ".png"); update(); } } void TrainingGuide::showEvent(QShowEvent *event) { currentIndex = 0; - image.load(img_path + "step0.png"); click_timer.start(); } +QImage TrainingGuide::loadImage(int id) { + QImage img(img_path + QString("step%1.png").arg(id)); + image_raw_size = img.size(); + if (image_raw_size != rect().size()) { + img = img.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + return img; +} + void TrainingGuide::paintEvent(QPaintEvent *event) { QPainter painter(this); QRect bg(0, 0, painter.device()->width(), painter.device()->height()); painter.fillRect(bg, QColor("#000000")); + image = loadImage(currentIndex); QRect rect(image.rect()); rect.moveCenter(bg.center()); painter.drawImage(rect.topLeft(), image); diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index d347d1e613..2fdae35de0 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -20,8 +20,10 @@ private: void showEvent(QShowEvent *event) override; void paintEvent(QPaintEvent *event) override; void mouseReleaseEvent(QMouseEvent* e) override; + QImage loadImage(int id); QImage image; + QSize image_raw_size; int currentIndex = 0; // Bounding boxes for each training guide step diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index e37e5a9843..83ab772115 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -8,10 +8,6 @@ #include "selfdrive/ui/qt/offroad/networking.h" -#ifdef ENABLE_MAPS -#include "selfdrive/ui/qt/maps/map_settings.h" -#endif - #include "common/params.h" #include "common/watchdog.h" #include "common/util.h" @@ -174,7 +170,7 @@ void TogglesPanel::updateToggles() { tr("openpilot longitudinal control may come in a future update."); if (CP.getExperimentalLongitudinalAvailable()) { if (is_release) { - long_desc = unavailable + " " + tr("An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); + long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); } else { long_desc = tr("Enable experimental longitudinal control to allow Experimental mode."); } @@ -385,12 +381,6 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { {tr("Software"), new SoftwarePanel(this)}, }; -#ifdef ENABLE_MAPS - auto map_panel = new MapPanel(this); - panels.push_back({tr("Navigation"), map_panel}); - QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings); -#endif - nav_btns = new QButtonGroup(this); for (auto &[name, panel] : panels) { QPushButton *btn = new QPushButton(name); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 0dcf38ccf0..8809e85ebb 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -3,12 +3,13 @@ #include #include +#include #include "common/timing.h" #include "selfdrive/ui/qt/util.h" #ifdef ENABLE_MAPS -#include "selfdrive/ui/qt/maps/map.h" #include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/ui/qt/maps/map_panel.h" #endif OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { @@ -53,14 +54,7 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { void OnroadWindow::updateState(const UIState &s) { QColor bgColor = bg_colors[s.status]; Alert alert = Alert::get(*(s.sm), s.scene.started_frame); - if (s.sm->updated("controlsState") || !alert.equal({})) { - if (alert.type == "controlsUnresponsive") { - bgColor = bg_colors[STATUS_ALERT]; - } else if (alert.type == "controlsUnresponsivePermanent") { - bgColor = bg_colors[STATUS_DISENGAGED]; - } - alerts->updateAlert(alert, bgColor); - } + alerts->updateAlert(alert); if (s.scene.map_on_left) { split->setDirection(QBoxLayout::LeftToRight); @@ -78,10 +72,15 @@ void OnroadWindow::updateState(const UIState &s) { } void OnroadWindow::mousePressEvent(QMouseEvent* e) { +#ifdef ENABLE_MAPS if (map != nullptr) { bool sidebarVisible = geometry().x() > 0; + if (map->isVisible() && !((MapPanel *)map)->isShowingMap() && e->windowPos().x() >= 1080) { + return; + } map->setVisible(!sidebarVisible && !map->isVisible()); } +#endif // propagation event to parent(HomeWindow) QWidget::mousePressEvent(e); } @@ -90,21 +89,19 @@ void OnroadWindow::offroadTransition(bool offroad) { #ifdef ENABLE_MAPS if (!offroad) { if (map == nullptr && (uiState()->primeType() || !MAPBOX_TOKEN.isEmpty())) { - MapWindow * m = new MapWindow(get_mapbox_settings()); + auto m = new MapPanel(get_mapbox_settings()); map = m; - QObject::connect(uiState(), &UIState::offroadTransition, m, &MapWindow::offroadTransition); - m->setFixedWidth(topWidget(this)->width() / 2 - bdr_s); split->insertWidget(0, m); - // Make map visible after adding to split - m->offroadTransition(offroad); + // hidden by default, made visible when navRoute is published + m->setVisible(false); } } #endif - alerts->updateAlert({}, bg); + alerts->updateAlert({}); } void OnroadWindow::paintEvent(QPaintEvent *event) { @@ -115,10 +112,9 @@ void OnroadWindow::paintEvent(QPaintEvent *event) { // ***** onroad widgets ***** // OnroadAlerts -void OnroadAlerts::updateAlert(const Alert &a, const QColor &color) { - if (!alert.equal(a) || color != bg) { +void OnroadAlerts::updateAlert(const Alert &a) { + if (!alert.equal(a)) { alert = a; - bg = color; update(); } } @@ -127,22 +123,28 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { if (alert.size == cereal::ControlsState::AlertSize::NONE) { return; } - static std::map alert_sizes = { + static std::map alert_heights = { {cereal::ControlsState::AlertSize::SMALL, 271}, {cereal::ControlsState::AlertSize::MID, 420}, {cereal::ControlsState::AlertSize::FULL, height()}, }; - int h = alert_sizes[alert.size]; - QRect r = QRect(0, height() - h, width(), h); + int h = alert_heights[alert.size]; + + int margin = 40; + int radius = 30; + if (alert.size == cereal::ControlsState::AlertSize::FULL) { + margin = 0; + radius = 0; + } + QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2); QPainter p(this); // draw background + gradient p.setPen(Qt::NoPen); p.setCompositionMode(QPainter::CompositionMode_SourceOver); - - p.setBrush(QBrush(bg)); - p.drawRect(r); + p.setBrush(QBrush(alert_colors[alert.status])); + p.drawRoundedRect(r, radius, radius); QLinearGradient g(0, r.y(), 0, r.bottom()); g.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.05)); @@ -150,7 +152,7 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { p.setCompositionMode(QPainter::CompositionMode_DestinationOver); p.setBrush(QBrush(g)); - p.fillRect(r, g); + p.drawRoundedRect(r, radius, radius); p.setCompositionMode(QPainter::CompositionMode_SourceOver); // text @@ -221,6 +223,7 @@ void ExperimentalButton::paintEvent(QPaintEvent *event) { } +// Window that shows camera view and variety of info drawn on top AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { pm = std::make_unique>({"uiDebug"}); @@ -303,131 +306,79 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; // Draw outer box + border to contain set speed and speed limit - int default_rect_width = 172; - int rect_width = default_rect_width; - if (is_metric || has_eu_speed_limit) rect_width = 200; - if (has_us_speed_limit && speedLimitStr.size() >= 3) rect_width = 223; + const int sign_margin = 12; + const int us_sign_height = 186; + const int eu_sign_size = 176; - int rect_height = 204; - if (has_us_speed_limit) rect_height = 402; - else if (has_eu_speed_limit) rect_height = 392; + 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; int top_radius = 32; int bottom_radius = has_eu_speed_limit ? 100 : 32; - QRect set_speed_rect(60 + default_rect_width / 2 - rect_width / 2, 45, rect_width, rect_height); + QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); 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) { - p.setPen(whiteColor()); + max_color = whiteColor(); } else if (status == STATUS_OVERRIDE) { - p.setPen(QColor(0x91, 0x9b, 0x95, 0xff)); + max_color = QColor(0x91, 0x9b, 0x95, 0xff); } else if (speedLimit > 0) { - p.setPen(interpColor( - setSpeed, - {speedLimit + 5, speedLimit + 15, speedLimit + 25}, - {QColor(0x80, 0xd8, 0xa6, 0xff), QColor(0xff, 0xe4, 0xbf, 0xff), QColor(0xff, 0xbf, 0xbf, 0xff)} - )); - } else { - p.setPen(QColor(0x80, 0xd8, 0xa6, 0xff)); + 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 { - p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff)); + max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); + set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); } configFont(p, "Inter", 40, "SemiBold"); - QRect max_rect = getTextRect(p, Qt::AlignCenter, tr("MAX")); - max_rect.moveCenter({set_speed_rect.center().x(), 0}); - max_rect.moveTop(set_speed_rect.top() + 27); - p.drawText(max_rect, Qt::AlignCenter, tr("MAX")); - - // Draw set speed - if (is_cruise_set) { - if (speedLimit > 0 && status != STATUS_DISENGAGED && status != STATUS_OVERRIDE) { - p.setPen(interpColor( - setSpeed, - {speedLimit + 5, speedLimit + 15, speedLimit + 25}, - {whiteColor(), QColor(0xff, 0x95, 0x00, 0xff), QColor(0xff, 0x00, 0x00, 0xff)} - )); - } else { - p.setPen(whiteColor()); - } - } else { - p.setPen(QColor(0x72, 0x72, 0x72, 0xff)); - } + p.setPen(max_color); + p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); configFont(p, "Inter", 90, "Bold"); - QRect speed_rect = getTextRect(p, Qt::AlignCenter, setSpeedStr); - speed_rect.moveCenter({set_speed_rect.center().x(), 0}); - speed_rect.moveTop(set_speed_rect.top() + 77); - p.drawText(speed_rect, Qt::AlignCenter, setSpeedStr); - - + 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) { - const int border_width = 6; - const int sign_width = rect_width - 24; - const int sign_height = 186; - - // White outer square - QRect sign_rect_outer(set_speed_rect.left() + 12, set_speed_rect.bottom() - 11 - sign_height, sign_width, sign_height); p.setPen(Qt::NoPen); p.setBrush(whiteColor()); - p.drawRoundedRect(sign_rect_outer, 24, 24); + p.drawRoundedRect(sign_rect, 24, 24); + p.setPen(QPen(blackColor(), 6)); + p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); - // Smaller white square with black border - QRect sign_rect(sign_rect_outer.left() + 1.5 * border_width, sign_rect_outer.top() + 1.5 * border_width, sign_width - 3 * border_width, sign_height - 3 * border_width); - p.setPen(QPen(blackColor(), border_width)); - p.setBrush(whiteColor()); - p.drawRoundedRect(sign_rect, 16, 16); - - // "SPEED" configFont(p, "Inter", 28, "SemiBold"); - QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, tr("SPEED")); - text_speed_rect.moveCenter({sign_rect.center().x(), 0}); - text_speed_rect.moveTop(sign_rect_outer.top() + 22); - p.drawText(text_speed_rect, Qt::AlignCenter, tr("SPEED")); - - // "LIMIT" - QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, tr("LIMIT")); - text_limit_rect.moveCenter({sign_rect.center().x(), 0}); - text_limit_rect.moveTop(sign_rect_outer.top() + 51); - p.drawText(text_limit_rect, Qt::AlignCenter, tr("LIMIT")); - - // Speed limit value + 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")); configFont(p, "Inter", 70, "Bold"); - QRect speed_limit_rect = getTextRect(p, Qt::AlignCenter, speedLimitStr); - speed_limit_rect.moveCenter({sign_rect.center().x(), 0}); - speed_limit_rect.moveTop(sign_rect_outer.top() + 85); - p.drawText(speed_limit_rect, Qt::AlignCenter, speedLimitStr); + p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr); } // EU (Vienna style) sign if (has_eu_speed_limit) { - int outer_radius = 176 / 2; - int inner_radius_1 = outer_radius - 6; // White outer border - int inner_radius_2 = inner_radius_1 - 20; // Red circle - - // Draw white circle with red border - QPoint center(set_speed_rect.center().x() + 1, set_speed_rect.top() + 204 + outer_radius); p.setPen(Qt::NoPen); p.setBrush(whiteColor()); - p.drawEllipse(center, outer_radius, outer_radius); - p.setBrush(QColor(255, 0, 0, 255)); - p.drawEllipse(center, inner_radius_1, inner_radius_1); - p.setBrush(whiteColor()); - p.drawEllipse(center, inner_radius_2, inner_radius_2); + p.drawEllipse(sign_rect); + p.setPen(QPen(Qt::red, 20)); + p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); - // Speed limit value - int font_size = (speedLimitStr.size() >= 3) ? 60 : 70; - configFont(p, "Inter", font_size, "Bold"); - QRect speed_limit_rect = getTextRect(p, Qt::AlignCenter, speedLimitStr); - speed_limit_rect.moveCenter(center); + configFont(p, "Inter", (speedLimitStr.size() >= 3) ? 60 : 70, "Bold"); p.setPen(blackColor()); - p.drawText(speed_limit_rect, Qt::AlignCenter, speedLimitStr); + p.drawText(sign_rect, Qt::AlignCenter, speedLimitStr); } // current speed @@ -439,10 +390,6 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { p.restore(); } - -// Window that shows camera view and variety of -// info drawn on top - void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &text, int alpha) { QRect real_rect = getTextRect(p, 0, text); real_rect.moveCenter({x, y - real_rect.height() / 2}); @@ -461,7 +408,6 @@ void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QB p.setOpacity(1.0); } - void AnnotatedCameraWidget::initializeGL() { CameraWidget::initializeGL(); qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION)); @@ -556,8 +502,9 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) painter.save(); // base icon - int x = rightHandDM ? rect().right() - (btn_size - 24) / 2 - (bdr_s * 2) : (btn_size - 24) / 2 + (bdr_s * 2); - int y = rect().bottom() - footer_h / 2; + int offset = bdr_s + btn_size / 2; + int x = rightHandDM ? width() - offset : offset; + int y = height() - offset; float opacity = dmActive ? 0.65 : 0.2; drawIcon(painter, x, y, dm_img, blackColor(70), opacity); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 73c2a37892..5a5a84f0e1 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -19,7 +19,7 @@ class OnroadAlerts : public QWidget { public: OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {}; - void updateAlert(const Alert &a, const QColor &color); + void updateAlert(const Alert &a); protected: void paintEvent(QPaintEvent*) override; diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 456cf748f4..6693c2247b 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -7,8 +7,6 @@ QFrame *horizontal_line(QWidget *parent) { QFrame *line = new QFrame(parent); line->setFrameShape(QFrame::StyledPanel); line->setStyleSheet(R"( - margin-left: 40px; - margin-right: 40px; border-width: 1px; border-bottom-style: solid; border-color: gray; @@ -127,19 +125,3 @@ void ElidedLabel::paintEvent(QPaintEvent *event) { opt.initFrom(this); style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); } - -ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { } - -void ClickableWidget::mouseReleaseEvent(QMouseEvent *event) { - if (rect().contains(event->pos())) { - emit clicked(); - } -} - -// Fix stylesheets -void ClickableWidget::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 807441cc85..9e745ad1af 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -298,17 +298,3 @@ public: setLayout(l); } }; - -class ClickableWidget : public QWidget { - Q_OBJECT - -public: - ClickableWidget(QWidget *parent = nullptr); - -protected: - void mouseReleaseEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *) override; - -signals: - void clicked(); -}; diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc index ceb823fb2b..cdfa86c8eb 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -92,7 +92,11 @@ int OffroadAlert::refresh() { std::string bytes = params.get(key); if (bytes.size()) { auto doc_par = QJsonDocument::fromJson(bytes.c_str()); - text = doc_par["text"].toString(); + text = tr(doc_par["text"].toString().toUtf8().data()); + auto extra = doc_par["extra"].toString(); + if (!extra.isEmpty()) { + text = text.arg(extra); + } } label->setText(text); label->setVisible(!text.isEmpty()); diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 447f66f016..8ee679c73b 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -116,6 +116,33 @@ Ablehnen, deinstallieren %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + No %1 location set + + + + home + + + + work + + + DevicePanel @@ -356,42 +383,14 @@ - MapPanel - - Current Destination - Aktuelles Ziel - - - CLEAR - LÖSCHEN - - - Recent Destinations - Letzte Ziele - + MapSettings - Try the Navigation Beta - Beta Navigation ausprobieren - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - Erhalte echtzeit Wegführung und mehr mit dem comma prime -Abonnement. Melde dich jetzt an: https://connect.comma.ai - - - No home -location set - Keine Heimadresse gesetzt - - - No work -location set - Keine Arbeitsadresse gesetzt + NAVIGATION + - no recent destinations - Keine kürzlich gewählten Ziele + Manage at connect.comma.ai + @@ -435,6 +434,62 @@ location set Falsches Passwort + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + + + + Unable to download updates +%1 + + + + Invalid date and time settings, system won't start. Connect to internet to set time. + + + + Taking camera snapshots. System won't start until finished. + + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + + + + NVMe drive not mounted. + + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -620,10 +675,6 @@ This may take up to a minute. Software Software - - Navigation - Navigation - Setup @@ -1036,10 +1087,6 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - Enable experimental longitudinal control to allow Experimental mode. Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen. @@ -1076,6 +1123,10 @@ This may take up to a minute. 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. + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + Updater diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 6dc183d7f7..803a896d6d 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -116,6 +116,33 @@ 拒否して %1 をアンインストール + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + No %1 location set + + + + home + + + + work + + + DevicePanel @@ -355,44 +382,14 @@ - MapPanel - - Current Destination - 現在の目的地 - - - CLEAR - 削除 - - - Recent Destinations - 最近の目的地 - + MapSettings - Try the Navigation Beta - β版ナビゲーションを試す - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - より詳細な案内情報を得ることができます。 -詳しくはこちら:https://connect.comma.ai - - - No home -location set - 自宅の住所はまだ -設定されていません - - - No work -location set - 職場の住所はまだ -設定されていません + NAVIGATION + - no recent destinations - 最近の目的地履歴がありません + Manage at connect.comma.ai + @@ -436,6 +433,62 @@ location set パスワードが間違っています + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + + + + Unable to download updates +%1 + + + + Invalid date and time settings, system won't start. Connect to internet to set time. + + + + Taking camera snapshots. System won't start until finished. + + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + + + + NVMe drive not mounted. + + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -618,10 +671,6 @@ This may take up to a minute. Software ソフトウェア - - Navigation - ナビゲーション - Setup @@ -1030,10 +1079,6 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - Enable experimental longitudinal control to allow Experimental mode. 実験段階のopenpilotによるアクセル制御を有効にしてください。 @@ -1070,6 +1115,10 @@ This may take up to a minute. 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. + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 1936805aa5..1b692fb29a 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -116,6 +116,33 @@ 거절, %1 제거 + + DestinationWidget + + Home + + + + Work + 회사 + + + No destination set + 설정된 목적지 없음 + + + No %1 location set + %1 위치가 설정되지 않았습니다 + + + home + + + + work + 회사 + + DevicePanel @@ -355,44 +382,14 @@ - MapPanel - - Current Destination - 현재 목적지 - - - CLEAR - 삭제 - - - Recent Destinations - 최근 목적지 - - - Try the Navigation Beta - 네비게이션(베타)를 사용해보세요 - + MapSettings - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - 자세한 경로안내를 원하시면 comma prime을 구독하세요. -등록:https://connect.comma.ai + NAVIGATION + 내비게이션 - No home -location set - 집 -설정되지않음 - - - No work -location set - 회사 -설정되지않음 - - - no recent destinations - 최근 목적지 없음 + Manage at connect.comma.ai + connect.comma.ai에서 관리 @@ -436,6 +433,63 @@ location set 비밀번호가 틀렸습니다 + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + 즉시 인터넷에 연결하여 업데이트를 확인하세요. 인터넷에 연결되어 있지 않으면 %1 이후에는 openpilot이 활성화되지 않습니다. + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + 인터넷에 연결하여 업데이트를 확인하세요. openpilot은 업데이트를 확인하기 위해 인터넷에 연결할 때까지 자동으로 시작되지 않습니다. + + + Unable to download updates +%1 + 업데이트를 다운로드할수 없습니다 +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + 날짜 및 시간 설정이 잘못되어 시스템이 시작되지 않습니다. 날짜와 시간을 동기화하려면 인터넷에 연결하세요. + + + Taking camera snapshots. System won't start until finished. + 카메라 스냅샷 찍기가 완료될 때까지 시스템이 시작되지 않습니다. + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + 백그라운드에서 운영 체제에 대한 업데이트를 다운로드되고 있습니다. 설치할 준비가 되면 업데이트하라는 메시지가 표시됩니다. + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + 장치를 등록하지 못했습니다. comma.ai 서버에 연결하거나 업로드하지 않으며 comma.ai에서 지원을 받지 않습니다. 공식 장치는 https://comma.ai/support로 방문하세요 + + + NVMe drive not mounted. + NVMe 드라이브가 마운트되지 않았습니다. + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + 지원되지 않는 NVMe 드라이브가 감지되었습니다. 지원되지 않는 NVMe 드라이브로 인해 장치가 훨씬 더 많은 전력을 소비하고 과열될 수 있습니다. + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + opepilot이 차량을 식별할수 없었습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 펌웨어 버전을 추가하려면 PR을 제출하십시오. 도움이 필요하시면 discord.comma.ai에 가입하세요. + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + openpilot이 차량을 식별할수 없었습니다. 케이블의 무결성을 점검하고 모든 연결부, 특히 comma power가 차량의 OBD-II 포트에 완전히 삽입되었는지 확인하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요. + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + openpilot이 장치의 장착 위치 변경을 감지했습니다. 장치가 마운트에 완전히 장착되고 마운트가 앞유리에 단단히 고정되었는지 확인하십시오. + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -619,10 +673,6 @@ This may take up to a minute. Software 소프트웨어 - - Navigation - 네비게이션 - Setup @@ -664,7 +714,7 @@ This may take up to a minute. Waiting for internet - 네트워크 접속을 기다립니다 + 인터넷 대기중 Enter URL @@ -684,7 +734,7 @@ This may take up to a minute. Ensure the entered URL is valid, and the device’s internet connection is good. - 입력된 URL이 유효하고 장치의 네트워크 연결이 잘 되어 있는지 확인하세요. + 입력된 URL이 유효하고 장치의 인터넷 연결이 양호한지 확인하세요. Reboot device @@ -1031,10 +1081,6 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. 오픈파일럿 롱컨트롤은 향후 업데이트에서 제공될 수 있습니다. - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 오픈파일럿 롱컨트롤의 실험 버전은 실험적 모드와 함께 릴리즈 되지 않은 브랜치에서 테스트할 수 있습니다. - Enable experimental longitudinal control to allow Experimental mode. 실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요. @@ -1071,6 +1117,10 @@ This may take up to a minute. 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 롱컨 제어의 알파 버전은 비 릴리스 분기에서 실험 모드와 함께 테스트할 수 있습니다. + Updater diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 226416dca9..a28ab89c6d 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -116,6 +116,33 @@ Rejeitar, desintalar %1 + + DestinationWidget + + Home + Casa + + + Work + Trabalho + + + No destination set + Nenhum destino definido + + + No %1 location set + Endereço de %1 não definido + + + home + casa + + + work + trabalho + + DevicePanel @@ -356,44 +383,14 @@ - MapPanel - - Current Destination - Destino Atual - - - CLEAR - LIMPAR - - - Recent Destinations - Destinos Recentes - - - Try the Navigation Beta - Experimente a Navegação Beta - + MapSettings - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - Obtenha instruções passo a passo exibidas e muito mais com -uma assinatura prime. Inscreva-se agora: https://connect.comma.ai + NAVIGATION + NAVEGAÇÃO - No home -location set - Sem local -residência definido - - - No work -location set - Sem local de -trabalho definido - - - no recent destinations - sem destinos recentes + Manage at connect.comma.ai + Gerencie em connect.comma.ai @@ -437,6 +434,63 @@ trabalho definido Senha incorreta + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + Conecte-se imediatamente à internet para verificar se há atualizações. Se você não se conectar à internet em %1 não será possível acionar o openpilot. + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + Conecte-se à internet para verificar se há atualizações. O openpilot não será iniciado automaticamente até que ele se conecte à internet para verificar se há atualizações. + + + Unable to download updates +%1 + Não é possível baixar atualizações +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + Configurações de data e hora inválidas, o sistema não será iniciado. Conecte-se à internet para definir o horário. + + + Taking camera snapshots. System won't start until finished. + Tirando fotos da câmera. O sistema não será iniciado até terminar. + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + Uma atualização para o sistema operacional do seu dispositivo está sendo baixada em segundo plano. Você será solicitado a atualizar quando estiver pronto para instalar. + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + Falha ao registrar o dispositivo. Ele não se conectará ou fará upload para os servidores comma.ai e não receberá suporte da comma.ai. Se este for um dispositivo oficial, visite https://comma.ai/support. + + + NVMe drive not mounted. + Unidade NVMe não montada. + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + Unidade NVMe não suportada detectada. O dispositivo pode consumir significativamente mais energia e superaquecimento devido ao NVMe não suportado. + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + O openpilot não conseguiu identificar o seu carro. Seu carro não é suportado ou seus ECUs não são reconhecidos. Envie um pull request para adicionar as versões de firmware ao veículo adequado. Precisa de ajuda? Junte-se discord.comma.ai. + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + O openpilot não conseguiu identificar o seu carro. Verifique a integridade dos cabos e certifique-se de que todas as conexões estejam seguras, especialmente se o comma power está totalmente inserido na porta OBD-II do veículo. Precisa de ajuda? Junte-se discord.comma.ai. + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + O openpilot detectou uma mudança na posição de montagem do dispositivo. Verifique se o dispositivo está totalmente encaixado no suporte e se o suporte está firmemente preso ao para-brisa. + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -623,10 +677,6 @@ Isso pode levar até um minuto. Software Software - - Navigation - Navegação - Setup @@ -1009,7 +1059,7 @@ Isso pode levar até um minuto. openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-embrionário</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: 🌮 End-to-End Longitudinal Control 🌮 @@ -1017,7 +1067,7 @@ Isso pode levar até um minuto. Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade alfa; erros devem ser esperados. + Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade embrionária; erros devem ser esperados. New Driving Visualization @@ -1033,11 +1083,7 @@ Isso pode levar até um minuto. openpilot longitudinal control may come in a future update. - O controle longitudinal openpilot pode vir em uma atualização futura. - - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Uma versão experimental do controle longitudinal openpilot pode ser testada, juntamente com o modo Experimental, em branches de desenvolvimento. + O controle longitudinal openpilot poderá vir em uma atualização futura. Enable experimental longitudinal control to allow Experimental mode. @@ -1045,15 +1091,15 @@ Isso pode levar até um minuto. openpilot Longitudinal Control (Alpha) - Controle Longitudinal openpilot (Alpha) + Controle Longitudinal openpilot (Embrionário) WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - AVISO: o controle longitudinal openpilot está em alfa para este carro e desativará a Frenagem Automática de Emergência (AEB). + AVISO: o controle longitudinal openpilot está em estado embrionário para este carro e desativará a Frenagem Automática de Emergência (AEB). 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. - Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o alfa de controle longitudinal openpilot. + Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o embrionário controle longitudinal openpilot. Aggressive @@ -1075,6 +1121,10 @@ Isso pode levar até um minuto. 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. + Updater @@ -1115,12 +1165,11 @@ Isso pode levar até um minuto. WiFiPromptWidget Setup Wi-Fi - Configurar -Wi-Fi + Configurar Wi-Fi Connect to Wi-Fi to upload driving data and help improve openpilot - Conecte se ao Wi-Fi para upload de dados de condução e ajudar a melhorar o openpilot + Conecte se ao Wi-Fi para realizar upload de dados de condução e ajudar a melhorar o openpilot Open Settings @@ -1128,7 +1177,7 @@ Wi-Fi Uploading training data - Subindo dados para treinamento + Subindo dados Your data is used to train driving models and help improve openpilot diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 45f91f1cdb..75665eb84e 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -116,6 +116,33 @@ ปฏิเสธ และถอนการติดตั้ง %1 + + DestinationWidget + + Home + บ้าน + + + Work + ที่ทำงาน + + + No destination set + ยังไม่ได้เลือกจุดหมาย + + + home + บ้าน + + + work + ที่ทำงาน + + + No %1 location set + ยังไม่ได้เลือกตำแหน่ง%1 + + DevicePanel @@ -355,44 +382,14 @@ - MapPanel + MapSettings - Current Destination - ปลายทางปัจจุบัน - - - CLEAR - ล้างข้อมูล - - - Recent Destinations - ปลายทางล่าสุด - - - Try the Navigation Beta - ลองใช้ระบบนำทาง (เบต้า) - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - รับการแสดงเส้นทางแบบเลี้ยวต่อเลี้ยว และอื่นๆ ด้วยการสมัครบริการ -comma prime สมัครเลย: https://connect.comma.ai - - - No home -location set - ยังไม่ได้กำหนด -ตำแหน่งของบ้าน - - - No work -location set - ยังไม่ได้กำหนด -ตำแหน่งของที่ทำงาน + NAVIGATION + การนำทาง - no recent destinations - ไม่พบปลายทางล่าสุด + Manage at connect.comma.ai + จัดการได้ที่ connect.comma.ai @@ -436,6 +433,63 @@ location set รหัสผ่านผิด + + OffroadAlert + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + อุณหภูมิของอุปกรณ์สูงเกินไป ระบบกำลังทำความเย็นก่อนเริ่ม อุณหภูมิของชิ้นส่วนภายในปัจจุบัน: %1 + + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + กรุณาเชื่อมต่ออินเตอร์เน็ตเพื่อตรวจสอบอัปเดทเดี๋ยวนี้ ถ้าคุณไม่เชื่อมต่ออินเตอร์เน็ต openpilot จะไม่ทำงานในอีก %1 + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + กรุณาเชื่อมต่ออินเตอร์เน็ตเพื่อตรวจสอบอัปเดท openpilot จะไม่เริ่มทำงานอัตโนมัติจนกว่าจะได้เชื่อมต่อกับอินเตอร์เน็ตเพื่อตรวจสอบอัปเดท + + + Unable to download updates +%1 + ไม่สามารถดาวน์โหลดอัพเดทได้ +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + วันที่และเวลาไม่ถูกต้อง ระบบจะไม่เริ่มทำงาน เชื่อต่ออินเตอร์เน็ตเพื่อตั้งเวลา + + + Taking camera snapshots. System won't start until finished. + กล้องกำลังถ่ายภาพ ระบบจะไม่เริ่มทำงานจนกว่าจะเสร็จ + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + กำลังดาวน์โหลดอัปเดทสำหรับระบบปฏิบัติการอยู่เบื้องหลัง คุณจะได้รับการแจ้งเตือนเมื่อระบบพร้อมสำหรับการติดตั้ง + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + ไม่สามารถลงทะเบียนอุปกรณ์ได้ อุปกรณ์จะไม่สามารถเชื่อมต่อหรืออัปโหลดไปยังเซิร์ฟเวอร์ของ comma.ai ได้และจะไม่ได้รับการสนับสนุนจาก comma.ai ถ้านี่คืออุปกรณ์อย่างเป็นทางการ กรุณาติดต่อ https://comma.ai/support + + + NVMe drive not mounted. + ไม่ได้ติดตั้งไดร์ฟ NVMe + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + ตรวจพบไดร์ฟ NVMe ที่ไม่รองรับ อุปกรณ์อาจใช้พลังงานมากขึ้นและร้อนเกินไปเนื่องจากไดร์ฟ NVMe ที่ไม่รองรับ + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + openpilot ไม่สามารถระบุรถยนต์ของคุณได้ ระบบอาจไม่รองรับรถยนต์ของคุณหรือไม่รู้จัก ECU กรุณาส่ง pull request เพื่อเพิ่มรุ่นของเฟิร์มแวร์ให้กับรถยนต์ที่เหมาะสม หากต้องการความช่วยเหลือให้เข้าร่วม discord.comma.ai + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + openpilot ไม่สามารถระบุรถยนต์ของคุณได้ กรุณาตรวจสอบสายเคเบิ้ลและจุดเชื่อมต่อทั้งหมดว่าแน่นหนา โดยเฉพาะ comma power ว่าได้ดันเข้าไปยังพอร์ต OBD II ของรถยนต์จนสุด หากต้องการความช่วยเหลือให้เข้าร่วม discord.comma.ai + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + openpilot ตรวจพบการเปลี่ยนแปลงของตำแหน่งที่ติดตั้ง กรุณาตรวจสอบว่าได้เลื่อนอุปกรณ์เข้ากับจุดติดตั้งจนสุดแล้ว และจุดติดตั้งได้ยึดติดกับกระจกหน้าอย่างแน่นหนา + + OffroadHome @@ -518,14 +572,6 @@ location set comma prime comma prime - - CONNECT.COMMA.AI - CONNECT.COMMA.AI - - - COMMA POINTS - คะแนน COMMA - QObject @@ -627,10 +673,6 @@ This may take up to a minute. Software ซอฟต์แวร์ - - Navigation - การนำทาง - Setup @@ -863,6 +905,26 @@ This may take up to a minute. Uninstall ถอนการติดตั้ง + + failed to check for update + ไม่สามารถตรวจสอบอัปเดตได้ + + + DOWNLOAD + ดาวน์โหลด + + + update available + มีอัปเดตใหม่ + + + never + ไม่เคย + + + up to date, last checked %1 + ล่าสุดแล้ว ตรวจสอบครั้งสุดท้ายเมื่อ %1 + SshControl @@ -987,22 +1049,10 @@ This may take up to a minute. Show map on left side when in split screen view. แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ - - Experimental openpilot Longitudinal Control - ทดลองใช้ระบบควบคุมการเร่ง/เบรคโดย openpilot - Experimental Mode โหมดทดลอง - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในขั้นพัฒนา และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในขั้นพัฒนา - 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> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: @@ -1031,14 +1081,46 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - เวอร์ชันทดลองของระบบควบคุมการเร่ง/เบรคโดย openpilot สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา - Enable experimental longitudinal control to allow Experimental mode. เปิดระบบควบคุมการเร่ง/เบรคขั้นพัฒนาโดย openpilot เพื่อเปิดใช้งานโหมดทดลอง + + openpilot Longitudinal Control (Alpha) + ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในสถานะ alpha และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด + + + 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. + โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในสถานะ alpha + + + Aggressive + ดุดัน + + + Standard + มาตรฐาน + + + Relaxed + ผ่อนคลาย + + + 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 ที่กำลังพัฒนา + Updater @@ -1075,6 +1157,29 @@ This may take up to a minute. การอัปเดตล้มเหลว + + WiFiPromptWidget + + Setup Wi-Fi + ตั้งค่า Wi-Fi + + + Connect to Wi-Fi to upload driving data and help improve openpilot + เชื่อมต่อกับ Wi-Fi เพื่ออัปโหลดข้อมูลการขับขี่และช่วยปรับปรุง openpilot + + + Open Settings + เปิดการตั้งค่า + + + Uploading training data + กำลังอัปโหลดข้อมูลสำหรับการฝึก + + + Your data is used to train driving models and help improve openpilot + ข้อมูลของคุณถูกใช้เพื่อฝึกโมเดลการขับขี่และช่วยปรับปรุง openpilot + + WifiUI diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 1d7cdd9f60..fce734dcb0 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -116,6 +116,33 @@ 拒绝并卸载%1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + No %1 location set + + + + home + + + + work + + + DevicePanel @@ -355,42 +382,14 @@ - MapPanel - - Current Destination - 当前目的地 - - - CLEAR - 清空 - - - Recent Destinations - 最近目的地 - - - Try the Navigation Beta - 试用导航测试版 - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - 订阅comma prime以获取导航。 -立即注册:https://connect.comma.ai - - - No home -location set - 家:未设定 - + MapSettings - No work -location set - 工作:未设定 + NAVIGATION + - no recent destinations - 无最近目的地 + Manage at connect.comma.ai + @@ -434,6 +433,62 @@ location set 密码错误 + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + + + + Unable to download updates +%1 + + + + Invalid date and time settings, system won't start. Connect to internet to set time. + + + + Taking camera snapshots. System won't start until finished. + + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + + + + NVMe drive not mounted. + + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -616,10 +671,6 @@ This may take up to a minute. Software 软件 - - Navigation - 导航 - Setup @@ -1028,10 +1079,6 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - Enable experimental longitudinal control to allow Experimental mode. 启用试验性的纵向控制,以便允许使用试验模式。 @@ -1068,6 +1115,10 @@ This may take up to a minute. 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. + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index c6e2d22f5a..900ef6f7d3 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -116,6 +116,33 @@ 拒絕並解除安裝 %1 + + DestinationWidget + + Home + 住家 + + + Work + 工作 + + + No destination set + 尚未設定目的地 + + + No %1 location set + 尚未設定 %1 的位置 + + + home + 住家 + + + work + 工作 + + DevicePanel @@ -355,44 +382,14 @@ - MapPanel - - Current Destination - 當前目的地 - + MapSettings - CLEAR - 清除 - - - Recent Destinations - 最近目的地 - - - Try the Navigation Beta - 試用導航功能 - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - 成為 comma 高級會員來使用導航功能 -立即註冊:https://connect.comma.ai - - - No home -location set - 未設定 -住家位置 - - - No work -location set - 未設定 -工作位置 + NAVIGATION + 導航 - no recent destinations - 沒有最近的導航記錄 + Manage at connect.comma.ai + 請在 connect.comma.ai 上進行管理 @@ -436,6 +433,63 @@ location set 密碼錯誤 + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + 請立即連接網路檢查更新。如果不連接網路,openpilot 將在 %1 後便無法使用 + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + 請連接至網際網路以檢查更新。在連接至網際網路並完成更新檢查之前,openpilot 將不會自動啟動。 + + + Unable to download updates +%1 + 無法下載更新 +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + 日期和時間設定無效,系統無法啟動。請連接至網際網路以設定時間。 + + + Taking camera snapshots. System won't start until finished. + 正在使用相機拍攝中。在完成之前,系統將無法啟動。 + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + 一個給您設備的操作系統的更新正在後台下載中。當更新準備好安裝時,您將收到提示進行更新。 + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + 設備註冊失敗。它將無法連接或上傳至 comma.ai 伺服器,並且無法獲得 comma.ai 的支援。如果這是一個官方設備,請訪問 https://comma.ai/support 。 + + + NVMe drive not mounted. + NVMe 固態硬碟未被掛載。 + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + 檢測到不支援的 NVMe 固態硬碟。您的設備因為使用了不支援的 NVMe 固態硬碟可能會消耗更多電力並更易過熱。 + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + openpilot 無法識別您的車輛。您的車輛可能未被支援,或是其電控單元 (ECU) 未被識別。請提交一個 Pull Request 為您的車輛添加正確的固件版本。需要幫助嗎?請加入 discord.comma.ai 。 + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + openpilot 無法識別您的車輛。請檢查線路是否正確的安裝並確保所有的連接都牢固,特別是確保 comma power 完全插入車輛的 OBD-II 接口。需要幫助嗎?請加入 discord.comma.ai 。 + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + openpilot偵測到設備的安裝位置發生變化。請確保設備完全安裝在支架上,並確保支架牢固地固定在擋風玻璃上。 + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + 設備溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 + + OffroadHome @@ -584,16 +638,17 @@ location set 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. - 設備重置中 此過程可能需要幾分鐘 + 設備重置中… +這可能需要一分鐘的時間。 @@ -618,10 +673,6 @@ This may take up to a minute. Software 軟體 - - Navigation - 導航 - Setup @@ -695,11 +746,11 @@ This may take up to a minute. No custom software found at this URL. - 無法在此URL找到定制的軟體 + 在此網址找不到自訂軟體。 Something went wrong. Reboot the device. - 發生了一些錯誤 請重新啟動您的設備 + 發生了一些錯誤。請重新啟動您的設備。 @@ -1028,11 +1079,7 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - 未來可能會推出openpilot縱向控制 - - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 在非發行分支中 可找到包含實驗模式的openpilot縱向控制測試版本 + openpilot 縱向控制可能會在未來的更新中提供。 Enable experimental longitudinal control to allow Experimental mode. @@ -1070,6 +1117,10 @@ This may take up to a minute. 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 版本,以及實驗模式。 + Updater @@ -1114,7 +1165,7 @@ This may take up to a minute. Connect to Wi-Fi to upload driving data and help improve openpilot - 請連接至 Wi-Fi 以上傳駕駛數據,並協助改進 openpilot。 + 請連接至 Wi-Fi 以上傳駕駛數據,並協助改進 openpilot Open Settings diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index ac8caa0f20..622c03a4fc 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -216,13 +216,8 @@ void ui_update_params(UIState *s) { void UIState::updateStatus() { if (scene.started && sm->updated("controlsState")) { auto controls_state = (*sm)["controlsState"].getControlsState(); - auto alert_status = controls_state.getAlertStatus(); auto state = controls_state.getState(); - if (alert_status == cereal::ControlsState::AlertStatus::USER_PROMPT) { - status = STATUS_WARNING; - } else if (alert_status == cereal::ControlsState::AlertStatus::CRITICAL) { - status = STATUS_ALERT; - } else if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { + if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { status = STATUS_OVERRIDE; } else { status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 2df0470035..60d6ee56a1 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -40,6 +40,7 @@ struct Alert { QString text2; QString type; cereal::ControlsState::AlertSize size; + cereal::ControlsState::AlertStatus status; AudibleAlert sound; bool equal(const Alert &a2) { @@ -51,6 +52,7 @@ struct Alert { if (sm.updated("controlsState")) { return {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(), cs.getAlertType().cStr(), cs.getAlertSize(), + cs.getAlertStatus(), cs.getAlertSound()}; } else if ((sm.frame - started_frame) > 5 * UI_FREQ) { const int CONTROLS_TIMEOUT = 5; @@ -61,16 +63,19 @@ struct Alert { // car is started, but controlsState hasn't been seen at all return {"openpilot Unavailable", "Waiting for controls to start", "controlsWaiting", cereal::ControlsState::AlertSize::MID, + cereal::ControlsState::AlertStatus::NORMAL, AudibleAlert::NONE}; } else if (controls_missing > CONTROLS_TIMEOUT && !Hardware::PC()) { // car is started, but controls is lagging or died if (cs.getEnabled() && (controls_missing - CONTROLS_TIMEOUT) < 10) { return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, + cereal::ControlsState::AlertStatus::CRITICAL, AudibleAlert::WARNING_IMMEDIATE}; } else { return {"Controls Unresponsive", "Reboot Device", "controlsUnresponsivePermanent", cereal::ControlsState::AlertSize::MID, + cereal::ControlsState::AlertStatus::NORMAL, AudibleAlert::NONE}; } } @@ -83,16 +88,18 @@ typedef enum UIStatus { STATUS_DISENGAGED, STATUS_OVERRIDE, STATUS_ENGAGED, - STATUS_WARNING, - STATUS_ALERT, } UIStatus; const QColor bg_colors [] = { [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), - [STATUS_WARNING] = QColor(0xDA, 0x6F, 0x25, 0xf1), - [STATUS_ALERT] = QColor(0xC9, 0x22, 0x31, 0xf1), +}; + +static std::map alert_colors = { + {cereal::ControlsState::AlertStatus::NORMAL, QColor(0x15, 0x15, 0x15, 0xf1)}, + {cereal::ControlsState::AlertStatus::USER_PROMPT, QColor(0xDA, 0x6F, 0x25, 0xf1)}, + {cereal::ControlsState::AlertStatus::CRITICAL, QColor(0xC9, 0x22, 0x31, 0xf1)}, }; typedef struct UIScene { diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index 5964eb49dc..3de6e0f27c 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -8,9 +8,23 @@ from common.basedir import BASEDIR UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") +TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") +def generate_translations_include(): + # offroad alerts + # TODO translate events from selfdrive/controls/lib/events.py + content = "// THIS IS AN AUTOGENERATED FILE, PLEASE EDIT alerts_offroad.json\n" + with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) as f: + for alert in json.load(f).values(): + content += f'QT_TRANSLATE_NOOP("OffroadAlert", R"({alert["text"]})");\n' + + with open(TRANSLATIONS_INCLUDE_FILE, "w") as f: + f.write(content) + def update_translations(vanish=False, plural_only=None, translations_dir=TRANSLATIONS_DIR): + generate_translations_include() + if plural_only is None: plural_only = [] diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 9ef5d89a4e..1b2594bc80 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -82,8 +82,6 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, rgb_width = ci->frame_width; rgb_height = ci->frame_height; - yuv_transform = get_model_yuv_transform(); - int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, rgb_width); int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, rgb_height); assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, rgb_width)); @@ -151,7 +149,6 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setFrameId(frame_data.frame_id); framed.setTimestampEof(frame_data.timestamp_eof); framed.setTimestampSof(frame_data.timestamp_sof); - framed.setFrameLength(frame_data.frame_length); framed.setIntegLines(frame_data.integ_lines); framed.setGain(frame_data.gain); framed.setHighConversionGain(frame_data.high_conversion_gain); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 07d1291a2a..3e56f5690d 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -53,7 +53,6 @@ typedef struct CameraInfo { typedef struct FrameMetadata { uint32_t frame_id; - unsigned int frame_length; // Timestamps uint64_t timestamp_sof; // only set on tici @@ -91,8 +90,6 @@ public: std::unique_ptr camera_bufs_metadata; int rgb_width, rgb_height, rgb_stride; - mat3 yuv_transform; - CameraBuf() = default; ~CameraBuf(); void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType yuv_type); diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 4325eccde5..4b33ced045 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -1242,10 +1242,6 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { framed.setImage(get_raw_frame_image(b)); } LOGT(c->buf.cur_frame_data.frame_id, "%s: Image set", c == &s->road_cam ? "RoadCamera" : "WideRoadCamera"); - if (c == &s->road_cam) { - framed.setTransform(b->yuv_transform.v); - LOGT(c->buf.cur_frame_data.frame_id, "%s: Transformed", "RoadCamera"); - } if (c->camera_id == CAMERA_ID_AR0231) { ar0231_process_registers(s, c, framed); diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index d52710e950..780d870dcd 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -106,8 +106,10 @@ class Tici(HardwareBase): return model def get_sound_card_online(self): - return (os.path.isfile('/proc/asound/card0/state') and - open('/proc/asound/card0/state').read().strip() == 'ONLINE') + if os.path.isfile('/proc/asound/card0/state'): + with open('/proc/asound/card0/state') as f: + return f.read().strip() == 'ONLINE' + return False def reboot(self, reason=None): subprocess.check_output(["sudo", "reboot"]) @@ -452,7 +454,8 @@ class Tici(HardwareBase): def get_gpu_usage_percent(self): try: - used, total = open('/sys/class/kgsl/kgsl-3d0/gpubusy').read().strip().split() + with open('/sys/class/kgsl/kgsl-3d0/gpubusy') as f: + used, total = f.read().strip().split() return 100.0 * int(used) / int(total) except Exception: return 0 @@ -484,7 +487,7 @@ class Tici(HardwareBase): sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_bus_on") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_clk_on") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_rail_on") - sudo_write("1000000", "/sys/class/kgsl/kgsl-3d0/idle_timer") + sudo_write("1000", "/sys/class/kgsl/kgsl-3d0/idle_timer") sudo_write("performance", "/sys/class/kgsl/kgsl-3d0/devfreq/governor") sudo_write("596", "/sys/class/kgsl/kgsl-3d0/max_clock_mhz") diff --git a/system/hardware/tici/tests/test_amplifier.py b/system/hardware/tici/tests/test_amplifier.py index 4cacef4080..3a7c5987a3 100755 --- a/system/hardware/tici/tests/test_amplifier.py +++ b/system/hardware/tici/tests/test_amplifier.py @@ -32,27 +32,29 @@ class TestAmplifier(unittest.TestCase): dmesg = subprocess.check_output("dmesg", shell=True, encoding='utf8') i2c_lines = [l for l in dmesg.strip().splitlines() if 'i2c_geni a88000.i2c' in l] i2c_str = '\n'.join(i2c_lines) + if not expected: - assert len(i2c_lines) == 0 + return len(i2c_lines) == 0 else: - assert "i2c error :-107" in i2c_str or "Bus arbitration lost" in i2c_str + return "i2c error :-107" in i2c_str or "Bus arbitration lost" in i2c_str def test_init(self): amp = Amplifier(debug=True) r = amp.initialize_configuration(Tici().get_device_type()) assert r - self._check_for_i2c_errors(False) + assert self._check_for_i2c_errors(False) def test_shutdown(self): amp = Amplifier(debug=True) for _ in range(10): r = amp.set_global_shutdown(True) r = amp.set_global_shutdown(False) + # amp config should be successful, with no i2c errors assert r - self._check_for_i2c_errors(False) + assert self._check_for_i2c_errors(False) def test_init_while_siren_play(self): - for _ in range(5): + for _ in range(10): self.panda.set_siren(False) time.sleep(0.1) @@ -63,8 +65,10 @@ class TestAmplifier(unittest.TestCase): r = amp.initialize_configuration(Tici().get_device_type()) assert r - # make sure we're a good test - self._check_for_i2c_errors(True) + if self._check_for_i2c_errors(True): + break + else: + self.fail("didn't hit any i2c errors") if __name__ == "__main__": diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 9ad3687613..af526ccb41 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -58,7 +58,7 @@ struct RemoteEncoder { bool seen_first_packet = false; }; -int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct RemoteEncoder &re, EncoderInfo encoder_info) { +int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct RemoteEncoder &re, const EncoderInfo &encoder_info) { int bytes_count = 0; // extract the message diff --git a/system/logmessaged.py b/system/logmessaged.py index 280a23cf1d..04101d042b 100755 --- a/system/logmessaged.py +++ b/system/logmessaged.py @@ -12,7 +12,7 @@ def main() -> NoReturn: log_handler.setFormatter(SwagLogFileFormatter(None)) log_level = 20 # logging.INFO - ctx = zmq.Context().instance() + ctx = zmq.Context.instance() sock = ctx.socket(zmq.PULL) sock.bind("ipc:///tmp/logmessage") @@ -20,23 +20,37 @@ def main() -> NoReturn: log_message_sock = messaging.pub_sock('logMessage') error_log_message_sock = messaging.pub_sock('errorLogMessage') - while True: - dat = b''.join(sock.recv_multipart()) - level = dat[0] - record = dat[1:].decode("utf-8") - if level >= log_level: - log_handler.emit(record) + try: + while True: + dat = b''.join(sock.recv_multipart()) + level = dat[0] + record = dat[1:].decode("utf-8") + if level >= log_level: + log_handler.emit(record) - # then we publish them - msg = messaging.new_message() - msg.logMessage = record - log_message_sock.send(msg.to_bytes()) + if len(record) > 2*1024*1024: + print("WARNING: log too big to publish", len(record)) + print(print(record[:100])) + continue - if level >= 40: # logging.ERROR + # then we publish them msg = messaging.new_message() - msg.errorLogMessage = record - error_log_message_sock.send(msg.to_bytes()) - + msg.logMessage = record + log_message_sock.send(msg.to_bytes()) + + if level >= 40: # logging.ERROR + msg = messaging.new_message() + msg.errorLogMessage = record + error_log_message_sock.send(msg.to_bytes()) + finally: + sock.close() + ctx.term() + + # can hit this if interrupted during a rollover + try: + log_handler.close() + except ValueError: + pass if __name__ == "__main__": main() diff --git a/system/sensord/rawgps/rawgpsd.py b/system/sensord/rawgps/rawgpsd.py index 46f5cf83c9..8e3e160be2 100755 --- a/system/sensord/rawgps/rawgpsd.py +++ b/system/sensord/rawgps/rawgpsd.py @@ -8,7 +8,7 @@ import time import pycurl import subprocess from datetime import datetime -from typing import NoReturn +from typing import NoReturn, Optional from struct import unpack_from, calcsize, pack from cereal import log @@ -91,15 +91,13 @@ def try_setup_logs(diag, log_types): else: raise Exception(f"setup logs failed, {log_types=}") -def at_cmd(cmd: str) -> None: +def at_cmd(cmd: str) -> Optional[str]: for _ in range(5): try: - subprocess.check_call(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True) - break + return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') except subprocess.CalledProcessError: cloudlog.exception("rawgps.mmcli_command_failed") - else: - raise Exception(f"failed to execute mmcli command {cmd=}") + raise Exception(f"failed to execute mmcli command {cmd=}") def gps_enabled() -> bool: @@ -183,7 +181,7 @@ def setup_quectel(diag: ModemDiag): #at_cmd("AT+QGPSXTRADATA?") time_str = datetime.utcnow().strftime("%Y/%m/%d,%H:%M:%S") at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000") - + at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"") at_cmd("AT+QGPS=1") diff --git a/system/sensord/rawgps/test_rawgps.py b/system/sensord/rawgps/test_rawgps.py index 5bd0833955..0e0d5e0365 100755 --- a/system/sensord/rawgps/test_rawgps.py +++ b/system/sensord/rawgps/test_rawgps.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 +import os import json import time +import datetime import unittest import subprocess import cereal.messaging as messaging from system.hardware import TICI +from system.sensord.rawgps.rawgpsd import at_cmd from selfdrive.manager.process_config import managed_processes @@ -15,23 +18,36 @@ class TestRawgpsd(unittest.TestCase): if not TICI: raise unittest.SkipTest + cls.sm = messaging.SubMaster(['qcomGnss']) + def tearDown(self): managed_processes['rawgpsd'].stop() + def _wait_for_output(self, t=10): + self.sm.update(0) + for __ in range(t*10): + self.sm.update(100) + if self.sm.updated['qcomGnss']: + break + return self.sm.updated['qcomGnss'] + + def test_wait_for_modem(self): + os.system("sudo systemctl stop ModemManager lte") + managed_processes['rawgpsd'].start() + assert not self._wait_for_output(10) + + os.system("sudo systemctl restart ModemManager lte") + assert self._wait_for_output(30) + def test_startup_time(self): for _ in range(5): - sm = messaging.SubMaster(['qcomGnss']) managed_processes['rawgpsd'].start() start_time = time.monotonic() - for __ in range(10): - sm.update(1 * 1000) - if sm.updated['qcomGnss']: - break - assert sm.rcv_frame['qcomGnss'] > 0, "rawgpsd didn't start outputting messages in time" + assert self._wait_for_output(), "rawgpsd didn't start outputting messages in time" et = time.monotonic() - start_time - assert et < 5, f"rawgpsd took {et:.1f}s to start" + assert et < 7, f"rawgpsd took {et:.1f}s to start" managed_processes['rawgpsd'].stop() def test_turns_off_gnss(self): @@ -44,6 +60,24 @@ class TestRawgpsd(unittest.TestCase): loc_status = json.loads(ls) assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} + def test_assistance_loading(self): + # clear assistance data + at_cmd("AT+QGPSDEL=0") + + managed_processes['rawgpsd'].start() + assert self._wait_for_output(10) + managed_processes['rawgpsd'].stop() + + # after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"' + # after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"' + out = at_cmd("AT+QGPSXTRADATA?") + out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip() + valid_duration, injected_date = out.split(",", 1) + assert valid_duration == "10080" # should be max time + + # TODO: time doesn't match up + assert injected_date[1:].startswith(datetime.datetime.now().strftime("%Y/%m/%d")) + if __name__ == "__main__": - unittest.main() + unittest.main(failfast=True) diff --git a/system/sensord/tests/test_pigeond.py b/system/sensord/tests/test_pigeond.py index d15b731d0c..9519183aac 100755 --- a/system/sensord/tests/test_pigeond.py +++ b/system/sensord/tests/test_pigeond.py @@ -26,8 +26,8 @@ class TestPigeond(unittest.TestCase): sm = messaging.SubMaster(['ubloxRaw']) # setup time - time.sleep(2) - sm.update() + for _ in range(int(5 * service_list['ubloxRaw'].frequency)): + sm.update() for _ in range(int(10 * service_list['ubloxRaw'].frequency)): sm.update() diff --git a/system/swaglog.py b/system/swaglog.py index 68664330a5..953b9a93b2 100644 --- a/system/swaglog.py +++ b/system/swaglog.py @@ -72,6 +72,15 @@ class UnixDomainSocketHandler(logging.Handler): self.setFormatter(formatter) self.pid = None + self.zctx = None + self.sock = None + + def __del__(self): + if self.sock is not None: + self.sock.close() + if self.zctx is not None: + self.zctx.term() + def connect(self): self.zctx = zmq.Context() self.sock = self.zctx.socket(zmq.PUSH) diff --git a/system/tests/__init__.py b/system/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/system/tests/test_logmessaged.py b/system/tests/test_logmessaged.py new file mode 100755 index 0000000000..08335517ae --- /dev/null +++ b/system/tests/test_logmessaged.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +import glob +import os +import shutil +import time +import unittest + +import cereal.messaging as messaging +from selfdrive.manager.process_config import managed_processes +from system.swaglog import cloudlog, SWAGLOG_DIR + + +class TestLogmessaged(unittest.TestCase): + + def setUp(self): + if os.path.exists(SWAGLOG_DIR): + shutil.rmtree(SWAGLOG_DIR) + + managed_processes['logmessaged'].start() + self.sock = messaging.sub_sock("logMessage", timeout=1000, conflate=False) + self.error_sock = messaging.sub_sock("logMessage", timeout=1000, conflate=False) + + # ensure sockets are connected + time.sleep(0.2) + messaging.drain_sock(self.sock) + messaging.drain_sock(self.error_sock) + + def tearDown(self): + del self.sock + del self.error_sock + managed_processes['logmessaged'].stop(block=True) + + def _get_log_files(self): + return list(glob.glob(os.path.join(SWAGLOG_DIR, "swaglog.*"))) + + def test_simple_log(self): + msgs = [f"abc {i}" for i in range(10)] + for m in msgs: + cloudlog.error(m) + time.sleep(3) + m = messaging.drain_sock(self.sock) + assert len(m) == len(msgs) + assert len(self._get_log_files()) >= 1 + + def test_big_log(self): + n = 10 + msg = "a"*3*1024*1024 + for _ in range(n): + cloudlog.info(msg) + time.sleep(3) + + msgs = messaging.drain_sock(self.sock) + assert len(msgs) == 0 + + logsize = sum([os.path.getsize(f) for f in self._get_log_files()]) + assert (n*len(msg)) < logsize < (n*(len(msg)+1024)) + + +if __name__ == "__main__": + unittest.main() diff --git a/third_party/acados/.gitignore b/third_party/acados/.gitignore index 9787bd1b55..68858c62e4 100644 --- a/third_party/acados/.gitignore +++ b/third_party/acados/.gitignore @@ -1,4 +1,5 @@ acados_repo/ +lib !x86_64/ !larch64/ !aarch64/ diff --git a/third_party/acados/Darwin/lib/libacados.dylib b/third_party/acados/Darwin/lib/libacados.dylib index 6d133f693d..ca6c280297 100755 Binary files a/third_party/acados/Darwin/lib/libacados.dylib and b/third_party/acados/Darwin/lib/libacados.dylib differ diff --git a/third_party/acados/Darwin/lib/libblasfeo.dylib b/third_party/acados/Darwin/lib/libblasfeo.dylib index 7ba6a8b32d..0217db1048 100755 Binary files a/third_party/acados/Darwin/lib/libblasfeo.dylib and b/third_party/acados/Darwin/lib/libblasfeo.dylib differ diff --git a/third_party/acados/Darwin/lib/libhpipm.dylib b/third_party/acados/Darwin/lib/libhpipm.dylib index 69141c8178..420ac45753 100755 Binary files a/third_party/acados/Darwin/lib/libhpipm.dylib and b/third_party/acados/Darwin/lib/libhpipm.dylib differ diff --git a/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib b/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib index b0cf93b060..07dc6ea9b0 100755 Binary files a/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib and b/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib differ diff --git a/third_party/acados/Darwin/t_renderer b/third_party/acados/Darwin/t_renderer index 1afbd81519..f40327a59e 100755 Binary files a/third_party/acados/Darwin/t_renderer and b/third_party/acados/Darwin/t_renderer differ diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index 9b18b2b67d..574a577b4d 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -13,13 +13,8 @@ fi ACADOS_FLAGS="-DACADOS_WITH_QPOASES=ON -UBLASFEO_TARGET -DBLASFEO_TARGET=$BLAS_TARGET" if [[ "$OSTYPE" == "darwin"* ]]; then - if [[ $(uname -m) == "x86_64" ]]; then - ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=x86_64" - ARCHNAME="Darwin_x86_64" - else - ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" - ARCHNAME="Darwin" - fi + ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 -DCMAKE_MACOSX_RPATH=1" + ARCHNAME="Darwin" fi if [ ! -d acados_repo/ ]; then @@ -51,5 +46,11 @@ cp -r $DIR/acados_repo/interfaces/acados_template/acados_template $DIR/ # build tera cd $DIR/acados_repo/interfaces/acados_template/tera_renderer/ -cargo build --verbose --release +if [[ "$OSTYPE" == "darwin"* ]]; then + cargo build --verbose --release --target aarch64-apple-darwin + cargo build --verbose --release --target x86_64-apple-darwin + lipo -create -output target/release/t_renderer target/x86_64-apple-darwin/release/t_renderer target/aarch64-apple-darwin/release/t_renderer +else + cargo build --verbose --release +fi cp target/release/t_renderer $INSTALL_DIR/ diff --git a/tools/README.md b/tools/README.md index c3f5b0ada5..28c819c28b 100644 --- a/tools/README.md +++ b/tools/README.md @@ -2,7 +2,7 @@ ## System Requirements -openpilot is developed and tested on **Ubuntu 20.04**, which is the primary development target aside from the [supported embedded hardware](https://github.com/commaai/openpilot#running-on-pc). We also have a CI test to verify that openpilot builds on macOS, but the tools are untested. For the best experience, stick to Ubuntu 20.04, otherwise openpilot and the tools should work with minimal to no modifications on macOS and other Linux systems. +openpilot is developed and tested on **Ubuntu 20.04**, which is the primary development target aside from the [supported embedded hardware](https://github.com/commaai/openpilot#running-on-a-dedicated-device-in-a-car). We also have a CI test to verify that openpilot builds on macOS, but the tools are untested. For the best experience, stick to Ubuntu 20.04, otherwise openpilot and the tools should work with minimal to no modifications on macOS and other Linux systems. ## Setup your PC @@ -38,12 +38,11 @@ scons -u -j$(nproc) ### Windows -Neither openpilot nor any of the tools are developed or tested on Windows, but the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) should get Windows users a similar experience to Ubuntu. [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/compare-versions) specifically has been reported by several users to be a seamless experience. +Neither openpilot nor any of the tools are developed or tested on Windows, but the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) should provide a similar experience to native Ubuntu. [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/compare-versions) specifically has been reported by several users to be a seamless experience. -Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-20.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. - -GUI applications do not work with WSL out of the box. You will have to either [upgrade your system to Windows 11](https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) or [set up an Xorg server](https://techcommunity.microsoft.com/t5/windows-dev-appconsult/running-wsl-gui-apps-on-windows-10/ba-p/1493242). +Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-20.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. See [these instructions](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for running GUI apps. +**NOTE**: If you are running WSL and any GUIs are failing (segfaulting or other strange issues) even after following the steps above, you may need to enable software rendering with `LIBGL_ALWAYS_SOFTWARE=1`, e.g. `LIBGL_ALWAYS_SOFTWARE=1 selfdrive/ui/ui`. ## CTF Learn about the openpilot ecosystem and tools by playing our [CTF](/tools/CTF.md). diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index cc5259136a..6aac56cc78 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -1,7 +1,5 @@ #include "tools/cabana/binaryview.h" -#include - #include #include #include @@ -11,16 +9,12 @@ #include #include "tools/cabana/commands.h" -#include "tools/cabana/signalview.h" // BinaryView const int CELL_HEIGHT = 36; const int VERTICAL_HEADER_WIDTH = 30; - -inline int get_bit_index(const QModelIndex &index, bool little_endian) { - return index.row() * 8 + (little_endian ? 7 - index.column() : index.column()); -} +inline int get_bit_pos(const QModelIndex &index) { return flipBitPos(index.row() * 8 + index.column()); } BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { model = new BinaryViewModel(this); @@ -66,7 +60,7 @@ void BinaryView::addShortcuts() { QObject::connect(shortcut_delete_backspace, &QShortcut::activated, shortcut_delete_x, &QShortcut::activated); QObject::connect(shortcut_delete_x, &QShortcut::activated, [=]{ if (hovered_sig != nullptr) { - emit removeSignal(hovered_sig); + UndoStack::push(new RemoveSigCommand(model->msg_id, hovered_sig)); hovered_sig = nullptr; } }); @@ -75,13 +69,9 @@ void BinaryView::addShortcuts() { QShortcut *shortcut_endian = new QShortcut(QKeySequence(Qt::Key_E), this); QObject::connect(shortcut_endian, &QShortcut::activated, [=]{ if (hovered_sig != nullptr) { - const cabana::Signal *hovered_sig_prev = hovered_sig; cabana::Signal s = *hovered_sig; s.is_little_endian = !s.is_little_endian; emit editSignal(hovered_sig, s); - - hovered_sig = nullptr; - highlight(hovered_sig_prev); } }); @@ -89,13 +79,9 @@ void BinaryView::addShortcuts() { QShortcut *shortcut_sign = new QShortcut(QKeySequence(Qt::Key_S), this); QObject::connect(shortcut_sign, &QShortcut::activated, [=]{ if (hovered_sig != nullptr) { - const cabana::Signal *hovered_sig_prev = hovered_sig; cabana::Signal s = *hovered_sig; s.is_signed = !s.is_signed; emit editSignal(hovered_sig, s); - - hovered_sig = nullptr; - highlight(hovered_sig_prev); } }); @@ -139,24 +125,24 @@ void BinaryView::setSelection(const QRect &rect, QItemSelectionModel::SelectionF QItemSelection selection; auto [start, size, is_lb] = getSelection(index); - for (int i = start; i < start + size; ++i) { - auto idx = model->bitIndex(i, is_lb); - selection.merge({idx, idx}, flags); + for (int i = 0; i < size; ++i) { + int pos = is_lb ? flipBitPos(start + i) : flipBitPos(start) + i; + selection << QItemSelectionRange{model->index(pos / 8, pos % 8)}; } selectionModel()->select(selection, flags); } void BinaryView::mousePressEvent(QMouseEvent *event) { - delegate->selection_color = (palette().color(QPalette::Active, QPalette::Highlight)); + resize_sig = nullptr; if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) { anchor_index = index; auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); - int bit_idx = get_bit_index(anchor_index, true); + int bit_pos = get_bit_pos(anchor_index); for (auto s : item->sigs) { - if (bit_idx == s->lsb || bit_idx == s->msb) { - anchor_index = model->bitIndex(bit_idx == s->lsb ? s->msb : s->lsb, true); + if (bit_pos == s->lsb || bit_pos == s->msb) { + int idx = flipBitPos(bit_pos == s->lsb ? s->msb : s->lsb); + anchor_index = model->index(idx / 8, idx % 8); resize_sig = s; - delegate->selection_color = s->color; break; } } @@ -183,9 +169,10 @@ void BinaryView::mouseReleaseEvent(QMouseEvent *event) { auto release_index = indexAt(event->pos()); if (release_index.isValid() && anchor_index.isValid()) { if (selectionModel()->hasSelection()) { - auto [start_bit, size, is_lb] = getSelection(release_index); - resize_sig ? emit resizeSignal(resize_sig, start_bit, size) - : emit addSignal(start_bit, size, is_lb); + auto sig = resize_sig ? *resize_sig : cabana::Signal{}; + std::tie(sig.start_bit, sig.size, sig.is_little_endian) = getSelection(release_index); + resize_sig ? emit editSignal(resize_sig, sig) + : UndoStack::push(new AddSigCommand(model->msg_id, sig)); } else { auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); if (item && item->sigs.size() > 0) @@ -246,10 +233,11 @@ std::tuple BinaryView::getSelection(QModelIndex index) { is_lb = false; } - int cur_bit_idx = get_bit_index(index, is_lb); - int anchor_bit_idx = get_bit_index(anchor_index, is_lb); - auto [start_bit, end_bit] = std::minmax(cur_bit_idx, anchor_bit_idx); - return {start_bit, end_bit - start_bit + 1, is_lb}; + int cur_bit_pos = get_bit_pos(index); + int anchor_bit_pos = get_bit_pos(anchor_index); + int start_bit = is_lb ? std::min(cur_bit_pos, anchor_bit_pos) : get_bit_pos(std::min(index, anchor_index)); + int size = is_lb ? std::abs(cur_bit_pos - anchor_bit_pos) + 1 : std::abs(flipBitPos(cur_bit_pos) - flipBitPos(anchor_bit_pos)) + 1; + return {start_bit, size, is_lb}; } // BinaryViewModel @@ -261,16 +249,15 @@ void BinaryViewModel::refresh() { row_count = dbc_msg->size; items.resize(row_count * column_count); for (auto sig : dbc_msg->getSignals()) { - auto [start, end] = getSignalRange(sig); - for (int j = start; j <= end; ++j) { - int bit_index = sig->is_little_endian ? bigEndianBitIndex(j) : j; - int idx = column_count * (bit_index / 8) + bit_index % 8; + for (int j = 0; j < sig->size; ++j) { + int pos = sig->is_little_endian ? flipBitPos(sig->start_bit + j) : flipBitPos(sig->start_bit) + j; + int idx = column_count * (pos / 8) + pos % 8; if (idx >= items.size()) { qWarning() << "signal " << sig->name << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; break; } - if (j == start) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; - if (j == end) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; + if (j == 0) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; + if (j == sig->size - 1) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; auto &sigs = items[idx].sigs; sigs.push_back(sig); @@ -379,7 +366,8 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->fillRect(option.rect, item->bg_color); } } else if (option.state & QStyle::State_Selected) { - painter->fillRect(option.rect, selection_color); + auto color = bin_view->resize_sig ? bin_view->resize_sig->color : option.palette.color(QPalette::Active, QPalette::Highlight); + painter->fillRect(option.rect, color); painter->setPen(option.palette.color(QPalette::BrightText)); } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { // not resizing if (item->sigs.size() > 0) { @@ -441,7 +429,7 @@ void BinaryItemDelegate::drawSignalCell(QPainter *painter, const QStyleOptionVie QColor color = sig->color; color.setAlpha(item->bg_color.alpha()); // Mixing the signal colour with the Base background color to fade it - painter->fillRect(rc, QApplication::palette().color(QPalette::Base)); + painter->fillRect(rc, option.palette.color(QPalette::Base)); painter->fillRect(rc, color); // Draw edges diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index d9966c9110..161b2aad8a 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -13,12 +12,10 @@ class BinaryItemDelegate : public QStyledItemDelegate { public: BinaryItemDelegate(QObject *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - void setSelectionColor(const QColor &color) { selection_color = color; } bool hasSignal(const QModelIndex &index, int dx, int dy, const cabana::Signal *sig) const; void drawSignalCell(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index, const cabana::Signal *sig) const; QFont small_font, hex_font; - QColor selection_color; }; class BinaryViewModel : public QAbstractTableModel { @@ -31,7 +28,6 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } - inline QModelIndex bitIndex(int bit, bool is_lb) const { return index(bit / 8, is_lb ? (7 - bit % 8) : bit % 8); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { return createIndex(row, column, (void *)&items[row * column_count + column]); } @@ -68,9 +64,6 @@ public: signals: void signalClicked(const cabana::Signal *sig); void signalHovered(const cabana::Signal *sig); - void addSignal(int start_bit, int size, bool little_endian); - void resizeSignal(const cabana::Signal *sig, int from, int size); - void removeSignal(const cabana::Signal *sig); void editSignal(const cabana::Signal *origin_s, cabana::Signal &s); void showChart(const MessageId &id, const cabana::Signal *sig, bool show, bool merge); diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index d2c451be89..49b8fcf6ca 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -15,6 +15,8 @@ int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setApplicationDisplayName("Cabana"); app.setWindowIcon(QIcon(":cabana-icon.png")); + + UnixSignalHandler signalHandler; utils::setTheme(settings.theme); QCommandLineParser cmd_parser; diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 567fceebad..ebc463af0e 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -402,7 +402,7 @@ void ChartsWidget::removeAll() { if (!charts.isEmpty()) { for (auto c : charts) { - c->deleteLater(); + delete c; } charts.clear(); updateToolBar(); diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index 1ddec0ea93..cf48abb4c9 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -56,8 +56,20 @@ AddSigCommand::AddSigCommand(const MessageId &id, const cabana::Signal &sig, QUn setText(QObject::tr("add signal %1 to %2:%3").arg(sig.name).arg(msgName(id)).arg(id.address)); } -void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name); } -void AddSigCommand::redo() { dbc()->addSignal(id, signal); } +void AddSigCommand::undo() { + dbc()->removeSignal(id, signal.name); + if (msg_created) dbc()->removeMsg(id); +} + +void AddSigCommand::redo() { + if (auto msg = dbc()->msg(id); !msg) { + msg_created = true; + dbc()->updateMsg(id, dbc()->newMsgName(id), can->lastMessage(id).dat.size(), ""); + } + signal.name = dbc()->newSignalName(id); + signal.max = std::pow(2, signal.size) - 1; + dbc()->addSignal(id, signal); +} // RemoveSigCommand diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index 4e2e2eac4b..a1e667807e 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -37,6 +37,7 @@ public: private: const MessageId id; + bool msg_created = false; cabana::Signal signal = {}; }; diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index 269aeb4247..4b6ad86557 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -76,7 +76,7 @@ QString cabana::Msg::newSignalName() { } void cabana::Msg::update() { - mask = QVector(size, 0x00).toList(); + mask.assign(size, 0x00); multiplexor = nullptr; // sort signals @@ -111,6 +111,9 @@ void cabana::Msg::update() { for (auto sig : sigs) { sig->multiplexor = sig->type == cabana::Signal::Type::Multiplexed ? multiplexor : nullptr; if (!sig->multiplexor) { + if (sig->type == cabana::Signal::Type::Multiplexed) { + sig->type = cabana::Signal::Type::Normal; + } sig->multiplex_value = 0; } } @@ -119,6 +122,8 @@ void cabana::Msg::update() { // cabana::Signal void cabana::Signal::update() { + updateMsbLsb(*this); + float h = 19 * (float)lsb / 64.0; h = fmod(h, 1.0); size_t hash = qHash(name); @@ -131,8 +136,9 @@ void cabana::Signal::update() { QString cabana::Signal::formatValue(double value) const { // Show enum string + int64_t raw_value = round((value - offset) / factor); for (const auto &[val, desc] : val_desc) { - if (std::abs(value - val) < 1e-6) { + if (std::abs(raw_value - val) < 1e-6) { return desc; } } @@ -164,14 +170,6 @@ bool cabana::Signal::operator==(const cabana::Signal &other) const { // helper functions -static QVector BIG_ENDIAN_START_BITS = []() { - QVector ret; - for (int i = 0; i < 64; i++) - for (int j = 7; j >= 0; j--) - ret.push_back(j + i * 8); - return ret; -}(); - double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig) { int64_t val = 0; @@ -194,23 +192,12 @@ double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal return val * sig.factor + sig.offset; } -int bigEndianStartBitsIndex(int start_bit) { return BIG_ENDIAN_START_BITS[start_bit]; } -int bigEndianBitIndex(int index) { return BIG_ENDIAN_START_BITS.indexOf(index); } - -void updateSigSizeParamsFromRange(cabana::Signal &s, int start_bit, int size) { - s.start_bit = s.is_little_endian ? start_bit : bigEndianBitIndex(start_bit); - s.size = size; +void updateMsbLsb(cabana::Signal &s) { if (s.is_little_endian) { s.lsb = s.start_bit; s.msb = s.start_bit + s.size - 1; } else { - s.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(s.start_bit) + s.size - 1); + s.lsb = flipBitPos(flipBitPos(s.start_bit) + s.size - 1); s.msb = s.start_bit; } } - -std::pair getSignalRange(const cabana::Signal *s) { - int from = s->is_little_endian ? s->start_bit : bigEndianBitIndex(s->start_bit); - int to = from + s->size - 1; - return {from, to}; -} diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index d7439620a7..9896d1144b 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -66,7 +66,8 @@ public: Type type = Type::Normal; QString name; int start_bit, msb, lsb, size; - double factor, offset; + double factor = 1.0; + double offset = 0; bool is_signed; bool is_little_endian; double min, max; @@ -102,7 +103,7 @@ public: QString comment; std::vector sigs; - QList mask; + std::vector mask; cabana::Signal *multiplexor = nullptr; }; @@ -110,9 +111,7 @@ public: // Helper functions double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig); -int bigEndianStartBitsIndex(int start_bit); -int bigEndianBitIndex(int index); -void updateSigSizeParamsFromRange(cabana::Signal &s, int start_bit, int size); -std::pair getSignalRange(const cabana::Signal *s); +void updateMsbLsb(cabana::Signal &s); +inline int flipBitPos(int start_bit) { return 8 * (start_bit / 8) + 7 - start_bit % 8; } inline std::vector allDBCNames() { return get_dbc_names(); } inline QString doubleToString(double value) { return QString::number(value, 'g', std::numeric_limits::digits10); } diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index c7df7553ef..d894d56197 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -136,7 +136,6 @@ void DBCFile::parse(const QString &content) { dbc_assert(++multiplexor_cnt < 2, "Multiple multiplexor"); s.type = cabana::Signal::Type::Multiplexor; } else { - dbc_assert(multiplexor_cnt == 1, "No multiplexor"); s.type = cabana::Signal::Type::Multiplexed; s.multiplex_value = indicator.mid(1).toInt(); } @@ -148,16 +147,10 @@ void DBCFile::parse(const QString &content) { s.is_signed = match.captured(offset + 5) == "-"; s.factor = match.captured(offset + 6).toDouble(); s.offset = match.captured(offset + 7).toDouble(); - if (s.is_little_endian) { - s.lsb = s.start_bit; - s.msb = s.start_bit + s.size - 1; - } else { - s.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(s.start_bit) + s.size - 1); - s.msb = s.start_bit; - } s.min = match.captured(8 + offset).toDouble(); s.max = match.captured(9 + offset).toDouble(); s.unit = match.captured(10 + offset); + current_msg->sigs.push_back(new cabana::Signal(s)); } else if (line.startsWith("VAL_ ")) { auto match = val_regexp.match(line); diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 388844098a..5736ac1e89 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -58,6 +58,7 @@ void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { if (auto m = msg(id)) { if (auto s = m->addSignal(sig)) { emit signalAdded(id, s); + emit maskUpdated(); } } } @@ -66,6 +67,7 @@ void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, cons if (auto m = msg(id)) { if (auto s = m->updateSignal(sig_name, sig)) { emit signalUpdated(s); + emit maskUpdated(); } } } @@ -75,6 +77,7 @@ void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { if (auto s = m->sig(sig_name)) { emit signalRemoved(s); m->removeSignal(sig_name); + emit maskUpdated(); } } } @@ -91,6 +94,7 @@ void DBCManager::removeMsg(const MessageId &id) { assert(dbc_file); // This should be impossible dbc_file->removeMsg(id); emit msgRemoved(id); + emit maskUpdated(); } QString DBCManager::newMsgName(const MessageId &id) { @@ -102,8 +106,8 @@ QString DBCManager::newSignalName(const MessageId &id) { return m ? m->newSignalName() : ""; } -const QList &DBCManager::mask(const MessageId &id) { - static QList empty_mask; +const std::vector &DBCManager::mask(const MessageId &id) { + static std::vector empty_mask; auto m = msg(id); return m ? m->mask : empty_mask; } diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 659253b82a..f20d4888e2 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -32,7 +32,7 @@ public: QString newMsgName(const MessageId &id); QString newSignalName(const MessageId &id); - const QList& mask(const MessageId &id); + const std::vector& mask(const MessageId &id); const std::map &getMessages(uint8_t source); cabana::Msg *msg(const MessageId &id); @@ -57,6 +57,7 @@ signals: void msgUpdated(MessageId id); void msgRemoved(MessageId id); void DBCFileChanged(); + void maskUpdated(); private: std::map> dbc_files; diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 47c5e4a62e..2f6e9cbfe8 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -64,12 +64,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart QObject::connect(edit_btn, &QToolButton::clicked, this, &DetailWidget::editMsg); QObject::connect(remove_btn, &QToolButton::clicked, this, &DetailWidget::removeMsg); - QObject::connect(binary_view, &BinaryView::resizeSignal, signal_view->model, &SignalModel::resizeSignal); - QObject::connect(binary_view, &BinaryView::addSignal, signal_view->model, &SignalModel::addSignal); QObject::connect(binary_view, &BinaryView::signalHovered, signal_view, &SignalView::signalHovered); QObject::connect(binary_view, &BinaryView::signalClicked, [this](const cabana::Signal *s) { signal_view->selectSignal(s, true); }); QObject::connect(binary_view, &BinaryView::editSignal, signal_view->model, &SignalModel::saveSignal); - QObject::connect(binary_view, &BinaryView::removeSignal, signal_view->model, &SignalModel::removeSignal); QObject::connect(binary_view, &BinaryView::showChart, charts, &ChartsWidget::showChart); QObject::connect(signal_view, &SignalView::showChart, charts, &ChartsWidget::showChart); QObject::connect(signal_view, &SignalView::highlight, binary_view, &BinaryView::highlight); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index f61a89b73c..cf9dea88db 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -141,7 +141,6 @@ std::deque HistoryLogModel::fetchData(InputIt first, I } std::deque HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) { - const QList mask; const auto &events = can->events(msg_id); const auto freq = can->lastMessage(msg_id).freq; const bool update_colors = !display_signals_mode || sigs.empty(); @@ -154,7 +153,7 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti auto msgs = fetchData(first, events.rend(), min_time); if (update_colors && (min_time > 0 || messages.empty())) { for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { - hex_colors.compute(it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, mask, freq); + hex_colors.compute(it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); it->colors = hex_colors.colors; } } @@ -167,7 +166,7 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti auto msgs = fetchData(first, events.cend(), 0); if (update_colors) { for (auto it = msgs.begin(); it != msgs.end(); ++it) { - hex_colors.compute(it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, mask, freq); + hex_colors.compute(it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); it->colors = hex_colors.colors; } } diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 2961d888ff..87066bf9e6 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -373,7 +373,8 @@ void MainWindow::eventsMerged() { if (!dbc()->msgCount() && !car_fingerprint.isEmpty()) { auto dbc_name = fingerprint_to_dbc[car_fingerprint]; if (dbc_name != QJsonValue::Undefined) { - loadDBCFromOpendbc(dbc_name.toString()); + // Prevent dialog that load autosaved file from blocking replay->start(). + QTimer::singleShot(0, [dbc_name, this]() { loadDBCFromOpendbc(dbc_name.toString()); }); } } } diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 5982689bda..0b2c635f88 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -83,6 +83,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { }); QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, [=](int state) { settings.suppress_defined_signals = (state == Qt::Checked); + emit settings.changed(); }); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified); diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index a557aa57fb..b862abe56a 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -1,11 +1,9 @@ #include "tools/cabana/signalview.h" -#include #include #include #include #include -#include #include #include #include @@ -214,53 +212,12 @@ bool SignalModel::saveSignal(const cabana::Signal *origin_s, cabana::Signal &s) } if (s.is_little_endian != origin_s->is_little_endian) { - int start = std::floor(s.start_bit / 8); - if (s.is_little_endian) { - int end = std::floor((s.start_bit - s.size + 1) / 8); - s.start_bit = start == end ? s.start_bit - s.size + 1 : bigEndianStartBitsIndex(s.start_bit); - } else { - int end = std::floor((s.start_bit + s.size - 1) / 8); - s.start_bit = start == end ? s.start_bit + s.size - 1 : bigEndianBitIndex(s.start_bit); - } + s.start_bit = flipBitPos(s.start_bit); } - if (s.is_little_endian) { - s.lsb = s.start_bit; - s.msb = s.start_bit + s.size - 1; - } else { - s.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(s.start_bit) + s.size - 1); - s.msb = s.start_bit; - } - UndoStack::push(new EditSignalCommand(msg_id, origin_s, s)); return true; } -void SignalModel::addSignal(int start_bit, int size, bool little_endian) { - auto msg = dbc()->msg(msg_id); - if (!msg) { - QString name = dbc()->newMsgName(msg_id); - UndoStack::push(new EditMsgCommand(msg_id, name, can->lastMessage(msg_id).dat.size(), "")); - msg = dbc()->msg(msg_id); - } - - cabana::Signal sig = {.name = dbc()->newSignalName(msg_id), .is_little_endian = little_endian, .factor = 1, .min = 0, .max = std::pow(2, size) - 1}; - updateSigSizeParamsFromRange(sig, start_bit, size); - UndoStack::push(new AddSigCommand(msg_id, sig)); -} - -void SignalModel::resizeSignal(const cabana::Signal *sig, int start_bit, int size) { - cabana::Signal s = *sig; - updateSigSizeParamsFromRange(s, start_bit, size); - saveSignal(sig, s); -} - -void SignalModel::removeSignal(const cabana::Signal *sig) { - UndoStack::push(new RemoveSigCommand(msg_id, sig)); - if (dbc()->signalCount(msg_id) == 0) { - UndoStack::push(new RemoveMsgCommand(msg_id)); - } -} - void SignalModel::handleMsgChanged(MessageId id) { if (id.address == msg_id.address) { refresh(); @@ -324,7 +281,7 @@ QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo } width = std::min(option.widget->size().width() / 3.0, it.value() + spacing); } - return {width, QApplication::fontMetrics().height()}; + return {width, option.fontMetrics.height()}; } void SignalItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -528,7 +485,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QObject::connect(tree, &QTreeView::entered, [this](const QModelIndex &index) { emit highlight(model->getItem(index)->sig); }); QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged); - QObject::connect(dbc(), &DBCManager::signalAdded, [this](MessageId id, const cabana::Signal *sig) { selectSignal(sig); }); + QObject::connect(dbc(), &DBCManager::signalAdded, this, &SignalView::handleSignalAdded); QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalView::handleSignalUpdated); QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); }); QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); }); @@ -565,7 +522,7 @@ void SignalView::rowsChanged() { tree->setIndexWidget(index, w); auto sig = model->getItem(index)->sig; - QObject::connect(remove_btn, &QToolButton::clicked, [=]() { model->removeSignal(sig); }); + QObject::connect(remove_btn, &QToolButton::clicked, [=]() { UndoStack::push(new RemoveSigCommand(model->msg_id, sig)); }); QObject::connect(plot_btn, &QToolButton::clicked, [=](bool checked) { emit showChart(model->msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); }); @@ -632,6 +589,12 @@ void SignalView::setSparklineRange(int value) { updateState(); } +void SignalView::handleSignalAdded(MessageId id, const cabana::Signal *sig) { + if (id.address == model->msg_id.address) { + selectSignal(sig); + } +} + void SignalView::handleSignalUpdated(const cabana::Signal *sig) { if (int row = model->signalRow(sig); row != -1) { auto item = model->getItem(model->index(row, 1)); diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index eb268e517c..9541ac8a3b 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -41,10 +41,7 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; void setMessage(const MessageId &id); void setFilter(const QString &txt); - void addSignal(int start_bit, int size, bool little_endian); bool saveSignal(const cabana::Signal *origin_s, cabana::Signal &s); - void resizeSignal(const cabana::Signal *sig, int start_bit, int size); - void removeSignal(const cabana::Signal *sig); Item *getItem(const QModelIndex &index) const; int signalRow(const cabana::Signal *sig) const; void showExtraInfo(const QModelIndex &index); @@ -116,6 +113,7 @@ private: void resizeEvent(QResizeEvent* event) override; void updateToolBar(); void setSparklineRange(int value); + void handleSignalAdded(MessageId id, const cabana::Signal *sig); void handleSignalUpdated(const cabana::Signal *sig); void updateState(const QHash *msgs = nullptr); diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 059215b364..7cd210d8f1 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -12,6 +12,9 @@ StreamNotifier *StreamNotifier::instance() { AbstractStream::AbstractStream(QObject *parent) : new_msgs(new QHash()), QObject(parent) { assert(parent != nullptr); QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo); + QObject::connect(&settings, &Settings::changed, this, &AbstractStream::updateMasks); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &AbstractStream::updateMasks); + QObject::connect(dbc(), &DBCManager::maskUpdated, this, &AbstractStream::updateMasks); QObject::connect(this, &AbstractStream::streamStarted, [this]() { emit StreamNotifier::instance()->changingStream(); delete can; @@ -20,6 +23,20 @@ AbstractStream::AbstractStream(QObject *parent) : new_msgs(new QHashfindDBCFile(s)) { + for (const auto &[address, m] : f->getMessages()) { + masks[{.source = (uint8_t)s, .address = address}] = m.mask; + } + } + } + } +} + void AbstractStream::updateMessages(QHash *messages) { auto prev_src_size = sources.size(); auto prev_msg_size = last_msgs.size(); @@ -29,6 +46,7 @@ void AbstractStream::updateMessages(QHash *messages) { sources.insert(id.source); } if (sources.size() != prev_src_size) { + updateMasks(); emit sourcesUpdated(sources); } emit updated(); @@ -38,7 +56,9 @@ void AbstractStream::updateMessages(QHash *messages) { } void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size) { - QList mask = settings.suppress_defined_signals ? dbc()->mask(id) : QList(); + std::lock_guard lk(mutex); + auto mask_it = masks.find(id); + std::vector *mask = mask_it == masks.end() ? nullptr : &mask_it->second; all_msgs[id].compute((const char *)data, size, sec, getSpeed(), mask); if (!new_msgs->contains(id)) { new_msgs->insert(id, {}); @@ -47,7 +67,8 @@ void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t bool AbstractStream::postEvents() { // delay posting CAN message if UI thread is busy - if (processing.exchange(true) == false) { + if (processing == false) { + processing = true; for (auto it = new_msgs->begin(); it != new_msgs->end(); ++it) { it.value() = all_msgs[it.key()]; } @@ -84,7 +105,8 @@ void AbstractStream::updateLastMsgsTo(double sec) { auto it = std::lower_bound(ev.crbegin(), ev.crend(), last_ts, [](auto e, uint64_t ts) { return e->mono_time > ts; }); - QList mask = settings.suppress_defined_signals ? dbc()->mask(id) : QList(); + auto mask_it = masks.find(id); + std::vector *mask = mask_it == masks.end() ? nullptr : &mask_it->second; if (it != ev.crend()) { double ts = (*it)->mono_time / 1e9 - routeStartTime(); auto &m = all_msgs[id]; @@ -93,7 +115,10 @@ void AbstractStream::updateLastMsgsTo(double sec) { m.freq = m.count / std::max(1.0, ts); } } + + // deep copy all_msgs to last_msgs to avoid multi-threading issue. last_msgs = all_msgs; + last_msgs.detach(); // use a timer to prevent recursive calls QTimer::singleShot(0, [this]() { emit updated(); @@ -171,7 +196,7 @@ static inline QColor blend(const QColor &a, const QColor &b) { return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); } -void CanData::compute(const char *can_data, const int size, double current_sec, double playback_speed, const QList &mask, uint32_t in_freq) { +void CanData::compute(const char *can_data, const int size, double current_sec, double playback_speed, const std::vector *mask, uint32_t in_freq) { ts = current_sec; ++count; const double sec_to_first_event = current_sec - (can->allEvents().front()->mono_time / 1e9 - can->routeStartTime()); @@ -190,7 +215,7 @@ void CanData::compute(const char *can_data, const int size, double current_sec, const QColor &greyish_blue = !lighter ? GREYISH_BLUE : GREYISH_BLUE_LIGHTER; for (int i = 0; i < size; ++i) { - const uint8_t mask_byte = (i < mask.size()) ? (~mask[i]) : 0xff; + const uint8_t mask_byte = (mask && i < mask->size()) ? (~((*mask)[i])) : 0xff; const uint8_t last = dat[i] & mask_byte; const uint8_t cur = can_data[i] & mask_byte; const int delta = cur - last; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 78610fc657..9a3ab73183 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -13,7 +13,7 @@ #include "tools/replay/replay.h" struct CanData { - void compute(const char *dat, const int size, double current_sec, double playback_speed, const QList &mask, uint32_t in_freq = 0); + void compute(const char *dat, const int size, double current_sec, double playback_speed, const std::vector *mask, uint32_t in_freq = 0); double ts = 0.; uint32_t count = 0; @@ -79,6 +79,7 @@ protected: uint64_t lastEventMonoTime() const { return lastest_event_ts; } void updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size); void updateMessages(QHash *); + void updateMasks(); void updateLastMsgsTo(double sec); uint64_t lastest_event_ts = 0; @@ -88,6 +89,8 @@ protected: std::unordered_map> events_; std::vector all_events_; std::deque> memory_blocks; + std::mutex mutex; + std::unordered_map> masks; }; class AbstractOpenStreamWidget : public QWidget { diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index b7d2f4c015..fceb823bbb 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -231,7 +231,9 @@ void FindSignalDlg::setInitialSignals() { for (int size = min_size->value(); size <= max_size->value(); ++size) { for (int start = 0; start <= total_size - size; ++start) { FindSignalModel::SearchSignal s{.id = it.key(), .mono_time = first_time, .sig = sig}; - updateSigSizeParamsFromRange(s.sig, start, size); + s.sig.start_bit = start; + s.sig.size = size; + updateMsbLsb(s.sig); s.value = get_raw_value((*e)->dat, (*e)->size, s.sig); model->initial_signals.push_back(s); } @@ -258,12 +260,6 @@ void FindSignalDlg::customMenuRequested(const QPoint &pos) { menu.addAction(tr("Create Signal")); if (menu.exec(view->mapToGlobal(pos))) { auto &s = model->filtered_signals[index.row()]; - auto msg = dbc()->msg(s.id); - if (!msg) { - UndoStack::push(new EditMsgCommand(s.id, dbc()->newMsgName(s.id), can->lastMessage(s.id).dat.size(), "")); - msg = dbc()->msg(s.id); - } - s.sig.name = dbc()->newSignalName(s.id); UndoStack::push(new AddSigCommand(s.id, s.sig)); emit openMessage(s.id); } diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index 3cf328e1e0..e7ae4a2d0d 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -1,12 +1,16 @@ #include "tools/cabana/util.h" +#include +#include +#include +#include + +#include #include #include -#include #include #include #include -#include #include "selfdrive/ui/qt/util.h" @@ -145,6 +149,40 @@ void TabBar::closeTabClicked() { } } +// UnixSignalHandler + +UnixSignalHandler::UnixSignalHandler(QObject *parent) : QObject(nullptr) { + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sig_fd)) { + qFatal("Couldn't create TERM socketpair"); + } + + sn = new QSocketNotifier(sig_fd[1], QSocketNotifier::Read, this); + connect(sn, &QSocketNotifier::activated, this, &UnixSignalHandler::handleSigTerm); + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, UnixSignalHandler::signalHandler); +} + +UnixSignalHandler::~UnixSignalHandler() { + ::close(sig_fd[0]); + ::close(sig_fd[1]); +} + +void UnixSignalHandler::signalHandler(int s) { + ::write(sig_fd[0], &s, sizeof(s)); +} + +void UnixSignalHandler::handleSigTerm() { + sn->setEnabled(false); + int tmp; + ::read(sig_fd[1], &tmp, sizeof(tmp)); + + printf("\nexiting...\n"); + qApp->closeAllWindows(); + qApp->exit(); +} + +// NameValidator + NameValidator::NameValidator(QObject *parent) : QRegExpValidator(QRegExp("^(\\w+)"), parent) {} QValidator::State NameValidator::validate(QString &input, int &pos) const { diff --git a/tools/cabana/util.h b/tools/cabana/util.h index 74724dfb33..25dc245fe0 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -135,5 +136,21 @@ private: void closeTabClicked(); }; +class UnixSignalHandler : public QObject { + Q_OBJECT + +public: + UnixSignalHandler(QObject *parent = nullptr); + ~UnixSignalHandler(); + static void signalHandler(int s); + +public slots: + void handleSigTerm(); + +private: + inline static int sig_fd[2] = {}; + QSocketNotifier *sn; +}; + int num_decimals(double num); QString signalToolTip(const cabana::Signal *sig); diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 6e6538c5c5..d5b0f189bb 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -69,6 +69,7 @@ export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/bzip2/include" # pycurl curl/openssl backend dependencies export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/openssl@3/lib" export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/openssl@3/include" +export PYCURL_CURL_CONFIG=/usr/bin/curl-config export PYCURL_SSL_LIBRARY=openssl # openpilot environment @@ -83,29 +84,6 @@ $ROOT/update_requirements.sh eval "$(pyenv init --path)" echo "[ ] installed python dependencies t=$SECONDS" -# install casadi -VENV=`poetry env info --path` -PYTHON_VER=3.8 -PYTHON_VERSION=$(cat $ROOT/.python-version) -if [ ! -f "$VENV/include/casadi/casadi.hpp" ]; then - echo "-- casadi manual install" - cd /tmp/ && curl -L https://github.com/casadi/casadi/archive/refs/tags/ge6.tar.gz --output casadi.tar.gz - tar -xzf casadi.tar.gz - cd casadi-ge6/ && mkdir -p build && cd build - cmake .. \ - -DWITH_PYTHON=ON \ - -DWITH_EXAMPLES=OFF \ - -DCMAKE_INSTALL_PREFIX:PATH=$VENV \ - -DPYTHON_PREFIX:PATH=$VENV/lib/python$PYTHON_VER/site-packages \ - -DPYTHON_LIBRARY:FILEPATH=$HOME/.pyenv/versions/$PYTHON_VERSION/lib/libpython$PYTHON_VER.dylib \ - -DPYTHON_EXECUTABLE:FILEPATH=$HOME/.pyenv/versions/$PYTHON_VERSION/bin/python \ - -DPYTHON_INCLUDE_DIR:PATH=$HOME/.pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_VER \ - -DCMAKE_CXX_FLAGS="-ferror-limit=0" -DCMAKE_C_FLAGS="-ferror-limit=0" - CFLAGS="-ferror-limit=0" make -j$(nproc) && make install -else - echo "---- casadi found in venv. skipping build ----" -fi - echo echo "---- OPENPILOT SETUP DONE ----" echo "Open a new shell or configure your active shell env by running:" diff --git a/tools/plotjuggler/layouts/thermal_debug.xml b/tools/plotjuggler/layouts/thermal_debug.xml index c10b78f1c5..0a0a077245 100644 --- a/tools/plotjuggler/layouts/thermal_debug.xml +++ b/tools/plotjuggler/layouts/thermal_debug.xml @@ -1,12 +1,12 @@ - + - + - - + + @@ -19,8 +19,8 @@ - - + + @@ -29,40 +29,47 @@ - - + + + + + + + + + - + - - + + - - + + - - + + - + - - + + @@ -71,8 +78,8 @@ - - + + diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 28adcffb92..a5e227d871 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -100,7 +100,7 @@ void Replay::start(int seconds) { } void Replay::updateEvents(const std::function &lambda) { - // set updating_events to true to force stream thread release the lock and wait for evnets_udpated. + // set updating_events to true to force stream thread release the lock and wait for events_updated. updating_events_ = true; { std::unique_lock lk(stream_lock_);