Merge remote-tracking branch 'upstream/master' into 10hz-pandaStates

pull/29889/head
Shane Smiskol 2 years ago
commit 43c9df413b
  1. 4
      .github/workflows/prebuilt.yaml
  2. 18
      .github/workflows/selfdrive_tests.yaml
  3. 6
      .github/workflows/tools_tests.yaml
  4. 2
      .pre-commit-config.yaml
  5. 18
      README.md
  6. 2
      RELEASES.md
  7. 52
      SConstruct
  8. 11
      common/SConscript
  9. 4
      docs/CARS.md
  10. 2
      opendbc
  11. 2
      panda
  12. 191
      poetry.lock
  13. 4
      selfdrive/car/gm/carstate.py
  14. 3
      selfdrive/car/hyundai/hyundaican.py
  15. 10
      selfdrive/car/hyundai/interface.py
  16. 35
      selfdrive/car/hyundai/values.py
  17. 4
      selfdrive/car/tests/routes.py
  18. 14
      selfdrive/car/tests/test_models.py
  19. 8
      selfdrive/car/torque_data/override.yaml
  20. 49
      selfdrive/car/toyota/interface.py
  21. 3
      selfdrive/car/toyota/values.py
  22. 6
      selfdrive/locationd/SConscript
  23. 41
      selfdrive/locationd/liblocationd.cc
  24. 109
      selfdrive/locationd/test/_test_locationd_lib.py
  25. 223
      selfdrive/locationd/test/test_locationd_scenarios.py
  26. 17
      tools/cabana/binaryview.cc
  27. 6
      tools/cabana/binaryview.h
  28. 36
      tools/cabana/chart/chart.cc
  29. 2
      tools/cabana/chart/chart.h
  30. 5
      tools/cabana/chart/chartswidget.cc
  31. 14
      tools/cabana/commands.cc
  32. 5
      tools/cabana/commands.h
  33. 6
      tools/cabana/dbc/dbc.cc
  34. 1
      tools/cabana/dbc/dbc.h
  35. 8
      tools/cabana/dbc/dbcfile.cc
  36. 2
      tools/cabana/dbc/dbcfile.h
  37. 4
      tools/cabana/dbc/dbcmanager.cc
  38. 2
      tools/cabana/dbc/dbcmanager.h
  39. 23
      tools/cabana/detailwidget.cc
  40. 1
      tools/cabana/detailwidget.h
  41. 14
      tools/cabana/mainwin.cc
  42. 5
      tools/cabana/settings.cc
  43. 3
      tools/cabana/settings.h
  44. 16
      tools/cabana/signalview.cc
  45. 4
      tools/cabana/signalview.h
  46. 2
      tools/cabana/streams/abstractstream.h
  47. 7
      tools/cabana/util.cc
  48. 8
      tools/cabana/util.h
  49. 9
      tools/lib/auth_config.py
  50. 6
      tools/lib/cache.py
  51. 26
      tools/lib/framereader.py
  52. 47
      tools/replay/tests/test_replay.cc
  53. 5
      tools/sim/launch_openpilot.sh

@ -5,7 +5,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_GHCR_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: selfdrive/test/docker_build.sh prebuilt BUILD: selfdrive/test/docker_build.sh prebuilt
jobs: jobs:
@ -29,5 +29,5 @@ jobs:
submodules: true submodules: true
- name: Build and Push docker image - name: Build and Push docker image
run: | run: |
$DOCKER_LOGIN $DOCKER_GHCR_LOGIN
eval "$BUILD" eval "$BUILD"

@ -16,7 +16,8 @@ env:
CL_BASE_IMAGE: openpilot-base-cl CL_BASE_IMAGE: openpilot-base-cl
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_GHCR_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
DOCKER_HUB_LOGIN: docker login -u adeebshihadeh -p ${{ secrets.DOCKER_HUB_PAT }}
BUILD: selfdrive/test/docker_build.sh base BUILD: selfdrive/test/docker_build.sh base
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c
@ -76,6 +77,11 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
# login only on arm machines, due to buildjet rate limits
- name: Setup docker
if: contains(runner.name, 'buildjet')
run: |
$DOCKER_HUB_LOGIN
- uses: ./.github/workflows/setup-with-retry - uses: ./.github/workflows/setup-with-retry
with: with:
cache_key_prefix: scons_${{ matrix.arch }} cache_key_prefix: scons_${{ matrix.arch }}
@ -190,7 +196,12 @@ jobs:
run: | run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV" echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV"
$DOCKER_LOGIN $DOCKER_GHCR_LOGIN
# login only on arm machines, due to buildjet rate limits
- name: Additional setup for buildjet
if: contains(runner.name, 'buildjet')
run: |
$DOCKER_HUB_LOGIN
- uses: ./.github/workflows/setup-with-retry - uses: ./.github/workflows/setup-with-retry
with: with:
git-lfs: false git-lfs: false
@ -211,7 +222,7 @@ jobs:
submodules: false submodules: false
- name: Setup docker - name: Setup docker
run: | run: |
$DOCKER_LOGIN $DOCKER_GHCR_LOGIN
- name: Merge x64 and arm64 tags - name: Merge x64 and arm64 tags
run: | run: |
export PUSH_IMAGE=true export PUSH_IMAGE=true
@ -264,7 +275,6 @@ jobs:
run: | run: |
${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \ ${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \
$PYTEST -n auto --dist=loadscope --timeout 30 -o cpp_files=test_* && \ $PYTEST -n auto --dist=loadscope --timeout 30 -o cpp_files=test_* && \
selfdrive/locationd/test/_test_locationd_lib.py && \
./selfdrive/ui/tests/create_test_translations.sh && \ ./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
./selfdrive/ui/tests/test_translations.py && \ ./selfdrive/ui/tests/test_translations.py && \

@ -13,7 +13,7 @@ concurrency:
env: env:
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
CL_BASE_IMAGE: openpilot-base-cl CL_BASE_IMAGE: openpilot-base-cl
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_GHCR_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: selfdrive/test/docker_build.sh base BUILD: selfdrive/test/docker_build.sh base
@ -58,7 +58,7 @@ jobs:
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
run: | run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
$DOCKER_LOGIN $DOCKER_GHCR_LOGIN
- name: Build and push sim image - name: Build and push sim image
run: | run: |
selfdrive/test/docker_build.sh sim selfdrive/test/docker_build.sh sim
@ -78,7 +78,7 @@ jobs:
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
run: | run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
$DOCKER_LOGIN $DOCKER_GHCR_LOGIN
- name: Build and push docs image - name: Build and push docs image
run: | run: |
selfdrive/test/docker_build.sh docs selfdrive/test/docker_build.sh docs

@ -38,7 +38,7 @@ repos:
args: ['--explicit-package-bases'] args: ['--explicit-package-bases']
exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)'
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.287 rev: v0.0.288
hooks: hooks:
- id: ruff - id: ruff
exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)'

@ -39,13 +39,17 @@ Running on a dedicated device in a car
------ ------
To use openpilot in a car, you need four things To use openpilot in a car, you need four things
* A supported device to run this software: a [comma 3X](https://comma.ai/shop/comma-3x) or comma three. 1. **Supported Device:** A comma 3/3X. You can purchase these devices from (https://comma.ai/shop/comma-3x)
* This software. The setup procedure of the comma 3/3X allows the user to enter a URL for custom software.
The URL, openpilot.comma.ai will install the release version of openpilot. To install openpilot master, you can use installer.comma.ai/commaai/master, and replacing commaai with another GitHub username can install a fork. 2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software.
* One of [the 250+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run openpilot. To install the release version of openpilot, use the URL `openpilot.comma.ai`.
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car. To install openpilot master (for more advanced users), use the URL `installer.comma.ai/commaai/master`. You can replace "commaai" with another GitHub username to install a fork.
We have detailed instructions for [how to mount the device in a car](https://comma.ai/setup). 3. **Supported Car:** Ensure that you have one of [the 250+ supported cars](docs/CARS.md). openpilot supports a wide range of car makes including Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and many more.
If your car is not officially listed as supported but has adaptive cruise control and lane-keeping assist, it's likely capable of running openpilot.
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car.
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup).
Running on PC Running on PC
------ ------

@ -1,7 +1,9 @@
Version 0.9.5 (202X-XX-XX) Version 0.9.5 (202X-XX-XX)
======================== ========================
* Hyundai Azera 2022 support thanks to sunnyhaibin!
* Hyundai Ioniq 6 2023 support thanks to sunnyhaibin, alamo3, and sshane! * Hyundai Ioniq 6 2023 support thanks to sunnyhaibin, alamo3, and sshane!
* Hyundai Kona Electric 2023 (Korean version) support thanks to sunnyhaibin and haram-KONA! * Hyundai Kona Electric 2023 (Korean version) support thanks to sunnyhaibin and haram-KONA!
* Kia K8 Hybrid (with HDA II) 2023 support thanks to sunnyhaibin!
* Kia Sorento Hybrid 2023 support thanks to sunnyhaibin! * Kia Sorento Hybrid 2023 support thanks to sunnyhaibin!
* Lexus IS 2023 support thanks to L3R5! * Lexus IS 2023 support thanks to L3R5!

@ -244,18 +244,6 @@ def progress_function(node):
if os.environ.get('SCONS_PROGRESS'): if os.environ.get('SCONS_PROGRESS'):
Progress(progress_function, interval=node_interval) Progress(progress_function, interval=node_interval)
SHARED = False
# TODO: this can probably be removed
def abspath(x):
if arch == 'aarch64':
pth = os.path.join("/data/pythonpath", x[0].path)
env.Depends(pth, x)
return File(pth)
else:
# rpath works elsewhere
return x[0].path.rsplit("/", 1)[1][:-3]
# Cython build environment # Cython build environment
py_include = sysconfig.get_paths()['include'] py_include = sysconfig.get_paths()['include']
envCython = env.Clone() envCython = env.Clone()
@ -337,34 +325,35 @@ if GetOption("clazy"):
qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0] qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0]
qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks) qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks)
Export('env', 'qt_env', 'arch', 'real_arch', 'SHARED') Export('env', 'qt_env', 'arch', 'real_arch')
# Build common module
SConscript(['common/SConscript']) SConscript(['common/SConscript'])
Import('_common', '_gpucommon') Import('_common', '_gpucommon')
if SHARED: common = [_common, 'json11']
common, gpucommon = abspath(common), abspath(gpucommon) gpucommon = [_gpucommon]
else:
common = [_common, 'json11']
gpucommon = [_gpucommon]
Export('common', 'gpucommon') Export('common', 'gpucommon')
# cereal and messaging are shared with the system # Build cereal and messaging
SConscript(['cereal/SConscript']) SConscript(['cereal/SConscript'])
if SHARED:
cereal = abspath([File('cereal/libcereal_shared.so')]) cereal = [File('#cereal/libcereal.a')]
messaging = abspath([File('cereal/libmessaging_shared.so')]) messaging = [File('#cereal/libmessaging.a')]
else: visionipc = [File('#cereal/libvisionipc.a')]
cereal = [File('#cereal/libcereal.a')]
messaging = [File('#cereal/libmessaging.a')]
visionipc = [File('#cereal/libvisionipc.a')]
messaging_python = [File('#cereal/messaging/messaging_pyx.so')] messaging_python = [File('#cereal/messaging/messaging_pyx.so')]
Export('cereal', 'messaging', 'messaging_python', 'visionipc') Export('cereal', 'messaging', 'messaging_python', 'visionipc')
# Build rednose library and ekf models # Build other submodules
SConscript([
'body/board/SConscript',
'opendbc/can/SConscript',
'panda/SConscript',
])
# Build rednose library and ekf models
rednose_deps = [ rednose_deps = [
"#selfdrive/locationd/models/constants.py", "#selfdrive/locationd/models/constants.py",
"#selfdrive/locationd/models/gnss_helpers.py", "#selfdrive/locationd/models/gnss_helpers.py",
@ -406,15 +395,6 @@ if arch != "Darwin":
]) ])
# Build openpilot # Build openpilot
# build submodules
SConscript([
'body/board/SConscript',
'cereal/SConscript',
'opendbc/can/SConscript',
'panda/SConscript',
])
SConscript(['third_party/SConscript']) SConscript(['third_party/SConscript'])
SConscript(['selfdrive/boardd/SConscript']) SConscript(['selfdrive/boardd/SConscript'])

@ -1,9 +1,4 @@
Import('env', 'envCython', 'arch', 'SHARED') Import('env', 'envCython', 'arch')
if SHARED:
fxn = env.SharedLibrary
else:
fxn = env.Library
common_libs = [ common_libs = [
'params.cc', 'params.cc',
@ -18,13 +13,13 @@ common_libs = [
if arch != "Darwin": if arch != "Darwin":
common_libs.append('gpio.cc') common_libs.append('gpio.cc')
_common = fxn('common', common_libs, LIBS="json11") _common = env.Library('common', common_libs, LIBS="json11")
files = [ files = [
'clutil.cc', 'clutil.cc',
] ]
_gpucommon = fxn('gpucommon', files) _gpucommon = env.Library('gpucommon', files)
Export('_common', '_gpucommon') Export('_common', '_gpucommon')
if GetOption('extras'): if GetOption('extras'):

@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 260 Supported Cars # 262 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video| |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@ -71,6 +71,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Passport 2019-23">Buy Here</a></sub></details>|| |Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Passport 2019-23">Buy Here</a></sub></details>||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Pilot 2016-22">Buy Here</a></sub></details>|| |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Pilot 2016-22">Buy Here</a></sub></details>||
|Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Ridgeline 2017-23">Buy Here</a></sub></details>|| |Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Ridgeline 2017-23">Buy Here</a></sub></details>||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera 2022">Buy Here</a></sub></details>||
|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-19">Buy Here</a></sub></details>|| |Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-19">Buy Here</a></sub></details>||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra GT 2017-19">Buy Here</a></sub></details>|| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra GT 2017-19">Buy Here</a></sub></details>||
@ -119,6 +120,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Forte 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2023">Buy Here</a></sub></details>|| |Kia|Forte 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2023">Buy Here</a></sub></details>||
|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K5 2021-22">Buy Here</a></sub></details>|| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K5 2021-22">Buy Here</a></sub></details>||
|Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K5 Hybrid 2020">Buy Here</a></sub></details>|| |Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K5 Hybrid 2020">Buy Here</a></sub></details>||
|Kia|K8 Hybrid (with HDA II) 2023[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K8 Hybrid (with HDA II) 2023">Buy Here</a></sub></details>||
|Kia|Niro EV 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Kia|Niro EV 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Kia|Niro EV 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2020">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Kia|Niro EV 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2020">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Kia|Niro EV 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2021">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Kia|Niro EV 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2021">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|

@ -1 +1 @@
Subproject commit 8d2f614ce3f323c65de802b2d5f29143de9c427b Subproject commit 4ab347baefb7473771ada0723c969c50d0c28d01

@ -1 +1 @@
Subproject commit 8e8aa5acf6ab0e45d91e72beaab74e69810448cd Subproject commit f6603239690ccee80f19e8bc5cf3ba03123f3498

191
poetry.lock generated

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
@ -1595,13 +1595,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"]
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.5.27" version = "2.5.28"
description = "File identification library for Python" description = "File identification library for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"}, {file = "identify-2.5.28-py2.py3-none-any.whl", hash = "sha256:87816de144bf46d161bd5b3e8f5596b16cade3b80be537087334b26bc5c177f3"},
{file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"}, {file = "identify-2.5.28.tar.gz", hash = "sha256:94bb59643083ebd60dc996d043497479ee554381fbc5307763915cda49b0e78f"},
] ]
[package.extras] [package.extras]
@ -2144,6 +2144,16 @@ files = [
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@ -2178,52 +2188,58 @@ files = [
[[package]] [[package]]
name = "matplotlib" name = "matplotlib"
version = "3.7.2" version = "3.7.3"
description = "Python plotting package" description = "Python plotting package"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de"}, {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:085c33b27561d9c04386789d5aa5eb4a932ddef43cfcdd0e01735f9a6e85ce0c"},
{file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d"}, {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c568e80e1c17f68a727f30f591926751b97b98314d8e59804f54f86ae6fa6a22"},
{file = "matplotlib-3.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1"}, {file = "matplotlib-3.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7baf98c5ad59c5c4743ea884bb025cbffa52dacdfdac0da3e6021a285a90377e"},
{file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603"}, {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236024f582e40dac39bca592258888b38ae47a9fed7b8de652d68d3d02d47d2b"},
{file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201"}, {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12b4f6795efea037ce2d41e7c417ad8bd02d5719c6ad4a8450a0708f4a1cfb89"},
{file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8"}, {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b2136cc6c5415b78977e0e8c608647d597204b05b1d9089ccf513c7d913733"},
{file = "matplotlib-3.7.2-cp310-cp310-win32.whl", hash = "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a"}, {file = "matplotlib-3.7.3-cp310-cp310-win32.whl", hash = "sha256:122dcbf9be0086e2a95d9e5e0632dbf3bd5b65eaa68c369363310a6c87753059"},
{file = "matplotlib-3.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1"}, {file = "matplotlib-3.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4aab27d9e33293389e3c1d7c881d414a72bdfda0fedc3a6bf46c6fa88d9b8015"},
{file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544"}, {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d5adc743de91e8e0b13df60deb1b1c285b8effea3d66223afceb14b63c9b05de"},
{file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e"}, {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:55de4cf7cd0071b8ebf203981b53ab64f988a0a1f897a2dff300a1124e8bcd8b"},
{file = "matplotlib-3.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64"}, {file = "matplotlib-3.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac03377fd908aaee2312d0b11735753e907adb6f4d1d102de5e2425249693f6c"},
{file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24"}, {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755bafc10a46918ce9a39980009b54b02dd249594e5adf52f9c56acfddb5d0b7"},
{file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d"}, {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a6094c6f8e8d18db631754df4fe9a34dec3caf074f6869a7db09f18f9b1d6b2"},
{file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4"}, {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:272dba2f1b107790ed78ebf5385b8d14b27ad9e90419de340364b49fe549a993"},
{file = "matplotlib-3.7.2-cp311-cp311-win32.whl", hash = "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc"}, {file = "matplotlib-3.7.3-cp311-cp311-win32.whl", hash = "sha256:591c123bed1cb4b9996fb60b41a6d89c2ec4943244540776c5f1283fb6960a53"},
{file = "matplotlib-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e"}, {file = "matplotlib-3.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:3bf3a178c6504694cee8b88b353df0051583f2f6f8faa146f67115c27c856881"},
{file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7"}, {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:edf54cac8ee3603f3093616b40a931e8c063969756a4d78a86e82c2fea9659f7"},
{file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391"}, {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:91e36a85ea639a1ba9f91427041eac064b04829945fe331a92617b6cb21d27e5"},
{file = "matplotlib-3.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca"}, {file = "matplotlib-3.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:caf5eaaf7c68f8d7df269dfbcaf46f48a70ff482bfcebdcc97519671023f2a7d"},
{file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9"}, {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74bf57f505efea376097e948b7cdd87191a7ce8180616390aef496639edf601f"},
{file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273"}, {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee152a88a0da527840a426535514b6ed8ac4240eb856b1da92cf48124320e346"},
{file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117"}, {file = "matplotlib-3.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:67a410a9c9e07cbc83581eeea144bbe298870bf0ac0ee2f2e10a015ab7efee19"},
{file = "matplotlib-3.7.2-cp38-cp38-win32.whl", hash = "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13"}, {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:259999c05285cb993d7f2a419cea547863fa215379eda81f7254c9e932963729"},
{file = "matplotlib-3.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676"}, {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3f4e7fd5a6157e1d018ce2166ec8e531a481dd4a36f035b5c23edfe05a25419a"},
{file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608"}, {file = "matplotlib-3.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:faa3d12d8811d08d14080a8b7b9caea9a457dc495350166b56df0db4b9909ef5"},
{file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256"}, {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:336e88900c11441e458da01c8414fc57e04e17f9d3bb94958a76faa2652bcf6b"},
{file = "matplotlib-3.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f"}, {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:12f4c0dd8aa280d796c8772ea8265a14f11a04319baa3a16daa5556065e8baea"},
{file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b"}, {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1990955b11e7918d256cf3b956b10997f405b7917a3f1c7d8e69c1d15c7b1930"},
{file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e"}, {file = "matplotlib-3.7.3-cp38-cp38-win32.whl", hash = "sha256:e78707b751260b42b721507ad7aa60fe4026d7f51c74cca6b9cd8b123ebb633a"},
{file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f"}, {file = "matplotlib-3.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:e594ee43c59ea39ca5c6244667cac9d017a3527febc31f5532ad9135cf7469ec"},
{file = "matplotlib-3.7.2-cp39-cp39-win32.whl", hash = "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20"}, {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6eaa1cf0e94c936a26b78f6d756c5fbc12e0a58c8a68b7248a2a31456ce4e234"},
{file = "matplotlib-3.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e"}, {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0a97af9d22e8ebedc9f00b043d9bbd29a375e9e10b656982012dded44c10fd77"},
{file = "matplotlib-3.7.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0"}, {file = "matplotlib-3.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f9c6c16597af660433ab330b59ee2934b832ee1fabcaf5cbde7b2add840f31e"},
{file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b"}, {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7240259b4b9cbc62381f6378cff4d57af539162a18e832c1e48042fabc40b6b"},
{file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c"}, {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747c6191d2e88ae854809e69aa358dbf852ff1a5738401b85c1cc9012309897a"},
{file = "matplotlib-3.7.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2"}, {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec726b08a5275d827aa91bb951e68234a4423adb91cf65bc0fcdc0f2777663f7"},
{file = "matplotlib-3.7.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd"}, {file = "matplotlib-3.7.3-cp39-cp39-win32.whl", hash = "sha256:40e3b9b450c6534f07278310c4e34caff41c2a42377e4b9d47b0f8d3ac1083a2"},
{file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07"}, {file = "matplotlib-3.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfc118642903a23e309b1da32886bb39a4314147d013e820c86b5fb4cb2e36d0"},
{file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d"}, {file = "matplotlib-3.7.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:165c8082bf8fc0360c24aa4724a22eaadbfd8c28bf1ccf7e94d685cad48261e4"},
{file = "matplotlib-3.7.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11"}, {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebd8470cc2a3594746ff0513aecbfa2c55ff6f58e6cef2efb1a54eb87c88ffa2"},
{file = "matplotlib-3.7.2.tar.gz", hash = "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b"}, {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7153453669c9672b52095119fd21dd032d19225d48413a2871519b17db4b0fde"},
{file = "matplotlib-3.7.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:498a08267dc69dd8f24c4b5d7423fa584d7ce0027ba71f7881df05fc09b89bb7"},
{file = "matplotlib-3.7.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48999c4b19b5a0c058c9cd828ff6fc7748390679f6cf9a2ad653a3e802c87d3"},
{file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22d65d18b4ee8070a5fea5761d59293f1f9e2fac37ec9ce090463b0e629432fd"},
{file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c40cde976c36693cc0767e27cf5f443f91c23520060bd9496678364adfafe9c"},
{file = "matplotlib-3.7.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:39018a2b17592448fbfdf4b8352955e6c3905359939791d4ff429296494d1a0c"},
{file = "matplotlib-3.7.3.tar.gz", hash = "sha256:f09b3dd6bdeb588de91f853bbb2d6f0ff8ab693485b0c49035eaa510cb4f142e"},
] ]
[package.dependencies] [package.dependencies]
@ -2231,11 +2247,12 @@ contourpy = ">=1.0.1"
cycler = ">=0.10" cycler = ">=0.10"
fonttools = ">=4.22.0" fonttools = ">=4.22.0"
kiwisolver = ">=1.0.1" kiwisolver = ">=1.0.1"
numpy = ">=1.20" numpy = ">=1.20,<2"
packaging = ">=20.0" packaging = ">=20.0"
pillow = ">=6.2.0" pillow = ">=6.2.0"
pyparsing = ">=2.3.1,<3.1" pyparsing = ">=2.3.1"
python-dateutil = ">=2.7" python-dateutil = ">=2.7"
setuptools_scm = ">=7"
[[package]] [[package]]
name = "mdit-py-plugins" name = "mdit-py-plugins"
@ -2766,14 +2783,7 @@ files = [
] ]
[package.dependencies] [package.dependencies]
numpy = [ numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""}
{version = ">=1.21.2", markers = "python_version >= \"3.10\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\""},
{version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""},
{version = ">=1.17.0", markers = "python_version >= \"3.7\""},
{version = ">=1.17.3", markers = "python_version >= \"3.8\""},
]
[[package]] [[package]]
name = "opencv-python-headless" name = "opencv-python-headless"
@ -2792,14 +2802,7 @@ files = [
] ]
[package.dependencies] [package.dependencies]
numpy = [ numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""}
{version = ">=1.21.2", markers = "python_version >= \"3.10\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\""},
{version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""},
{version = ">=1.17.0", markers = "python_version >= \"3.7\""},
{version = ">=1.17.3", markers = "python_version >= \"3.8\""},
]
[[package]] [[package]]
name = "packaging" name = "packaging"
@ -3623,13 +3626,13 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"]
[[package]] [[package]]
name = "pyparsing" name = "pyparsing"
version = "3.0.9" version = "3.1.1"
description = "pyparsing module - Classes and methods to define and execute parsing grammars" description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false optional = false
python-versions = ">=3.6.8" python-versions = ">=3.6.8"
files = [ files = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"},
] ]
[package.extras] [package.extras]
@ -3949,6 +3952,7 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@ -3956,8 +3960,15 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@ -3974,6 +3985,7 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@ -3981,6 +3993,7 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@ -4171,9 +4184,9 @@ setuptools = "*"
[[package]] [[package]]
name = "sconscontrib" name = "sconscontrib"
version = "1.0" version = "1.0"
description = "" description = "Contributed builders and other useful logic for the SCons build system.,"
optional = false optional = false
python-versions = ">=3.6, <4" python-versions = "<4,>=3.6"
files = [] files = []
develop = false develop = false
@ -4338,19 +4351,39 @@ test = ["pytest"]
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "68.2.0" version = "68.2.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"}, {file = "setuptools-68.2.1-py3-none-any.whl", hash = "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae"},
{file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"}, {file = "setuptools-68.2.1.tar.gz", hash = "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1"},
] ]
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "setuptools-scm"
version = "7.1.0"
description = "the blessed package to manage your versions by scm tags"
optional = false
python-versions = ">=3.7"
files = [
{file = "setuptools_scm-7.1.0-py3-none-any.whl", hash = "sha256:73988b6d848709e2af142aa48c986ea29592bbcfca5375678064708205253d8e"},
{file = "setuptools_scm-7.1.0.tar.gz", hash = "sha256:6c508345a771aad7d56ebff0e70628bf2b0ec7573762be9960214730de278f27"},
]
[package.dependencies]
packaging = ">=20.0"
setuptools = "*"
typing-extensions = "*"
[package.extras]
test = ["pytest (>=6.2)", "virtualenv (>20)"]
toml = ["setuptools (>=42)"]
[[package]] [[package]]
name = "shapely" name = "shapely"
@ -4899,13 +4932,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.24.4" version = "20.24.5"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "virtualenv-20.24.4-py3-none-any.whl", hash = "sha256:29c70bb9b88510f6414ac3e55c8b413a1f96239b6b789ca123437d5e892190cb"}, {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"},
{file = "virtualenv-20.24.4.tar.gz", hash = "sha256:772b05bfda7ed3b8ecd16021ca9716273ad9f4467c801f27e83ac73430246dca"}, {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"},
] ]
[package.dependencies] [package.dependencies]
@ -4919,13 +4952,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[[package]] [[package]]
name = "websocket-client" name = "websocket-client"
version = "1.6.2" version = "1.6.3"
description = "WebSocket client for Python with low level API options" description = "WebSocket client for Python with low level API options"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "websocket-client-1.6.2.tar.gz", hash = "sha256:53e95c826bf800c4c465f50093a8c4ff091c7327023b10bfaff40cf1ef170eaa"}, {file = "websocket-client-1.6.3.tar.gz", hash = "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f"},
{file = "websocket_client-1.6.2-py3-none-any.whl", hash = "sha256:ce54f419dfae71f4bdba69ebe65bf7f0a93fe71bc009ad3a010aacc3eebad537"}, {file = "websocket_client-1.6.3-py3-none-any.whl", hash = "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03"},
] ]
[package.extras] [package.extras]

@ -98,7 +98,7 @@ class CarState(CarStateBase):
ret.leftBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 1 ret.leftBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 1
ret.rightBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 2 ret.rightBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 2
ret.parkingBrake = pt_cp.vl["VehicleIgnitionAlt"]["ParkBrake"] == 1 ret.parkingBrake = pt_cp.vl["BCMGeneralPlatformStatus"]["ParkBrakeSwActive"] == 1
ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0 ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1 ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or
@ -135,7 +135,7 @@ class CarState(CarStateBase):
("PSCMStatus", 10), ("PSCMStatus", 10),
("ESPStatus", 10), ("ESPStatus", 10),
("BCMDoorBeltStatus", 10), ("BCMDoorBeltStatus", 10),
("VehicleIgnitionAlt", 10), ("BCMGeneralPlatformStatus", 10),
("EBCMWheelSpdFront", 20), ("EBCMWheelSpdFront", 20),
("EBCMWheelSpdRear", 20), ("EBCMWheelSpdRear", 20),
("EBCMFrictionBrakeStatus", 20), ("EBCMFrictionBrakeStatus", 20),

@ -37,7 +37,8 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020,
CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022,
CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022,
CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED): CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED,
CAR.AZERA_6TH_GEN):
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
values["CF_Lkas_LdwsOpt_USM"] = 2 values["CF_Lkas_LdwsOpt_USM"] = 2

@ -64,7 +64,11 @@ class CarInterface(CarInterfaceBase):
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): if candidate == CAR.AZERA_6TH_GEN:
ret.mass = 1540. # average
ret.wheelbase = 2.885
ret.steerRatio = 14.5
elif candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022):
ret.mass = 3982. * CV.LB_TO_KG ret.mass = 3982. * CV.LB_TO_KG
ret.wheelbase = 2.766 ret.wheelbase = 2.766
# Values from optimizer # Values from optimizer
@ -211,6 +215,10 @@ class CarInterface(CarInterfaceBase):
ret.mass = 2087. ret.mass = 2087.
ret.wheelbase = 3.09 ret.wheelbase = 3.09
ret.steerRatio = 14.23 ret.steerRatio = 14.23
elif candidate == CAR.KIA_K8_HEV_1ST_GEN:
ret.mass = 1630. # https://carprices.ae/brands/kia/2023/k8/1.6-turbo-hybrid
ret.wheelbase = 2.895
ret.steerRatio = 13.27 # guesstimate from K5 platform
# Genesis # Genesis
elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN: elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN:

@ -69,6 +69,7 @@ class HyundaiFlags(IntFlag):
class CAR: class CAR:
# Hyundai # Hyundai
AZERA_6TH_GEN = "HYUNDAI AZERA 6TH GEN"
ELANTRA = "HYUNDAI ELANTRA 2017" ELANTRA = "HYUNDAI ELANTRA 2017"
ELANTRA_2021 = "HYUNDAI ELANTRA 2021" ELANTRA_2021 = "HYUNDAI ELANTRA 2021"
ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021" ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021"
@ -104,6 +105,7 @@ class CAR:
KIA_FORTE = "KIA FORTE E 2018 & GT 2021" KIA_FORTE = "KIA FORTE E 2018 & GT 2021"
KIA_K5_2021 = "KIA K5 2021" KIA_K5_2021 = "KIA K5 2021"
KIA_K5_HEV_2020 = "KIA K5 HYBRID 2020" KIA_K5_HEV_2020 = "KIA K5 HYBRID 2020"
KIA_K8_HEV_1ST_GEN = "KIA K8 HYBRID 1ST GEN"
KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_EV = "KIA NIRO EV 2020"
KIA_NIRO_EV_2ND_GEN = "KIA NIRO EV 2ND GEN" KIA_NIRO_EV_2ND_GEN = "KIA NIRO EV 2ND GEN"
KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019"
@ -152,6 +154,7 @@ class HyundaiCarInfo(CarInfo):
CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
CAR.AZERA_6TH_GEN: HyundaiCarInfo("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])),
CAR.ELANTRA: [ CAR.ELANTRA: [
HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])),
HyundaiCarInfo("Hyundai Elantra GT 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), HyundaiCarInfo("Hyundai Elantra GT 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])),
@ -217,6 +220,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
], ],
CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", car_parts=CarParts.common([CarHarness.hyundai_a])),
CAR.KIA_K5_HEV_2020: HyundaiCarInfo("Kia K5 Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_K5_HEV_2020: HyundaiCarInfo("Kia K5 Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_a])),
CAR.KIA_K8_HEV_1ST_GEN: HyundaiCarInfo("Kia K8 Hybrid (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])),
CAR.KIA_NIRO_EV: [ CAR.KIA_NIRO_EV: [
HyundaiCarInfo("Kia Niro EV 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])), HyundaiCarInfo("Kia Niro EV 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])),
HyundaiCarInfo("Kia Niro EV 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_f])), HyundaiCarInfo("Kia Niro EV 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_f])),
@ -523,6 +527,23 @@ FW_QUERY_CONFIG = FwQueryConfig(
) )
FW_VERSIONS = { FW_VERSIONS = {
CAR.AZERA_6TH_GEN: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00IG__ SCC F-CU- 1.00 1.00 99110-G8100 ',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00IG MDPS C 1.00 1.02 56310G8510\x00 4IGSC103',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00IG MFC AT MES LHD 1.00 1.04 99211-G8100 200511',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x00bcsh8p54 U912\x00\x00\x00\x00\x00\x00SIG0M35MH0\xa4 |.',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x81641KA051\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
CAR.HYUNDAI_GENESIS: { CAR.HYUNDAI_GENESIS: {
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DH LKAS 1.1 -150210', b'\xf1\x00DH LKAS 1.1 -150210',
@ -1940,6 +1961,14 @@ FW_VERSIONS = {
b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ',
], ],
}, },
CAR.KIA_K8_HEV_1ST_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.03 99211-L8000 210907',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00GL3_ RDR ----- 1.00 1.02 99110-L8000 ',
],
},
} }
CHECKSUM = { CHECKSUM = {
@ -1961,7 +1990,7 @@ CAN_GEARS = {
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN,
CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN,
CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN,
CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN} CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN}
# The radar does SCC on these cars when HDA I, rather than the camera # The radar does SCC on these cars when HDA I, rather than the camera
CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80,
@ -1974,7 +2003,7 @@ CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ,
CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN,
CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN,
CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_OPTIMA_H} CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_OPTIMA_H, CAR.KIA_K8_HEV_1ST_GEN}
EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022, EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022,
CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KONA_EV_2ND_GEN} CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KONA_EV_2ND_GEN}
@ -1990,6 +2019,7 @@ UNSUPPORTED_LONGITUDINAL_CAR = LEGACY_SAFETY_MODE_CAR | {CAR.KIA_NIRO_PHEV, CAR.
# If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. # If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points.
# If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py # If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py
DBC = { DBC = {
CAR.AZERA_6TH_GEN: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None),
@ -2050,4 +2080,5 @@ DBC = {
CAR.KIA_CARNIVAL_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_CARNIVAL_4TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SORENTO_HEV_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SORENTO_HEV_4TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.KONA_EV_2ND_GEN: dbc_dict('hyundai_canfd', None), CAR.KONA_EV_2ND_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_K8_HEV_1ST_GEN: dbc_dict('hyundai_canfd', None),
} }

@ -25,7 +25,6 @@ non_tested_cars = [
HONDA.ODYSSEY_CHN, HONDA.ODYSSEY_CHN,
VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter
TOYOTA.RAV4_TSS2_2023, TOYOTA.RAV4_TSS2_2023,
TOYOTA.RAV4H_TSS2_2023,
SUBARU.FORESTER_HYBRID, SUBARU.FORESTER_HYBRID,
] ]
@ -94,6 +93,7 @@ routes = [
CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E), CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E),
CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022), CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022),
CarTestRoute("87d7f06ade479c2e|2023-09-11--23-30-11", HYUNDAI.AZERA_6TH_GEN),
CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS),
CarTestRoute("b5d6dc830ad63071|2022-12-12--21-28-25", HYUNDAI.GENESIS_GV60_EV_1ST_GEN, segment=12), CarTestRoute("b5d6dc830ad63071|2022-12-12--21-28-25", HYUNDAI.GENESIS_GV60_EV_1ST_GEN, segment=12),
CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70),
@ -146,6 +146,7 @@ routes = [
CarTestRoute("9b25e8c1484a1b67|2023-04-13--10-41-45", HYUNDAI.KIA_EV6), CarTestRoute("9b25e8c1484a1b67|2023-04-13--10-41-45", HYUNDAI.KIA_EV6),
CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021), CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021),
CarTestRoute("c58dfc9fc16590e0|2023-01-14--13-51-48", HYUNDAI.KIA_K5_HEV_2020), CarTestRoute("c58dfc9fc16590e0|2023-01-14--13-51-48", HYUNDAI.KIA_K5_HEV_2020),
CarTestRoute("78ad5150de133637|2023-09-13--16-15-57", HYUNDAI.KIA_K8_HEV_1ST_GEN),
CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV),
CarTestRoute("b153671049a867b3|2023-04-05--10-00-30", HYUNDAI.KIA_NIRO_EV_2ND_GEN), CarTestRoute("b153671049a867b3|2023-04-05--10-00-30", HYUNDAI.KIA_NIRO_EV_2ND_GEN),
CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV),
@ -182,6 +183,7 @@ routes = [
CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022), CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022),
CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4H_TSS2), CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4H_TSS2),
CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4H_TSS2_2022), CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4H_TSS2_2022),
CarTestRoute("49e041422a032273|2023-09-14--09-21-32", TOYOTA.RAV4H_TSS2_2023),
CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES), CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES),
CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2), CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2),
CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3), CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3),

@ -9,6 +9,7 @@ from parameterized import parameterized_class
from cereal import log, car from cereal import log, car
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.realtime import DT_CTRL from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.car.fingerprints import all_known_cars from openpilot.selfdrive.car.fingerprints import all_known_cars
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces
@ -16,6 +17,7 @@ from openpilot.selfdrive.car.gm.values import CAR as GM
from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH
from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI
from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute
from openpilot.selfdrive.controls.controlsd import Controls
from openpilot.selfdrive.test.openpilotci import get_url from openpilot.selfdrive.test.openpilotci import get_url
from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.logreader import LogReader
from openpilot.tools.lib.route import Route, SegmentName, RouteName from openpilot.tools.lib.route import Route, SegmentName, RouteName
@ -23,6 +25,7 @@ from openpilot.tools.lib.route import Route, SegmentName, RouteName
from panda.tests.libpanda import libpanda_py from panda.tests.libpanda import libpanda_py
from openpilot.selfdrive.test.helpers import SKIP_ENV_VAR from openpilot.selfdrive.test.helpers import SKIP_ENV_VAR
EventName = car.CarEvent.EventName
PandaType = log.PandaState.PandaType PandaType = log.PandaState.PandaType
SafetyModel = car.CarParams.SafetyModel SafetyModel = car.CarParams.SafetyModel
@ -163,9 +166,11 @@ class TestCarModelBase(unittest.TestCase):
del cls.can_msgs del cls.can_msgs
def setUp(self): def setUp(self):
self.CI = self.CarInterface(self.CP, self.CarController, self.CarState) self.CI = self.CarInterface(self.CP.copy(), self.CarController, self.CarState)
assert self.CI assert self.CI
Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled)
# TODO: check safetyModel is in release panda build # TODO: check safetyModel is in release panda build
self.safety = libpanda_py.libpanda self.safety = libpanda_py.libpanda
@ -315,6 +320,8 @@ class TestCarModelBase(unittest.TestCase):
controls_allowed_prev = False controls_allowed_prev = False
CS_prev = car.CarState.new_message() CS_prev = car.CarState.new_message()
checks = defaultdict(lambda: 0) checks = defaultdict(lambda: 0)
controlsd = Controls(CI=self.CI)
controlsd.initialized = True
for idx, can in enumerate(self.can_msgs): for idx, can in enumerate(self.can_msgs):
CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) CS = self.CI.update(CC, (can.as_builder().to_bytes(), ))
for msg in filter(lambda m: m.src in range(64), can.can): for msg in filter(lambda m: m.src in range(64), can.can):
@ -359,7 +366,10 @@ class TestCarModelBase(unittest.TestCase):
checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev() checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev()
else: else:
# Check for enable events on rising edge of controls allowed # Check for enable events on rising edge of controls allowed
button_enable = any(evt.enable for evt in CS.events) controlsd.update_events(CS)
controlsd.CS_prev = CS
button_enable = (any(evt.enable for evt in CS.events) and
not any(evt == EventName.pedalPressed for evt in controlsd.events.names))
mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev) mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev)
checks['controlsAllowed'] += mismatch checks['controlsAllowed'] += mismatch
controls_allowed_prev = self.safety.get_controls_allowed() controls_allowed_prev = self.safety.get_controls_allowed()

@ -12,10 +12,6 @@ SUBARU FORESTER 2022: [.nan, 3.0, .nan]
SUBARU OUTBACK 7TH GEN: [.nan, 3.0, .nan] SUBARU OUTBACK 7TH GEN: [.nan, 3.0, .nan]
SUBARU ASCENT 2023: [.nan, 3.0, .nan] SUBARU ASCENT 2023: [.nan, 3.0, .nan]
# Toyota LTA also has torque
TOYOTA RAV4 2023: [.nan, 3.0, .nan]
TOYOTA RAV4 HYBRID 2023: [.nan, 3.0, .nan]
# Tesla has high torque # Tesla has high torque
TESLA AP1 MODEL S: [.nan, 2.5, .nan] TESLA AP1 MODEL S: [.nan, 2.5, .nan]
TESLA AP2 MODEL S: [.nan, 2.5, .nan] TESLA AP2 MODEL S: [.nan, 2.5, .nan]
@ -59,6 +55,10 @@ LEXUS IS 2023: [2.0, 2.0, 0.1]
KIA SORENTO HYBRID 4TH GEN: [2.5, 2.5, 0.1] KIA SORENTO HYBRID 4TH GEN: [2.5, 2.5, 0.1]
HYUNDAI KONA ELECTRIC 2ND GEN: [2.5, 2.5, 0.1] HYUNDAI KONA ELECTRIC 2ND GEN: [2.5, 2.5, 0.1]
HYUNDAI IONIQ 6 2023: [2.5, 2.5, 0.1] HYUNDAI IONIQ 6 2023: [2.5, 2.5, 0.1]
HYUNDAI AZERA 6TH GEN: [1.8, 1.8, 0.1]
KIA K8 HYBRID 1ST GEN: [2.5, 2.5, 0.1]
TOYOTA RAV4 2023: [2.5, 2.5, 0.1]
TOYOTA RAV4 HYBRID 2023: [2.5, 2.5, 0.1]
# Dashcam or fallback configured as ideal car # Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0] mock: [10.0, 10, 0.0]

@ -27,7 +27,15 @@ class CarInterface(CarInterfaceBase):
if DBC[candidate]["pt"] == "toyota_new_mc_pt_generated": if DBC[candidate]["pt"] == "toyota_new_mc_pt_generated":
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE
if candidate in ANGLE_CONTROL_CAR: # Allow angle control cars with whitelisted EPSs to use torque control (made in Japan)
# So far only hybrid RAV4 2023 has been seen with this FW version
angle_car_torque_fw = any(fw.ecu == "eps" and fw.fwVersion == b'8965B42371\x00\x00\x00\x00\x00\x00' for fw in car_fw)
if candidate not in ANGLE_CONTROL_CAR or (angle_car_torque_fw and candidate == CAR.RAV4H_TSS2_2023):
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
ret.steerActuatorDelay = 0.12 # Default delay, Prius has larger delay
ret.steerLimitTimer = 0.4
else:
ret.dashcamOnly = True ret.dashcamOnly = True
ret.steerControlType = SteerControlType.angle ret.steerControlType = SteerControlType.angle
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA
@ -35,11 +43,6 @@ class CarInterface(CarInterfaceBase):
# LTA control can be more delayed and winds up more often # LTA control can be more delayed and winds up more often
ret.steerActuatorDelay = 0.25 ret.steerActuatorDelay = 0.25
ret.steerLimitTimer = 0.8 ret.steerLimitTimer = 0.8
else:
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
ret.steerActuatorDelay = 0.12 # Default delay, Prius has larger delay
ret.steerLimitTimer = 0.4
ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop
@ -121,21 +124,25 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.3 ret.steerRatio = 14.3
ret.tireStiffnessFactor = 0.7933 ret.tireStiffnessFactor = 0.7933
ret.mass = 3585. * CV.LB_TO_KG # Average between ICE and Hybrid ret.mass = 3585. * CV.LB_TO_KG # Average between ICE and Hybrid
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kiBP = [0.0] # Only specific EPS FW accept torque on 2023 RAV4, so they likely are all the same
ret.lateralTuning.pid.kpBP = [0.0] # TODO: revisit this disparity if there is a divide for 2023
ret.lateralTuning.pid.kpV = [0.6] if candidate not in (CAR.RAV4_TSS2_2023, CAR.RAV4H_TSS2_2023):
ret.lateralTuning.pid.kiV = [0.1] ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00007818594 ret.lateralTuning.pid.kiBP = [0.0]
ret.lateralTuning.pid.kpBP = [0.0]
# 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary. ret.lateralTuning.pid.kpV = [0.6]
# See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891 ret.lateralTuning.pid.kiV = [0.1]
for fw in car_fw: ret.lateralTuning.pid.kf = 0.00007818594
if fw.ecu == "eps" and (fw.fwVersion.startswith(b'\x02') or fw.fwVersion in [b'8965B42181\x00\x00\x00\x00\x00\x00']):
ret.lateralTuning.pid.kpV = [0.15] # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary.
ret.lateralTuning.pid.kiV = [0.05] # See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891
ret.lateralTuning.pid.kf = 0.00004 for fw in car_fw:
break if fw.ecu == "eps" and (fw.fwVersion.startswith(b'\x02') or fw.fwVersion in [b'8965B42181\x00\x00\x00\x00\x00\x00']):
ret.lateralTuning.pid.kpV = [0.15]
ret.lateralTuning.pid.kiV = [0.05]
ret.lateralTuning.pid.kf = 0.00004
break
elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2): elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2):
ret.wheelbase = 2.67 # Average between 2.70 for sedan and 2.64 for hatchback ret.wheelbase = 2.67 # Average between 2.70 for sedan and 2.64 for hatchback

@ -2389,7 +2389,8 @@ UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC}
# these cars have a radar which sends ACC messages instead of the camera # these cars have a radar which sends ACC messages instead of the camera
RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2, CAR.CHRH_TSS2} RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2, CAR.CHRH_TSS2}
# these cars use the Lane Tracing Assist (LTA) message for lateral control # these cars manufactured in U.S., Canada have EPSs that reject Lane Keep Assist (LKA, torque) messages and require
# Lane Tracing Assist (LTA, angle) to steer properly. cars manufactured in Japan still work with the older LKA messages which is detected
ANGLE_CONTROL_CAR = {CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023} ANGLE_CONTROL_CAR = {CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023}
EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.CHRH_TSS2, CAR.COROLLAH_TSS2, EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.CHRH_TSS2, CAR.COROLLAH_TSS2,

@ -7,8 +7,4 @@ locationd_sources = ["locationd.cc", "models/live_kf.cc", ekf_sym_cc]
lenv = env.Clone() lenv = env.Clone()
lenv["_LIBFLAGS"] += f' {libkf[0].get_labspath()}' lenv["_LIBFLAGS"] += f' {libkf[0].get_labspath()}'
locationd = lenv.Program("locationd", locationd_sources, LIBS=loc_libs + transformations) locationd = lenv.Program("locationd", locationd_sources, LIBS=loc_libs + transformations)
lenv.Depends(locationd, libkf) lenv.Depends(locationd, libkf)
if File("liblocationd.cc").exists():
liblocationd = lenv.SharedLibrary("liblocationd", ["liblocationd.cc"] + locationd_sources, LIBS=loc_libs + transformations)
lenv.Depends(liblocationd, libkf)

@ -1,41 +0,0 @@
#include "selfdrive/locationd/locationd.h"
extern "C" {
typedef Localizer* Localizer_t;
Localizer *localizer_init(bool has_ublox) {
return new Localizer(has_ublox ? LocalizerGnssSource::UBLOX : LocalizerGnssSource::QCOM);
}
void localizer_get_message_bytes(Localizer *localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid,
char *buff, size_t buff_size) {
MessageBuilder msg_builder;
kj::ArrayPtr<char> arr = localizer->get_message_bytes(msg_builder, inputsOK, sensorsOK, gpsOK, msgValid).asChars();
assert(buff_size >= arr.size());
memcpy(buff, arr.begin(), arr.size());
}
void localizer_handle_msg_bytes(Localizer *localizer, const char *data, size_t size) {
localizer->handle_msg_bytes(data, size);
}
void get_filter_internals(Localizer *localizer, double *state_buff, double *std_buff){
Eigen::VectorXd state = localizer->get_state();
memcpy(state_buff, state.data(), sizeof(double) * state.size());
Eigen::VectorXd stdev = localizer->get_stdev();
memcpy(std_buff, stdev.data(), sizeof(double) * stdev.size());
}
bool is_gps_ok(Localizer *localizer){
return localizer->is_gps_ok();
}
bool are_inputs_ok(Localizer *localizer){
return localizer->are_inputs_ok();
}
void observation_timings_invalid_reset(Localizer *localizer){
localizer->observation_timings_invalid_reset();
}
}

@ -1,109 +0,0 @@
#!/usr/bin/env python3
"""This test can't be run together with other locationd tests.
cffi.dlopen breaks the list of registered filters."""
import os
import random
import unittest
from cffi import FFI
import cereal.messaging as messaging
from cereal import log
from openpilot.common.ffi_wrapper import suffix
SENSOR_DECIMATION = 1
VISION_DECIMATION = 1
LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd' + suffix()))
class TestLocationdLib(unittest.TestCase):
def setUp(self):
header = '''typedef ...* Localizer_t;
Localizer_t localizer_init(bool has_ublox);
void localizer_get_message_bytes(Localizer_t localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid, char *buff, size_t buff_size);
void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t size);'''
self.ffi = FFI()
self.ffi.cdef(header)
self.lib = self.ffi.dlopen(LIBLOCATIOND_PATH)
self.localizer = self.lib.localizer_init(True) # default to ublox
self.buff_size = 2048
self.msg_buff = self.ffi.new(f'char[{self.buff_size}]')
def localizer_handle_msg(self, msg_builder):
bytstr = msg_builder.to_bytes()
self.lib.localizer_handle_msg_bytes(self.localizer, self.ffi.from_buffer(bytstr), len(bytstr))
def localizer_get_msg(self, t=0, inputsOK=True, sensorsOK=True, gpsOK=True, msgValid=True):
self.lib.localizer_get_message_bytes(self.localizer, inputsOK, sensorsOK, gpsOK, msgValid, self.ffi.addressof(self.msg_buff, 0), self.buff_size)
with log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8) as log_evt:
return log_evt
def test_liblocalizer(self):
msg = messaging.new_message('liveCalibration')
msg.liveCalibration.validBlocks = random.randint(1, 10)
msg.liveCalibration.rpyCalib = [random.random() / 10 for _ in range(3)]
self.localizer_handle_msg(msg)
liveloc = self.localizer_get_msg()
self.assertTrue(liveloc is not None)
@unittest.skip("temporarily disabled due to false positives")
def test_device_fell(self):
msg = messaging.new_message('accelerometer')
msg.accelerometer.sensor = 1
msg.accelerometer.timestamp = msg.logMonoTime
msg.accelerometer.type = 1
msg.accelerometer.init('acceleration')
msg.accelerometer.acceleration.v = [10.0, 0.0, 0.0] # zero with gravity
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertTrue(ret.liveLocationKalman.deviceStable)
msg = messaging.new_message('accelerometer')
msg.accelerometer.sensor = 1
msg.accelerometer.timestamp = msg.logMonoTime
msg.accelerometer.type = 1
msg.accelerometer.init('acceleration')
msg.accelerometer.acceleration.v = [50.1, 0.0, 0.0] # more than 40 m/s**2
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertFalse(ret.liveLocationKalman.deviceStable)
def test_posenet_spike(self):
for _ in range(SENSOR_DECIMATION):
msg = messaging.new_message('carState')
msg.carState.vEgo = 6.0 # more than 5 m/s
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertTrue(ret.liveLocationKalman.posenetOK)
for _ in range(20 * VISION_DECIMATION): # size of hist_old
msg = messaging.new_message('cameraOdometry')
msg.cameraOdometry.rot = [0.0, 0.0, 0.0]
msg.cameraOdometry.rotStd = [0.1, 0.1, 0.1]
msg.cameraOdometry.trans = [0.0, 0.0, 0.0]
msg.cameraOdometry.transStd = [2.0, 0.1, 0.1]
self.localizer_handle_msg(msg)
for _ in range(20 * VISION_DECIMATION): # size of hist_new
msg = messaging.new_message('cameraOdometry')
msg.cameraOdometry.rot = [0.0, 0.0, 0.0]
msg.cameraOdometry.rotStd = [1.0, 1.0, 1.0]
msg.cameraOdometry.trans = [0.0, 0.0, 0.0]
msg.cameraOdometry.transStd = [10.1, 0.1, 0.1] # more than 4 times larger
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertFalse(ret.liveLocationKalman.posenetOK)
if __name__ == "__main__":
unittest.main()

@ -0,0 +1,223 @@
#!/usr/bin/env python3
import unittest
import numpy as np
from collections import defaultdict
from enum import Enum
from openpilot.selfdrive.test.openpilotci import get_url
from openpilot.tools.lib.logreader import LogReader
from openpilot.selfdrive.test.process_replay.process_replay import replay_process_with_name
TEST_ROUTE, TEST_SEG_NUM = "ff2bd20623fcaeaa|2023-09-05--10-14-54", 4
GPS_MESSAGES = ['gpsLocationExternal', 'gpsLocation']
SELECT_COMPARE_FIELDS = {
'yaw_rate': ['angularVelocityCalibrated', 'value', 2],
'roll': ['orientationNED', 'value', 0],
'gps_flag': ['gpsOK'],
'inputs_flag': ['inputsOK'],
'sensors_flag': ['sensorsOK'],
}
JUNK_IDX = 100
class Scenario(Enum):
BASE = 'base'
GPS_OFF = 'gps_off'
GPS_OFF_MIDWAY = 'gps_off_midway'
GPS_ON_MIDWAY = 'gps_on_midway'
GPS_TUNNEL = 'gps_tunnel'
GYRO_OFF = 'gyro_off'
GYRO_SPIKE_MIDWAY = 'gyro_spike_midway'
ACCEL_OFF = 'accel_off'
ACCEL_SPIKE_MIDWAY = 'accel_spike_midway'
def get_select_fields_data(logs):
def get_nested_keys(msg, keys):
val = None
for key in keys:
val = getattr(msg if val is None else val, key) if isinstance(key, str) else val[key]
return val
llk = [x.liveLocationKalman for x in logs if x.which() == 'liveLocationKalman']
data = defaultdict(list)
for msg in llk:
for key, fields in SELECT_COMPARE_FIELDS.items():
data[key].append(get_nested_keys(msg, fields))
for key in data:
data[key] = np.array(data[key][JUNK_IDX:], dtype=float)
return data
def run_scenarios(scenario):
logs = list(LogReader(get_url(TEST_ROUTE, TEST_SEG_NUM)))
if scenario == Scenario.BASE:
pass
elif scenario == Scenario.GPS_OFF:
logs = sorted([x for x in logs if x.which() not in GPS_MESSAGES], key=lambda x: x.logMonoTime)
elif scenario == Scenario.GPS_OFF_MIDWAY:
non_gps = [x for x in logs if x.which() not in GPS_MESSAGES]
gps = [x for x in logs if x.which() in GPS_MESSAGES]
logs = sorted(non_gps + gps[: len(gps) // 2], key=lambda x: x.logMonoTime)
elif scenario == Scenario.GPS_ON_MIDWAY:
non_gps = [x for x in logs if x.which() not in GPS_MESSAGES]
gps = [x for x in logs if x.which() in GPS_MESSAGES]
logs = sorted(non_gps + gps[len(gps) // 2:], key=lambda x: x.logMonoTime)
elif scenario == Scenario.GPS_TUNNEL:
non_gps = [x for x in logs if x.which() not in GPS_MESSAGES]
gps = [x for x in logs if x.which() in GPS_MESSAGES]
logs = sorted(non_gps + gps[:len(gps) // 4] + gps[-len(gps) // 4:], key=lambda x: x.logMonoTime)
elif scenario == Scenario.GYRO_OFF:
logs = sorted([x for x in logs if x.which() != 'gyroscope'], key=lambda x: x.logMonoTime)
elif scenario == Scenario.GYRO_SPIKE_MIDWAY:
non_gyro = [x for x in logs if x.which() not in 'gyroscope']
gyro = [x for x in logs if x.which() in 'gyroscope']
temp = gyro[len(gyro) // 2].as_builder()
temp.gyroscope.gyroUncalibrated.v[0] += 3.0
gyro[len(gyro) // 2] = temp.as_reader()
logs = sorted(non_gyro + gyro, key=lambda x: x.logMonoTime)
elif scenario == Scenario.ACCEL_OFF:
logs = sorted([x for x in logs if x.which() != 'accelerometer'], key=lambda x: x.logMonoTime)
elif scenario == Scenario.ACCEL_SPIKE_MIDWAY:
non_accel = [x for x in logs if x.which() not in 'accelerometer']
accel = [x for x in logs if x.which() in 'accelerometer']
temp = accel[len(accel) // 2].as_builder()
temp.accelerometer.acceleration.v[0] += 10.0
accel[len(accel) // 2] = temp.as_reader()
logs = sorted(non_accel + accel, key=lambda x: x.logMonoTime)
replayed_logs = replay_process_with_name(name='locationd', lr=logs)
return get_select_fields_data(logs), get_select_fields_data(replayed_logs)
class TestLocationdScenarios(unittest.TestCase):
"""
Test locationd with different scenarios. In all these scenarios, we expect the following:
- locationd kalman filter should never go unstable (we care mostly about yaw_rate, roll, gpsOK, inputsOK, sensorsOK)
- faulty values should be ignored, with appropriate flags set
"""
def test_base(self):
"""
Test: unchanged log
Expected Result:
- yaw_rate: unchanged
- roll: unchanged
"""
orig_data, replayed_data = run_scenarios(Scenario.BASE)
self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)))
self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)))
def test_gps_off(self):
"""
Test: no GPS message for the entire segment
Expected Result:
- yaw_rate: unchanged
- roll:
- gpsOK: False
"""
orig_data, replayed_data = run_scenarios(Scenario.GPS_OFF)
self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)))
self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)))
self.assertTrue(np.all(replayed_data['gps_flag'] == 0.0))
def test_gps_off_midway(self):
"""
Test: no GPS message for the second half of the segment
Expected Result:
- yaw_rate: unchanged
- roll:
- gpsOK: True for the first half, False for the second half
"""
orig_data, replayed_data = run_scenarios(Scenario.GPS_OFF_MIDWAY)
self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)))
self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)))
self.assertTrue(np.diff(replayed_data['gps_flag'])[512] == -1.0)
def test_gps_on_midway(self):
"""
Test: no GPS message for the first half of the segment
Expected Result:
- yaw_rate: unchanged
- roll:
- gpsOK: False for the first half, True for the second half
"""
orig_data, replayed_data = run_scenarios(Scenario.GPS_ON_MIDWAY)
self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)))
self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(1.5)))
self.assertTrue(np.diff(replayed_data['gps_flag'])[505] == 1.0)
def test_gps_tunnel(self):
"""
Test: no GPS message for the middle section of the segment
Expected Result:
- yaw_rate: unchanged
- roll:
- gpsOK: False for the middle section, True for the rest
"""
orig_data, replayed_data = run_scenarios(Scenario.GPS_TUNNEL)
self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)))
self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)))
self.assertTrue(np.diff(replayed_data['gps_flag'])[213] == -1.0)
self.assertTrue(np.diff(replayed_data['gps_flag'])[805] == 1.0)
def test_gyro_off(self):
"""
Test: no gyroscope message for the entire segment
Expected Result:
- yaw_rate: 0
- roll: 0
- sensorsOK: False
"""
_, replayed_data = run_scenarios(Scenario.GYRO_OFF)
self.assertTrue(np.allclose(replayed_data['yaw_rate'], 0.0))
self.assertTrue(np.allclose(replayed_data['roll'], 0.0))
self.assertTrue(np.all(replayed_data['sensors_flag'] == 0.0))
def test_gyro_spikes(self):
"""
Test: a gyroscope spike in the middle of the segment
Expected Result:
- yaw_rate: unchanged
- roll: unchanged
- inputsOK: False for some time after the spike, True for the rest
"""
orig_data, replayed_data = run_scenarios(Scenario.GYRO_SPIKE_MIDWAY)
self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)))
self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)))
self.assertTrue(np.diff(replayed_data['inputs_flag'])[500] == -1.0)
self.assertTrue(np.diff(replayed_data['inputs_flag'])[694] == 1.0)
def test_accel_off(self):
"""
Test: no accelerometer message for the entire segment
Expected Result:
- yaw_rate: 0
- roll: 0
- sensorsOK: False
"""
_, replayed_data = run_scenarios(Scenario.ACCEL_OFF)
self.assertTrue(np.allclose(replayed_data['yaw_rate'], 0.0))
self.assertTrue(np.allclose(replayed_data['roll'], 0.0))
self.assertTrue(np.all(replayed_data['sensors_flag'] == 0.0))
def test_accel_spikes(self):
"""
ToDo:
Test: an accelerometer spike in the middle of the segment
Expected Result: Right now, the kalman filter is not robust to small spikes like it is to gyroscope spikes.
"""
orig_data, replayed_data = run_scenarios(Scenario.ACCEL_SPIKE_MIDWAY)
self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)))
self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)))
if __name__ == "__main__":
unittest.main()

@ -280,7 +280,7 @@ void BinaryViewModel::refresh() {
updateState(); updateState();
} }
void BinaryViewModel::updateItem(int row, int col, const QString &val, const QColor &color) { void BinaryViewModel::updateItem(int row, int col, uint8_t val, const QColor &color) {
auto &item = items[row * column_count + col]; auto &item = items[row * column_count + col];
if (item.val != val || item.bg_color != color) { if (item.val != val || item.bg_color != color) {
item.val = val; item.val = val;
@ -307,7 +307,7 @@ void BinaryViewModel::updateState() {
for (int i = 0; i < binary.size(); ++i) { for (int i = 0; i < binary.size(); ++i) {
for (int j = 0; j < 8; ++j) { for (int j = 0; j < 8; ++j) {
auto &item = items[i * column_count + j]; auto &item = items[i * column_count + j];
QString val = ((binary[i] >> (7 - j)) & 1) != 0 ? "1" : "0"; int val = ((binary[i] >> (7 - j)) & 1) != 0 ? 1 : 0;
// Bit update frequency based highlighting // Bit update frequency based highlighting
double offset = !item.sigs.empty() ? 50 : 0; double offset = !item.sigs.empty() ? 50 : 0;
auto n = last_msg.bit_change_counts[i][7 - j]; auto n = last_msg.bit_change_counts[i][7 - j];
@ -317,7 +317,7 @@ void BinaryViewModel::updateState() {
color.setAlpha(alpha); color.setAlpha(alpha);
updateItem(i, j, val, color); updateItem(i, j, val, color);
} }
updateItem(i, 8, toHex(binary[i]), last_msg.colors[i]); updateItem(i, 8, binary[i], last_msg.colors[i]);
} }
} }
@ -348,6 +348,13 @@ BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(pa
small_font.setPixelSize(8); small_font.setPixelSize(8);
hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
hex_font.setBold(true); hex_font.setBold(true);
bin_text_table[0].setText("0");
bin_text_table[1].setText("1");
for (int i = 0; i < 256; ++i) {
hex_text_table[i].setText(QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
hex_text_table[i].prepare({}, hex_font);
}
} }
bool BinaryItemDelegate::hasSignal(const QModelIndex &index, int dx, int dy, const cabana::Signal *sig) const { bool BinaryItemDelegate::hasSignal(const QModelIndex &index, int dx, int dy, const cabana::Signal *sig) const {
@ -392,7 +399,9 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
} else if (!item->valid) { } else if (!item->valid) {
painter->fillRect(option.rect, QBrush(Qt::darkGray, Qt::BDiagPattern)); painter->fillRect(option.rect, QBrush(Qt::darkGray, Qt::BDiagPattern));
} }
painter->drawText(option.rect, Qt::AlignCenter, item->val); if (item->valid) {
utils::drawStaticText(painter, option.rect, index.column() == 8 ? hex_text_table[item->val] : bin_text_table[item->val]);
}
if (item->is_msb || item->is_lsb) { if (item->is_msb || item->is_lsb) {
painter->setFont(small_font); painter->setFont(small_font);
painter->drawText(option.rect.adjusted(8, 0, -8, -3), Qt::AlignRight | Qt::AlignBottom, item->is_msb ? "M" : "L"); painter->drawText(option.rect.adjusted(8, 0, -8, -3), Qt::AlignRight | Qt::AlignBottom, item->is_msb ? "M" : "L");

@ -19,6 +19,8 @@ public:
void drawSignalCell(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index, 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; QFont small_font, hex_font;
std::array<QStaticText, 256> hex_text_table;
std::array<QStaticText, 2> bin_text_table;
}; };
class BinaryViewModel : public QAbstractTableModel { class BinaryViewModel : public QAbstractTableModel {
@ -26,7 +28,7 @@ public:
BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {} BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {}
void refresh(); void refresh();
void updateState(); void updateState();
void updateItem(int row, int col, const QString &val, const QColor &color); void updateItem(int row, int col, uint8_t val, const QColor &color);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; }
@ -42,7 +44,7 @@ public:
QColor bg_color = QColor(102, 86, 169, 255); QColor bg_color = QColor(102, 86, 169, 255);
bool is_msb = false; bool is_msb = false;
bool is_lsb = false; bool is_lsb = false;
QString val; uint8_t val;
QList<const cabana::Signal *> sigs; QList<const cabana::Signal *> sigs;
bool valid = false; bool valid = false;
}; };

@ -14,6 +14,7 @@
#include <QMimeData> #include <QMimeData>
#include <QOpenGLWidget> #include <QOpenGLWidget>
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QRandomGenerator>
#include <QRubberBand> #include <QRubberBand>
#include <QScreen> #include <QScreen>
#include <QtMath> #include <QtMath>
@ -25,7 +26,8 @@
const int AXIS_X_TOP_MARGIN = 4; const int AXIS_X_TOP_MARGIN = 4;
static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; } static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; }
ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent) : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) { ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent)
: charts_widget(parent), tip_label(this), QChartView(nullptr, parent) {
series_type = (SeriesType)settings.chart_series_type; series_type = (SeriesType)settings.chart_series_type;
QChart *chart = new QChart(); QChart *chart = new QChart();
chart->setBackgroundVisible(false); chart->setBackgroundVisible(false);
@ -150,6 +152,11 @@ void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
void ChartView::signalUpdated(const cabana::Signal *sig) { void ChartView::signalUpdated(const cabana::Signal *sig) {
if (std::any_of(sigs.cbegin(), sigs.cend(), [=](auto &s) { return s.sig == sig; })) { if (std::any_of(sigs.cbegin(), sigs.cend(), [=](auto &s) { return s.sig == sig; })) {
for (const auto &s : sigs) {
if (s.sig == sig && s.series->color() != sig->color) {
setSeriesColor(s.series, sig->color);
}
}
updateTitle(); updateTitle();
updateSeries(sig); updateSeries(sig);
} }
@ -280,7 +287,6 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) {
s.step_vals.clear(); s.step_vals.clear();
s.last_value_mono_time = 0; s.last_value_mono_time = 0;
} }
s.series->setColor(s.sig->color);
const auto &msgs = can->events(s.msg_id); const auto &msgs = can->events(s.msg_id);
s.vals.reserve(msgs.capacity()); s.vals.reserve(msgs.capacity());
@ -668,6 +674,7 @@ void ChartView::drawBackground(QPainter *painter, const QRectF &rect) {
void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
drawTimeline(painter); drawTimeline(painter);
drawSignalValue(painter);
// draw track points // draw track points
painter->setPen(Qt::NoPen); painter->setPen(Qt::NoPen);
qreal track_line_x = -1; qreal track_line_x = -1;
@ -718,23 +725,24 @@ void ChartView::drawRubberBandTimeRange(QPainter *painter) {
void ChartView::drawTimeline(QPainter *painter) { void ChartView::drawTimeline(QPainter *painter) {
const auto plot_area = chart()->plotArea(); const auto plot_area = chart()->plotArea();
// draw line // draw vertical time line
qreal x = std::clamp(chart()->mapToPosition(QPointF{cur_sec, 0}).x(), plot_area.left(), plot_area.right()); qreal x = std::clamp(chart()->mapToPosition(QPointF{cur_sec, 0}).x(), plot_area.left(), plot_area.right());
painter->setPen(QPen(chart()->titleBrush().color(), 2)); painter->setPen(QPen(chart()->titleBrush().color(), 2));
painter->drawLine(QPointF{x, plot_area.top()}, QPointF{x, plot_area.bottom() + 1}); painter->drawLine(QPointF{x, plot_area.top()}, QPointF{x, plot_area.bottom() + 1});
// draw current time // draw current time under the axis-x
QString time_str = QString::number(cur_sec, 'f', 2); QString time_str = QString::number(cur_sec, 'f', 2);
QSize time_str_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, time_str) + QSize(8, 2); QSize time_str_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, time_str) + QSize(8, 2);
QRect time_str_rect(QPoint(x - time_str_size.width() / 2, plot_area.bottom() + AXIS_X_TOP_MARGIN), time_str_size); QRectF time_str_rect(QPointF(x - time_str_size.width() / 2.0, plot_area.bottom() + AXIS_X_TOP_MARGIN), time_str_size);
QPainterPath path; QPainterPath path;
path.addRoundedRect(time_str_rect, 3, 3); path.addRoundedRect(time_str_rect, 3, 3);
painter->fillPath(path, settings.theme == DARK_THEME ? Qt::darkGray : Qt::gray); painter->fillPath(path, settings.theme == DARK_THEME ? Qt::darkGray : Qt::gray);
painter->setPen(palette().color(QPalette::BrightText)); painter->setPen(palette().color(QPalette::BrightText));
painter->setFont(axis_x->labelsFont()); painter->setFont(axis_x->labelsFont());
painter->drawText(time_str_rect, Qt::AlignCenter, time_str); painter->drawText(time_str_rect, Qt::AlignCenter, time_str);
}
// draw signal value void ChartView::drawSignalValue(QPainter *painter) {
auto item_group = qgraphicsitem_cast<QGraphicsItemGroup *>(chart()->legend()->childItems()[0]); auto item_group = qgraphicsitem_cast<QGraphicsItemGroup *>(chart()->legend()->childItems()[0]);
assert(item_group != nullptr); assert(item_group != nullptr);
auto legend_markers = item_group->childItems(); auto legend_markers = item_group->childItems();
@ -786,6 +794,7 @@ QXYSeries *ChartView::createSeries(SeriesType type, QColor color) {
} }
void ChartView::addSeries(QXYSeries *series) { void ChartView::addSeries(QXYSeries *series) {
setSeriesColor(series, series->color());
chart()->addSeries(series); chart()->addSeries(series);
series->attachAxis(axis_x); series->attachAxis(axis_x);
series->attachAxis(axis_y); series->attachAxis(axis_y);
@ -798,6 +807,21 @@ void ChartView::addSeries(QXYSeries *series) {
} }
} }
void ChartView::setSeriesColor(QXYSeries *series, QColor color) {
auto existing_series = chart()->series();
for (auto s : existing_series) {
if (s != series && std::abs(color.hueF() - qobject_cast<QXYSeries *>(s)->color().hueF()) < 0.1) {
// use different color to distinguish it from others.
auto last_color = qobject_cast<QXYSeries *>(existing_series.back())->color();
color.setHsvF(std::fmod(last_color.hueF() + 60 / 360.0, 1.0),
QRandomGenerator::global()->bounded(35, 100) / 100.0,
QRandomGenerator::global()->bounded(85, 100) / 100.0);
break;
}
}
series->setColor(color);
}
void ChartView::setSeriesType(SeriesType type) { void ChartView::setSeriesType(SeriesType type) {
if (type != series_type) { if (type != series_type) {
series_type = type; series_type = type;

@ -83,11 +83,13 @@ private:
void drawForeground(QPainter *painter, const QRectF &rect) override; void drawForeground(QPainter *painter, const QRectF &rect) override;
void drawBackground(QPainter *painter, const QRectF &rect) override; void drawBackground(QPainter *painter, const QRectF &rect) override;
void drawDropIndicator(bool draw) { if (std::exchange(can_drop, draw) != can_drop) viewport()->update(); } void drawDropIndicator(bool draw) { if (std::exchange(can_drop, draw) != can_drop) viewport()->update(); }
void drawSignalValue(QPainter *painter);
void drawTimeline(QPainter *painter); void drawTimeline(QPainter *painter);
void drawRubberBandTimeRange(QPainter *painter); void drawRubberBandTimeRange(QPainter *painter);
std::tuple<double, double, int> getNiceAxisNumbers(qreal min, qreal max, int tick_count); std::tuple<double, double, int> getNiceAxisNumbers(qreal min, qreal max, int tick_count);
qreal niceNumber(qreal x, bool ceiling); qreal niceNumber(qreal x, bool ceiling);
QXYSeries *createSeries(SeriesType type, QColor color); QXYSeries *createSeries(SeriesType type, QColor color);
void setSeriesColor(QXYSeries *, QColor color);
void updateSeriesPoints(); void updateSeriesPoints();
void removeIf(std::function<bool(const SigItem &)> predicate); void removeIf(std::function<bool(const SigItem &)> predicate);
inline void clearTrackPoints() { for (auto &s : sigs) s.track_pt = {}; } inline void clearTrackPoints() { for (auto &s : sigs) s.track_pt = {}; }

@ -277,6 +277,10 @@ void ChartsWidget::splitChart(ChartView *src_chart) {
for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) { for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) {
auto c = createChart(); auto c = createChart();
src_chart->chart()->removeSeries(it->series); src_chart->chart()->removeSeries(it->series);
// Restore to the original color
it->series->setColor(it->sig->color);
c->addSeries(it->series); c->addSeries(it->series);
c->sigs.push_back(*it); c->sigs.push_back(*it);
c->updateAxisY(); c->updateAxisY();
@ -285,6 +289,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) {
} }
src_chart->updateAxisY(); src_chart->updateAxisY();
src_chart->updateTitle(); src_chart->updateTitle();
QTimer::singleShot(0, src_chart, &ChartView::resetChartCache);
} }
} }

@ -4,11 +4,13 @@
// EditMsgCommand // EditMsgCommand
EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &comment, QUndoCommand *parent) EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size,
: id(id), new_name(name), new_size(size), new_comment(comment), QUndoCommand(parent) { const QString &node, const QString &comment, QUndoCommand *parent)
: id(id), new_name(name), new_size(size), new_node(node), new_comment(comment), QUndoCommand(parent) {
if (auto msg = dbc()->msg(id)) { if (auto msg = dbc()->msg(id)) {
old_name = msg->name; old_name = msg->name;
old_size = msg->size; old_size = msg->size;
old_node = msg->transmitter;
old_comment = msg->comment; old_comment = msg->comment;
setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address)); setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address));
} else { } else {
@ -20,11 +22,11 @@ void EditMsgCommand::undo() {
if (old_name.isEmpty()) if (old_name.isEmpty())
dbc()->removeMsg(id); dbc()->removeMsg(id);
else else
dbc()->updateMsg(id, old_name, old_size, old_comment); dbc()->updateMsg(id, old_name, old_size, old_node, old_comment);
} }
void EditMsgCommand::redo() { void EditMsgCommand::redo() {
dbc()->updateMsg(id, new_name, new_size, new_comment); dbc()->updateMsg(id, new_name, new_size, new_node, new_comment);
} }
// RemoveMsgCommand // RemoveMsgCommand
@ -38,7 +40,7 @@ RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) :
void RemoveMsgCommand::undo() { void RemoveMsgCommand::undo() {
if (!message.name.isEmpty()) { if (!message.name.isEmpty()) {
dbc()->updateMsg(id, message.name, message.size, message.comment); dbc()->updateMsg(id, message.name, message.size, message.transmitter, message.comment);
for (auto s : message.getSignals()) for (auto s : message.getSignals())
dbc()->addSignal(id, *s); dbc()->addSignal(id, *s);
} }
@ -64,7 +66,7 @@ void AddSigCommand::undo() {
void AddSigCommand::redo() { void AddSigCommand::redo() {
if (auto msg = dbc()->msg(id); !msg) { if (auto msg = dbc()->msg(id); !msg) {
msg_created = true; msg_created = true;
dbc()->updateMsg(id, dbc()->newMsgName(id), can->lastMessage(id).dat.size(), ""); dbc()->updateMsg(id, dbc()->newMsgName(id), can->lastMessage(id).dat.size(), "", "");
} }
signal.name = dbc()->newSignalName(id); signal.name = dbc()->newSignalName(id);
signal.max = std::pow(2, signal.size) - 1; signal.max = std::pow(2, signal.size) - 1;

@ -10,13 +10,14 @@
class EditMsgCommand : public QUndoCommand { class EditMsgCommand : public QUndoCommand {
public: public:
EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &comment, QUndoCommand *parent = nullptr); EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &node,
const QString &comment, QUndoCommand *parent = nullptr);
void undo() override; void undo() override;
void redo() override; void redo() override;
private: private:
const MessageId id; const MessageId id;
QString old_name, new_name, old_comment, new_comment; QString old_name, new_name, old_comment, new_comment, old_node, new_node;
int old_size = 0, new_size = 0; int old_size = 0, new_size = 0;
}; };

@ -78,6 +78,9 @@ QString cabana::Msg::newSignalName() {
} }
void cabana::Msg::update() { void cabana::Msg::update() {
if (transmitter.isEmpty()) {
transmitter = DEFAULT_NODE_NAME;
}
mask.assign(size, 0x00); mask.assign(size, 0x00);
multiplexor = nullptr; multiplexor = nullptr;
@ -125,6 +128,9 @@ void cabana::Msg::update() {
void cabana::Signal::update() { void cabana::Signal::update() {
updateMsbLsb(*this); updateMsbLsb(*this);
if (receiver_name.isEmpty()) {
receiver_name = DEFAULT_NODE_NAME;
}
float h = 19 * (float)lsb / 64.0; float h = 19 * (float)lsb / 64.0;
h = fmod(h, 1.0); h = fmod(h, 1.0);

@ -13,6 +13,7 @@
#include "opendbc/can/common_dbc.h" #include "opendbc/can/common_dbc.h"
const QString UNTITLED = "untitled"; const QString UNTITLED = "untitled";
const QString DEFAULT_NODE_NAME = "XXX";
struct MessageId { struct MessageId {
uint8_t source = 0; uint8_t source = 0;

@ -60,11 +60,12 @@ bool DBCFile::writeContents(const QString &fn) {
return false; return false;
} }
void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment) { void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) {
auto &m = msgs[id.address]; auto &m = msgs[id.address];
m.address = id.address; m.address = id.address;
m.name = name; m.name = name;
m.size = size; m.size = size;
m.transmitter = node.isEmpty() ? DEFAULT_NODE_NAME : node;
m.comment = comment; m.comment = comment;
} }
@ -198,7 +199,8 @@ void DBCFile::parse(const QString &content) {
QString DBCFile::generateDBC() { QString DBCFile::generateDBC() {
QString dbc_string, signal_comment, message_comment, val_desc; QString dbc_string, signal_comment, message_comment, val_desc;
for (const auto &[address, m] : msgs) { for (const auto &[address, m] : msgs) {
dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(m.transmitter.isEmpty() ? "XXX" : m.transmitter); const QString transmitter = m.transmitter.isEmpty() ? DEFAULT_NODE_NAME : m.transmitter;
dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(transmitter);
if (!m.comment.isEmpty()) { if (!m.comment.isEmpty()) {
message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment); message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment);
} }
@ -221,7 +223,7 @@ QString DBCFile::generateDBC() {
.arg(doubleToString(sig->min)) .arg(doubleToString(sig->min))
.arg(doubleToString(sig->max)) .arg(doubleToString(sig->max))
.arg(sig->unit) .arg(sig->unit)
.arg(sig->receiver_name.isEmpty() ? "XXX" : sig->receiver_name); .arg(sig->receiver_name.isEmpty() ? DEFAULT_NODE_NAME : sig->receiver_name);
if (!sig->comment.isEmpty()) { if (!sig->comment.isEmpty()) {
signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment); signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment);
} }

@ -22,7 +22,7 @@ public:
void cleanupAutoSaveFile(); void cleanupAutoSaveFile();
QString generateDBC(); QString generateDBC();
void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment); void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment);
inline void removeMsg(const MessageId &id) { msgs.erase(id.address); } inline void removeMsg(const MessageId &id) { msgs.erase(id.address); }
inline const std::map<uint32_t, cabana::Msg> &getMessages() const { return msgs; } inline const std::map<uint32_t, cabana::Msg> &getMessages() const { return msgs; }

@ -82,10 +82,10 @@ void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) {
} }
} }
void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment) { void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) {
auto dbc_file = findDBCFile(id); auto dbc_file = findDBCFile(id);
assert(dbc_file); // This should be impossible assert(dbc_file); // This should be impossible
dbc_file->updateMsg(id, name, size, comment); dbc_file->updateMsg(id, name, size, node, comment);
emit msgUpdated(id); emit msgUpdated(id);
} }

@ -28,7 +28,7 @@ public:
void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig);
void removeSignal(const MessageId &id, const QString &sig_name); void removeSignal(const MessageId &id, const QString &sig_name);
void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment); void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment);
void removeMsg(const MessageId &id); void removeMsg(const MessageId &id);
QString newMsgName(const MessageId &id); QString newMsgName(const MessageId &id);

@ -2,7 +2,6 @@
#include <QFormLayout> #include <QFormLayout>
#include <QMenu> #include <QMenu>
#include <QMessageBox>
#include "tools/cabana/commands.h" #include "tools/cabana/commands.h"
#include "tools/cabana/mainwin.h" #include "tools/cabana/mainwin.h"
@ -135,11 +134,12 @@ void DetailWidget::refresh() {
for (auto s : binary_view->getOverlappingSignals()) { for (auto s : binary_view->getOverlappingSignals()) {
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name)); warnings.push_back(tr("%1 has overlapping bits.").arg(s->name));
} }
name_label->setText(QString("%1 (%2)").arg(msgName(msg_id), msg->transmitter));
} else { } else {
warnings.push_back(tr("Drag-Select in binary view to create new signal.")); warnings.push_back(tr("Drag-Select in binary view to create new signal."));
name_label->setText(msgName(msg_id));
} }
remove_btn->setEnabled(msg != nullptr); remove_btn->setEnabled(msg != nullptr);
name_label->setText(msgName(msg_id));
if (!warnings.isEmpty()) { if (!warnings.isEmpty()) {
warning_label->setText(warnings.join('\n')); warning_label->setText(warnings.join('\n'));
@ -164,8 +164,8 @@ void DetailWidget::editMsg() {
int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); int size = msg ? msg->size : can->lastMessage(msg_id).dat.size();
EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); EditMessageDialog dlg(msg_id, msgName(msg_id), size, this);
if (dlg.exec()) { if (dlg.exec()) {
UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), dlg.size_spin->value(),
dlg.size_spin->value(), dlg.comment_edit->toPlainText().trimmed())); dlg.node->text().trimmed(), dlg.comment_edit->toPlainText().trimmed()));
} }
} }
@ -182,25 +182,24 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit
form_layout->addRow("", error_label = new QLabel); form_layout->addRow("", error_label = new QLabel);
error_label->setVisible(false); error_label->setVisible(false);
name_edit = new QLineEdit(title, this); form_layout->addRow(tr("Name"), name_edit = new QLineEdit(title, this));
name_edit->setValidator(new NameValidator(name_edit)); name_edit->setValidator(new NameValidator(name_edit));
form_layout->addRow(tr("Name"), name_edit);
size_spin = new QSpinBox(this); form_layout->addRow(tr("Size"), size_spin = new QSpinBox(this));
// TODO: limit the maximum? // TODO: limit the maximum?
size_spin->setMinimum(1); size_spin->setMinimum(1);
size_spin->setValue(size); size_spin->setValue(size);
form_layout->addRow(tr("Size"), size_spin);
form_layout->addRow(tr("Node"), node = new QLineEdit(this));
node->setValidator(new NameValidator(name_edit));
form_layout->addRow(tr("Comment"), comment_edit = new QTextEdit(this)); form_layout->addRow(tr("Comment"), comment_edit = new QTextEdit(this));
form_layout->addRow(btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel));
if (auto msg = dbc()->msg(msg_id)) { if (auto msg = dbc()->msg(msg_id)) {
node->setText(msg->transmitter);
comment_edit->setText(msg->comment); comment_edit->setText(msg->comment);
} }
btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
validateName(name_edit->text()); validateName(name_edit->text());
form_layout->addRow(btn_box);
setFixedWidth(parent->width() * 0.9); setFixedWidth(parent->width() * 0.9);
connect(name_edit, &QLineEdit::textEdited, this, &EditMessageDialog::validateName); connect(name_edit, &QLineEdit::textEdited, this, &EditMessageDialog::validateName);
connect(btn_box, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(btn_box, &QDialogButtonBox::accepted, this, &QDialog::accept);

@ -21,6 +21,7 @@ public:
QString original_name; QString original_name;
QDialogButtonBox *btn_box; QDialogButtonBox *btn_box;
QLineEdit *name_edit; QLineEdit *name_edit;
QLineEdit *node;
QTextEdit *comment_edit; QTextEdit *comment_edit;
QLabel *error_label; QLabel *error_label;
QSpinBox *size_spin; QSpinBox *size_spin;

@ -439,11 +439,11 @@ void MainWindow::saveFile(DBCFile *dbc_file) {
if (!dbc_file->filename.isEmpty()) { if (!dbc_file->filename.isEmpty()) {
dbc_file->save(); dbc_file->save();
updateLoadSaveMenus(); updateLoadSaveMenus();
UndoStack::instance()->setClean();
statusBar()->showMessage(tr("File saved"), 2000);
} else if (!dbc_file->isEmpty()) { } else if (!dbc_file->isEmpty()) {
saveFileAs(dbc_file); saveFileAs(dbc_file);
} }
UndoStack::instance()->setClean();
statusBar()->showMessage(tr("File saved"), 2000);
} }
void MainWindow::saveFileAs(DBCFile *dbc_file) { void MainWindow::saveFileAs(DBCFile *dbc_file) {
@ -451,6 +451,8 @@ void MainWindow::saveFileAs(DBCFile *dbc_file) {
QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)"));
if (!fn.isEmpty()) { if (!fn.isEmpty()) {
dbc_file->saveAs(fn); dbc_file->saveAs(fn);
UndoStack::instance()->setClean();
statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000);
updateRecentFiles(fn); updateRecentFiles(fn);
updateLoadSaveMenus(); updateLoadSaveMenus();
} }
@ -606,7 +608,13 @@ void MainWindow::closeEvent(QCloseEvent *event) {
settings.video_splitter_state = video_splitter->saveState(); settings.video_splitter_state = video_splitter->saveState();
} }
settings.message_header_state = messages_widget->saveHeaderState(); settings.message_header_state = messages_widget->saveHeaderState();
settings.save();
auto status = settings.save();
if (status == QSettings::AccessError) {
QString error = tr("Failed to write settings to [%1]: access denied").arg(Settings::filePath());
qDebug() << error;
QMessageBox::warning(this, tr("Failed to write settings"), error);
}
QWidget::closeEvent(event); QWidget::closeEvent(event);
} }

@ -6,14 +6,13 @@
#include <QFileDialog> #include <QFileDialog>
#include <QFormLayout> #include <QFormLayout>
#include <QPushButton> #include <QPushButton>
#include <QSettings>
#include <QStandardPaths> #include <QStandardPaths>
#include "tools/cabana/util.h" #include "tools/cabana/util.h"
Settings settings; Settings settings;
void Settings::save() { QSettings::Status Settings::save() {
QSettings s(filePath(), QSettings::IniFormat); QSettings s(filePath(), QSettings::IniFormat);
s.setValue("fps", fps); s.setValue("fps", fps);
s.setValue("max_cached_minutes", max_cached_minutes); s.setValue("max_cached_minutes", max_cached_minutes);
@ -35,6 +34,8 @@ void Settings::save() {
s.setValue("log_path", log_path); s.setValue("log_path", log_path);
s.setValue("drag_direction", drag_direction); s.setValue("drag_direction", drag_direction);
s.setValue("suppress_defined_signals", suppress_defined_signals); s.setValue("suppress_defined_signals", suppress_defined_signals);
s.sync();
return s.status();
} }
void Settings::load() { void Settings::load() {

@ -7,6 +7,7 @@
#include <QDialog> #include <QDialog>
#include <QGroupBox> #include <QGroupBox>
#include <QLineEdit> #include <QLineEdit>
#include <QSettings>
#include <QSpinBox> #include <QSpinBox>
#define LIGHT_THEME 1 #define LIGHT_THEME 1
@ -24,7 +25,7 @@ public:
}; };
Settings() {} Settings() {}
void save(); QSettings::Status save();
void load(); void load();
inline static QString filePath() { return QApplication::applicationDirPath() + "/settings"; } inline static QString filePath() { return QApplication::applicationDirPath() + "/settings"; }

@ -36,7 +36,7 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p
void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) { void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) {
Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig}; Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig};
parent_item->children.insert(pos, item); parent_item->children.insert(pos, item);
QString titles[]{"Name", "Size", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", QString titles[]{"Name", "Size", "Node", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info",
"Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"}; "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"};
for (int i = 0; i < std::size(titles); ++i) { for (int i = 0; i < std::size(titles); ++i) {
item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)}); item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)});
@ -134,6 +134,7 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const {
case Item::Sig: return item->sig_val; case Item::Sig: return item->sig_val;
case Item::Name: return item->sig->name; case Item::Name: return item->sig->name;
case Item::Size: return item->sig->size; case Item::Size: return item->sig->size;
case Item::Node: return item->sig->receiver_name;
case Item::SignalType: return signalTypeToString(item->sig->type); case Item::SignalType: return signalTypeToString(item->sig->type);
case Item::MultiplexValue: return item->sig->multiplex_value; case Item::MultiplexValue: return item->sig->multiplex_value;
case Item::Offset: return doubleToString(item->sig->offset); case Item::Offset: return doubleToString(item->sig->offset);
@ -172,6 +173,7 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r
switch (item->type) { switch (item->type) {
case Item::Name: s.name = value.toString(); break; case Item::Name: s.name = value.toString(); break;
case Item::Size: s.size = value.toInt(); break; case Item::Size: s.size = value.toInt(); break;
case Item::Node: s.receiver_name = value.toString().trimmed(); break;
case Item::SignalType: s.type = (cabana::Signal::Type)value.toInt(); break; case Item::SignalType: s.type = (cabana::Signal::Type)value.toInt(); break;
case Item::MultiplexValue: s.multiplex_value = value.toInt(); break; case Item::MultiplexValue: s.multiplex_value = value.toInt(); break;
case Item::Endian: s.is_little_endian = value.toBool(); break; case Item::Endian: s.is_little_endian = value.toBool(); break;
@ -195,11 +197,11 @@ void SignalModel::showExtraInfo(const QModelIndex &index) {
if (item->type == Item::ExtraInfo) { if (item->type == Item::ExtraInfo) {
if (!item->parent->extra_expanded) { if (!item->parent->extra_expanded) {
item->parent->extra_expanded = true; item->parent->extra_expanded = true;
beginInsertRows(index.parent(), 7, 13); beginInsertRows(index.parent(), Item::ExtraInfo - 2, Item::Desc - 2);
endInsertRows(); endInsertRows();
} else { } else {
item->parent->extra_expanded = false; item->parent->extra_expanded = false;
beginRemoveRows(index.parent(), 7, 13); beginRemoveRows(index.parent(), Item::ExtraInfo - 2, Item::Desc - 2);
endRemoveRows(); endRemoveRows();
} }
} }
@ -267,6 +269,7 @@ void SignalModel::handleSignalRemoved(const cabana::Signal *sig) {
SignalItemDelegate::SignalItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { SignalItemDelegate::SignalItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
name_validator = new NameValidator(this); name_validator = new NameValidator(this);
node_validator = new QRegExpValidator(QRegExp("^\\w+(,\\w+)*$"), this);
double_validator = new DoubleValidator(this); double_validator = new DoubleValidator(this);
label_font.setPointSize(8); label_font.setPointSize(8);
@ -382,12 +385,14 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto item = (SignalModel::Item *)index.internalPointer(); auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset || if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Node || item->type == SignalModel::Item::Offset ||
item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::MultiplexValue || item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::MultiplexValue ||
item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) { item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) {
QLineEdit *e = new QLineEdit(parent); QLineEdit *e = new QLineEdit(parent);
e->setFrame(false); e->setFrame(false);
e->setValidator(item->type == SignalModel::Item::Name ? name_validator : double_validator); if (item->type == SignalModel::Item::Name) e->setValidator(name_validator);
else if (item->type == SignalModel::Item::Node) e->setValidator(node_validator);
else e->setValidator(double_validator);
if (item->type == SignalModel::Item::Name) { if (item->type == SignalModel::Item::Name) {
QCompleter *completer = new QCompleter(dbc()->signalNames()); QCompleter *completer = new QCompleter(dbc()->signalNames());
@ -395,7 +400,6 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie
completer->setFilterMode(Qt::MatchContains); completer->setFilterMode(Qt::MatchContains);
e->setCompleter(completer); e->setCompleter(completer);
} }
return e; return e;
} else if (item->type == SignalModel::Item::Size) { } else if (item->type == SignalModel::Item::Size) {
QSpinBox *spin = new QSpinBox(parent); QSpinBox *spin = new QSpinBox(parent);

@ -17,7 +17,7 @@ class SignalModel : public QAbstractItemModel {
Q_OBJECT Q_OBJECT
public: public:
struct Item { struct Item {
enum Type {Root, Sig, Name, Size, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc }; enum Type {Root, Sig, Name, Size, Node, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc };
~Item() { qDeleteAll(children); } ~Item() { qDeleteAll(children); }
inline int row() { return parent->children.indexOf(this); } inline int row() { return parent->children.indexOf(this); }
@ -87,7 +87,7 @@ public:
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
QValidator *name_validator, *double_validator; QValidator *name_validator, *double_validator, *node_validator;
QFont label_font, minmax_font; QFont label_font, minmax_font;
const int color_label_width = 18; const int color_label_width = 18;
mutable QSize button_size; mutable QSize button_size;

@ -30,7 +30,7 @@ struct CanData {
std::vector<std::array<uint32_t, 8>> bit_change_counts; std::vector<std::array<uint32_t, 8>> bit_change_counts;
std::vector<int> last_delta; std::vector<int> last_delta;
std::vector<int> same_delta_counter; std::vector<int> same_delta_counter;
double last_freq_update_ts = seconds_since_boot(); double last_freq_update_ts = 0;
}; };
struct CanEvent { struct CanEvent {

@ -1,7 +1,6 @@
#include "tools/cabana/util.h" #include "tools/cabana/util.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <csignal> #include <csignal>
#include <limits> #include <limits>
#include <memory> #include <memory>
@ -56,6 +55,10 @@ std::pair<double, double> SegmentTree::get_minmax(int n, int left, int right, in
MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) : multiple_lines(multiple_lines), QStyledItemDelegate(parent) { MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) : multiple_lines(multiple_lines), QStyledItemDelegate(parent) {
fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
byte_size = QFontMetrics(fixed_font).size(Qt::TextSingleLine, "00 ") + QSize(0, 2); byte_size = QFontMetrics(fixed_font).size(Qt::TextSingleLine, "00 ") + QSize(0, 2);
for (int i = 0; i < 256; ++i) {
hex_text_table[i].setText(QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
hex_text_table[i].prepare({}, fixed_font);
}
} }
int MessageBytesDelegate::widthForBytes(int n) const { int MessageBytesDelegate::widthForBytes(int n) const {
@ -107,7 +110,7 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
} else if (option.state & QStyle::State_Selected) { } else if (option.state & QStyle::State_Selected) {
painter->setPen(option.palette.color(QPalette::HighlightedText)); painter->setPen(option.palette.color(QPalette::HighlightedText));
} }
painter->drawText(r, Qt::AlignCenter, toHex(byte_list[i])); utils::drawStaticText(painter, r, hex_text_table[(uint8_t)(byte_list[i])]);
} }
painter->setFont(old_font); painter->setFont(old_font);
painter->setPen(old_pen); painter->setPen(old_pen);

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <array>
#include <cmath> #include <cmath>
#include <deque> #include <deque>
#include <vector> #include <vector>
@ -10,8 +11,10 @@
#include <QDateTime> #include <QDateTime>
#include <QDoubleValidator> #include <QDoubleValidator>
#include <QFont> #include <QFont>
#include <QPainter>
#include <QRegExpValidator> #include <QRegExpValidator>
#include <QSocketNotifier> #include <QSocketNotifier>
#include <QStaticText>
#include <QStringBuilder> #include <QStringBuilder>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include <QToolButton> #include <QToolButton>
@ -75,6 +78,7 @@ public:
int widthForBytes(int n) const; int widthForBytes(int n) const;
private: private:
std::array<QStaticText, 256> hex_text_table;
QFont fixed_font; QFont fixed_font;
QSize byte_size = {}; QSize byte_size = {};
bool multiple_lines = false; bool multiple_lines = false;
@ -102,6 +106,10 @@ void setTheme(int theme);
inline QString formatSeconds(int seconds) { inline QString formatSeconds(int seconds) {
return QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); return QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
} }
inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text) {
auto size = (r.size() - text.size()) / 2;
p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text);
}
} }
class ToolButton : public QToolButton { class ToolButton : public QToolButton {

@ -13,9 +13,6 @@ if PC:
else: else:
CONFIG_DIR = "/tmp/.comma" CONFIG_DIR = "/tmp/.comma"
mkdirs_exists_ok(CONFIG_DIR)
def get_token(): def get_token():
try: try:
with open(os.path.join(CONFIG_DIR, 'auth.json')) as f: with open(os.path.join(CONFIG_DIR, 'auth.json')) as f:
@ -26,9 +23,13 @@ def get_token():
def set_token(token): def set_token(token):
mkdirs_exists_ok(CONFIG_DIR)
with open(os.path.join(CONFIG_DIR, 'auth.json'), 'w') as f: with open(os.path.join(CONFIG_DIR, 'auth.json'), 'w') as f:
json.dump({'access_token': token}, f) json.dump({'access_token': token}, f)
def clear_token(): def clear_token():
os.unlink(os.path.join(CONFIG_DIR, 'auth.json')) try:
os.unlink(os.path.join(CONFIG_DIR, 'auth.json'))
except FileNotFoundError:
pass

@ -2,10 +2,10 @@ import os
import urllib.parse import urllib.parse
from openpilot.common.file_helpers import mkdirs_exists_ok from openpilot.common.file_helpers import mkdirs_exists_ok
DEFAULT_CACHE_DIR = os.path.expanduser("~/.commacache") DEFAULT_CACHE_DIR = os.getenv("CACHE_ROOT", os.path.expanduser("~/.commacache"))
def cache_path_for_file_path(fn, cache_prefix=None): def cache_path_for_file_path(fn, cache_dir=DEFAULT_CACHE_DIR):
dir_ = os.path.join(DEFAULT_CACHE_DIR, "local") dir_ = os.path.join(cache_dir, "local")
mkdirs_exists_ok(dir_) mkdirs_exists_ok(dir_)
fn_parsed = urllib.parse.urlparse(fn) fn_parsed = urllib.parse.urlparse(fn)
if fn_parsed.scheme == '': if fn_parsed.scheme == '':

@ -12,7 +12,7 @@ import numpy as np
from lru import LRU from lru import LRU
import _io import _io
from openpilot.tools.lib.cache import cache_path_for_file_path from openpilot.tools.lib.cache import cache_path_for_file_path, DEFAULT_CACHE_DIR
from openpilot.tools.lib.exceptions import DataUnreadableError from openpilot.tools.lib.exceptions import DataUnreadableError
from openpilot.common.file_helpers import atomic_write_in_dir from openpilot.common.file_helpers import atomic_write_in_dir
@ -106,8 +106,8 @@ def cache_fn(func):
if kwargs.pop('no_cache', None): if kwargs.pop('no_cache', None):
cache_path = None cache_path = None
else: else:
cache_prefix = kwargs.pop('cache_prefix', None) cache_dir = kwargs.pop('cache_dir', DEFAULT_CACHE_DIR)
cache_path = cache_path_for_file_path(fn, cache_prefix) cache_path = cache_path_for_file_path(fn, cache_dir)
if cache_path and os.path.exists(cache_path): if cache_path and os.path.exists(cache_path):
with open(cache_path, "rb") as cache_file: with open(cache_path, "rb") as cache_file:
@ -140,18 +140,18 @@ def index_stream(fn, typ):
} }
def index_videos(camera_paths, cache_prefix=None): def index_videos(camera_paths, cache_dir=DEFAULT_CACHE_DIR):
"""Requires that paths in camera_paths are contiguous and of the same type.""" """Requires that paths in camera_paths are contiguous and of the same type."""
if len(camera_paths) < 1: if len(camera_paths) < 1:
raise ValueError("must provide at least one video to index") raise ValueError("must provide at least one video to index")
frame_type = fingerprint_video(camera_paths[0]) frame_type = fingerprint_video(camera_paths[0])
for fn in camera_paths: for fn in camera_paths:
index_video(fn, frame_type, cache_prefix) index_video(fn, frame_type, cache_dir)
def index_video(fn, frame_type=None, cache_prefix=None): def index_video(fn, frame_type=None, cache_dir=DEFAULT_CACHE_DIR):
cache_path = cache_path_for_file_path(fn, cache_prefix) cache_path = cache_path_for_file_path(fn, cache_dir)
if os.path.exists(cache_path): if os.path.exists(cache_path):
return return
@ -160,16 +160,16 @@ def index_video(fn, frame_type=None, cache_prefix=None):
frame_type = fingerprint_video(fn[0]) frame_type = fingerprint_video(fn[0])
if frame_type == FrameType.h265_stream: if frame_type == FrameType.h265_stream:
index_stream(fn, "hevc", cache_prefix=cache_prefix) index_stream(fn, "hevc", cache_dir=cache_dir)
else: else:
raise NotImplementedError("Only h265 supported") raise NotImplementedError("Only h265 supported")
def get_video_index(fn, frame_type, cache_prefix=None): def get_video_index(fn, frame_type, cache_dir=DEFAULT_CACHE_DIR):
cache_path = cache_path_for_file_path(fn, cache_prefix) cache_path = cache_path_for_file_path(fn, cache_dir)
if not os.path.exists(cache_path): if not os.path.exists(cache_path):
index_video(fn, frame_type, cache_prefix) index_video(fn, frame_type, cache_dir)
if not os.path.exists(cache_path): if not os.path.exists(cache_path):
return None return None
@ -284,13 +284,13 @@ class BaseFrameReader:
raise NotImplementedError raise NotImplementedError
def FrameReader(fn, cache_prefix=None, readahead=False, readbehind=False, index_data=None): def FrameReader(fn, cache_dir=DEFAULT_CACHE_DIR, readahead=False, readbehind=False, index_data=None):
frame_type = fingerprint_video(fn) frame_type = fingerprint_video(fn)
if frame_type == FrameType.raw: if frame_type == FrameType.raw:
return RawFrameReader(fn) return RawFrameReader(fn)
elif frame_type in (FrameType.h265_stream,): elif frame_type in (FrameType.h265_stream,):
if not index_data: if not index_data:
index_data = get_video_index(fn, frame_type, cache_prefix) index_data = get_video_index(fn, frame_type, cache_dir)
return StreamFrameReader(fn, frame_type, index_data, readahead=readahead, readbehind=readbehind) return StreamFrameReader(fn, frame_type, index_data, readahead=readahead, readbehind=readbehind)
else: else:
raise NotImplementedError(frame_type) raise NotImplementedError(frame_type)

@ -12,7 +12,7 @@
const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2";
const std::string TEST_RLOG_CHECKSUM = "5b966d4bb21a100a8c4e59195faeb741b975ccbe268211765efd1763d892bfb3"; const std::string TEST_RLOG_CHECKSUM = "5b966d4bb21a100a8c4e59195faeb741b975ccbe268211765efd1763d892bfb3";
bool donload_to_file(const std::string &url, const std::string &local_file, int chunk_size = 5 * 1024 * 1024, int retries = 3) { bool download_to_file(const std::string &url, const std::string &local_file, int chunk_size = 5 * 1024 * 1024, int retries = 3) {
do { do {
if (httpDownload(url, local_file, chunk_size)) { if (httpDownload(url, local_file, chunk_size)) {
return true; return true;
@ -29,7 +29,7 @@ TEST_CASE("httpMultiPartDownload") {
const size_t chunk_size = 5 * 1024 * 1024; const size_t chunk_size = 5 * 1024 * 1024;
std::string content; std::string content;
SECTION("download to file") { SECTION("download to file") {
REQUIRE(donload_to_file(TEST_RLOG_URL, filename, chunk_size)); REQUIRE(download_to_file(TEST_RLOG_URL, filename, chunk_size));
content = util::read_file(filename); content = util::read_file(filename);
} }
SECTION("download to buffer") { SECTION("download to buffer") {
@ -110,23 +110,36 @@ void read_segment(int n, const SegmentFile &segment_file, uint32_t flags) {
loop.exec(); loop.exec();
} }
TEST_CASE("Route") { std::string download_demo_route() {
// Create a local route from remote for testing static std::string data_dir;
Route remote_route(DEMO_ROUTE);
REQUIRE(remote_route.load()); if (data_dir == "") {
char tmp_path[] = "/tmp/root_XXXXXX"; char tmp_path[] = "/tmp/root_XXXXXX";
const std::string data_dir = mkdtemp(tmp_path); data_dir = mkdtemp(tmp_path);
const std::string route_name = DEMO_ROUTE.mid(17).toStdString();
for (int i = 0; i < 2; ++i) { Route remote_route(DEMO_ROUTE);
std::string log_path = util::string_format("%s/%s--%d/", data_dir.c_str(), route_name.c_str(), i); assert(remote_route.load());
util::create_directories(log_path, 0755);
REQUIRE(donload_to_file(remote_route.at(i).rlog.toStdString(), log_path + "rlog.bz2")); // Create a local route from remote for testing
REQUIRE(donload_to_file(remote_route.at(i).road_cam.toStdString(), log_path + "fcamera.hevc")); const std::string route_name = DEMO_ROUTE.mid(17).toStdString();
REQUIRE(donload_to_file(remote_route.at(i).driver_cam.toStdString(), log_path + "dcamera.hevc")); for (int i = 0; i < 2; ++i) {
REQUIRE(donload_to_file(remote_route.at(i).wide_road_cam.toStdString(), log_path + "ecamera.hevc")); std::string log_path = util::string_format("%s/%s--%d/", data_dir.c_str(), route_name.c_str(), i);
REQUIRE(donload_to_file(remote_route.at(i).qcamera.toStdString(), log_path + "qcamera.ts")); util::create_directories(log_path, 0755);
REQUIRE(download_to_file(remote_route.at(i).rlog.toStdString(), log_path + "rlog.bz2"));
REQUIRE(download_to_file(remote_route.at(i).road_cam.toStdString(), log_path + "fcamera.hevc"));
REQUIRE(download_to_file(remote_route.at(i).driver_cam.toStdString(), log_path + "dcamera.hevc"));
REQUIRE(download_to_file(remote_route.at(i).wide_road_cam.toStdString(), log_path + "ecamera.hevc"));
REQUIRE(download_to_file(remote_route.at(i).qcamera.toStdString(), log_path + "qcamera.ts"));
}
} }
return data_dir;
}
TEST_CASE("Route") {
std::string data_dir = download_demo_route();
SECTION("Local route") { SECTION("Local route") {
auto flags = GENERATE(REPLAY_FLAG_DCAM | REPLAY_FLAG_ECAM, REPLAY_FLAG_QCAMERA); auto flags = GENERATE(REPLAY_FLAG_DCAM | REPLAY_FLAG_ECAM, REPLAY_FLAG_QCAMERA);
Route route(DEMO_ROUTE, QString::fromStdString(data_dir)); Route route(DEMO_ROUTE, QString::fromStdString(data_dir));

@ -12,5 +12,8 @@ if [[ "$CI" ]]; then
export BLOCK="${BLOCK},ui" export BLOCK="${BLOCK},ui"
fi fi
SCRIPT_DIR=$(dirname "$0")
OPENPILOT_DIR=$SCRIPT_DIR/../../
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
cd ../../selfdrive/manager && exec ./manager.py cd $OPENPILOT_DIR/selfdrive/manager && exec ./manager.py

Loading…
Cancel
Save