diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml
index 8b16ea90b9..3558d7eb7e 100644
--- a/.github/workflows/prebuilt.yaml
+++ b/.github/workflows/prebuilt.yaml
@@ -5,7 +5,7 @@ on:
workflow_dispatch:
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
jobs:
@@ -29,5 +29,5 @@ jobs:
submodules: true
- name: Build and Push docker image
run: |
- $DOCKER_LOGIN
+ $DOCKER_GHCR_LOGIN
eval "$BUILD"
diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml
index a038570c15..f7aba648a4 100644
--- a/.github/workflows/selfdrive_tests.yaml
+++ b/.github/workflows/selfdrive_tests.yaml
@@ -16,7 +16,8 @@ env:
CL_BASE_IMAGE: openpilot-base-cl
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
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
with:
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
with:
cache_key_prefix: scons_${{ matrix.arch }}
@@ -190,7 +196,12 @@ jobs:
run: |
echo "PUSH_IMAGE=true" >> "$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
with:
git-lfs: false
@@ -211,7 +222,7 @@ jobs:
submodules: false
- name: Setup docker
run: |
- $DOCKER_LOGIN
+ $DOCKER_GHCR_LOGIN
- name: Merge x64 and arm64 tags
run: |
export PUSH_IMAGE=true
@@ -264,7 +275,6 @@ jobs:
run: |
${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \
$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 && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
./selfdrive/ui/tests/test_translations.py && \
diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml
index c7a5f80c3b..27c362fcd6 100644
--- a/.github/workflows/tools_tests.yaml
+++ b/.github/workflows/tools_tests.yaml
@@ -13,7 +13,7 @@ concurrency:
env:
BASE_IMAGE: openpilot-base
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
@@ -58,7 +58,7 @@ jobs:
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
- $DOCKER_LOGIN
+ $DOCKER_GHCR_LOGIN
- name: Build and push sim image
run: |
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'
run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
- $DOCKER_LOGIN
+ $DOCKER_GHCR_LOGIN
- name: Build and push docs image
run: |
selfdrive/test/docker_build.sh docs
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f74692c01e..a5da03b266 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -38,7 +38,7 @@ repos:
args: ['--explicit-package-bases']
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
- rev: v0.0.287
+ rev: v0.0.288
hooks:
- id: ruff
exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)'
diff --git a/README.md b/README.md
index 5978a0e5dd..523f459f3b 100644
--- a/README.md
+++ b/README.md
@@ -39,13 +39,17 @@ Running on a dedicated device in a car
------
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.
-* 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.
-* 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.
-* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car.
-
-We have detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
+1. **Supported Device:** A comma 3/3X. You can purchase these devices from (https://comma.ai/shop/comma-3x)
+
+2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software.
+ To install the release version of openpilot, use the URL `openpilot.comma.ai`.
+ 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.
+
+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
------
diff --git a/RELEASES.md b/RELEASES.md
index 0a7925200e..c4572dba19 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,7 +1,9 @@
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 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!
* Lexus IS 2023 support thanks to L3R5!
diff --git a/SConstruct b/SConstruct
index f213a5e0bd..a086f8448a 100644
--- a/SConstruct
+++ b/SConstruct
@@ -244,18 +244,6 @@ def progress_function(node):
if os.environ.get('SCONS_PROGRESS'):
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
py_include = sysconfig.get_paths()['include']
envCython = env.Clone()
@@ -337,34 +325,35 @@ if GetOption("clazy"):
qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0]
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'])
Import('_common', '_gpucommon')
-if SHARED:
- common, gpucommon = abspath(common), abspath(gpucommon)
-else:
- common = [_common, 'json11']
- gpucommon = [_gpucommon]
+common = [_common, 'json11']
+gpucommon = [_gpucommon]
Export('common', 'gpucommon')
-# cereal and messaging are shared with the system
+# Build cereal and messaging
SConscript(['cereal/SConscript'])
-if SHARED:
- cereal = abspath([File('cereal/libcereal_shared.so')])
- messaging = abspath([File('cereal/libmessaging_shared.so')])
-else:
- cereal = [File('#cereal/libcereal.a')]
- messaging = [File('#cereal/libmessaging.a')]
- 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')]
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 = [
"#selfdrive/locationd/models/constants.py",
"#selfdrive/locationd/models/gnss_helpers.py",
@@ -406,15 +395,6 @@ if arch != "Darwin":
])
# Build openpilot
-
-# build submodules
-SConscript([
- 'body/board/SConscript',
- 'cereal/SConscript',
- 'opendbc/can/SConscript',
- 'panda/SConscript',
-])
-
SConscript(['third_party/SConscript'])
SConscript(['selfdrive/boardd/SConscript'])
diff --git a/common/SConscript b/common/SConscript
index d22aca128b..97322b248d 100644
--- a/common/SConscript
+++ b/common/SConscript
@@ -1,9 +1,4 @@
-Import('env', 'envCython', 'arch', 'SHARED')
-
-if SHARED:
- fxn = env.SharedLibrary
-else:
- fxn = env.Library
+Import('env', 'envCython', 'arch')
common_libs = [
'params.cc',
@@ -18,13 +13,13 @@ common_libs = [
if arch != "Darwin":
common_libs.append('gpio.cc')
-_common = fxn('common', common_libs, LIBS="json11")
+_common = env.Library('common', common_libs, LIBS="json11")
files = [
'clutil.cc',
]
-_gpucommon = fxn('gpucommon', files)
+_gpucommon = env.Library('gpucommon', files)
Export('_common', '_gpucommon')
if GetOption('extras'):
diff --git a/docs/CARS.md b/docs/CARS.md
index 775418427d..8bb318c13e 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma 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|
Hardware Needed
|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|[](##)|[](##)|Parts
- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|Parts
- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|Parts
- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Parts
- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts
- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
@@ -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[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
diff --git a/opendbc b/opendbc
index 8d2f614ce3..4ab347baef 160000
--- a/opendbc
+++ b/opendbc
@@ -1 +1 @@
-Subproject commit 8d2f614ce3f323c65de802b2d5f29143de9c427b
+Subproject commit 4ab347baefb7473771ada0723c969c50d0c28d01
diff --git a/panda b/panda
index 8e8aa5acf6..f660323969 160000
--- a/panda
+++ b/panda
@@ -1 +1 @@
-Subproject commit 8e8aa5acf6ab0e45d91e72beaab74e69810448cd
+Subproject commit f6603239690ccee80f19e8bc5cf3ba03123f3498
diff --git a/poetry.lock b/poetry.lock
index 255f6b2acc..2c134ec32d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -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]]
name = "aiohttp"
@@ -1595,13 +1595,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"]
[[package]]
name = "identify"
-version = "2.5.27"
+version = "2.5.28"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"},
- {file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"},
+ {file = "identify-2.5.28-py2.py3-none-any.whl", hash = "sha256:87816de144bf46d161bd5b3e8f5596b16cade3b80be537087334b26bc5c177f3"},
+ {file = "identify-2.5.28.tar.gz", hash = "sha256:94bb59643083ebd60dc996d043497479ee554381fbc5307763915cda49b0e78f"},
]
[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-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{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-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"},
@@ -2178,52 +2188,58 @@ files = [
[[package]]
name = "matplotlib"
-version = "3.7.2"
+version = "3.7.3"
description = "Python plotting package"
optional = false
python-versions = ">=3.8"
files = [
- {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de"},
- {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d"},
- {file = "matplotlib-3.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1"},
- {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603"},
- {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201"},
- {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8"},
- {file = "matplotlib-3.7.2-cp310-cp310-win32.whl", hash = "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a"},
- {file = "matplotlib-3.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1"},
- {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544"},
- {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e"},
- {file = "matplotlib-3.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64"},
- {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24"},
- {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d"},
- {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4"},
- {file = "matplotlib-3.7.2-cp311-cp311-win32.whl", hash = "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc"},
- {file = "matplotlib-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e"},
- {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7"},
- {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391"},
- {file = "matplotlib-3.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca"},
- {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9"},
- {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273"},
- {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117"},
- {file = "matplotlib-3.7.2-cp38-cp38-win32.whl", hash = "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13"},
- {file = "matplotlib-3.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676"},
- {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608"},
- {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256"},
- {file = "matplotlib-3.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f"},
- {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b"},
- {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e"},
- {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f"},
- {file = "matplotlib-3.7.2-cp39-cp39-win32.whl", hash = "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20"},
- {file = "matplotlib-3.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e"},
- {file = "matplotlib-3.7.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0"},
- {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b"},
- {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c"},
- {file = "matplotlib-3.7.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2"},
- {file = "matplotlib-3.7.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd"},
- {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07"},
- {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d"},
- {file = "matplotlib-3.7.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11"},
- {file = "matplotlib-3.7.2.tar.gz", hash = "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b"},
+ {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:085c33b27561d9c04386789d5aa5eb4a932ddef43cfcdd0e01735f9a6e85ce0c"},
+ {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c568e80e1c17f68a727f30f591926751b97b98314d8e59804f54f86ae6fa6a22"},
+ {file = "matplotlib-3.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7baf98c5ad59c5c4743ea884bb025cbffa52dacdfdac0da3e6021a285a90377e"},
+ {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236024f582e40dac39bca592258888b38ae47a9fed7b8de652d68d3d02d47d2b"},
+ {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12b4f6795efea037ce2d41e7c417ad8bd02d5719c6ad4a8450a0708f4a1cfb89"},
+ {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b2136cc6c5415b78977e0e8c608647d597204b05b1d9089ccf513c7d913733"},
+ {file = "matplotlib-3.7.3-cp310-cp310-win32.whl", hash = "sha256:122dcbf9be0086e2a95d9e5e0632dbf3bd5b65eaa68c369363310a6c87753059"},
+ {file = "matplotlib-3.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4aab27d9e33293389e3c1d7c881d414a72bdfda0fedc3a6bf46c6fa88d9b8015"},
+ {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d5adc743de91e8e0b13df60deb1b1c285b8effea3d66223afceb14b63c9b05de"},
+ {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:55de4cf7cd0071b8ebf203981b53ab64f988a0a1f897a2dff300a1124e8bcd8b"},
+ {file = "matplotlib-3.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac03377fd908aaee2312d0b11735753e907adb6f4d1d102de5e2425249693f6c"},
+ {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755bafc10a46918ce9a39980009b54b02dd249594e5adf52f9c56acfddb5d0b7"},
+ {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a6094c6f8e8d18db631754df4fe9a34dec3caf074f6869a7db09f18f9b1d6b2"},
+ {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:272dba2f1b107790ed78ebf5385b8d14b27ad9e90419de340364b49fe549a993"},
+ {file = "matplotlib-3.7.3-cp311-cp311-win32.whl", hash = "sha256:591c123bed1cb4b9996fb60b41a6d89c2ec4943244540776c5f1283fb6960a53"},
+ {file = "matplotlib-3.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:3bf3a178c6504694cee8b88b353df0051583f2f6f8faa146f67115c27c856881"},
+ {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:edf54cac8ee3603f3093616b40a931e8c063969756a4d78a86e82c2fea9659f7"},
+ {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:91e36a85ea639a1ba9f91427041eac064b04829945fe331a92617b6cb21d27e5"},
+ {file = "matplotlib-3.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:caf5eaaf7c68f8d7df269dfbcaf46f48a70ff482bfcebdcc97519671023f2a7d"},
+ {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74bf57f505efea376097e948b7cdd87191a7ce8180616390aef496639edf601f"},
+ {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee152a88a0da527840a426535514b6ed8ac4240eb856b1da92cf48124320e346"},
+ {file = "matplotlib-3.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:67a410a9c9e07cbc83581eeea144bbe298870bf0ac0ee2f2e10a015ab7efee19"},
+ {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:259999c05285cb993d7f2a419cea547863fa215379eda81f7254c9e932963729"},
+ {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3f4e7fd5a6157e1d018ce2166ec8e531a481dd4a36f035b5c23edfe05a25419a"},
+ {file = "matplotlib-3.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:faa3d12d8811d08d14080a8b7b9caea9a457dc495350166b56df0db4b9909ef5"},
+ {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:336e88900c11441e458da01c8414fc57e04e17f9d3bb94958a76faa2652bcf6b"},
+ {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:12f4c0dd8aa280d796c8772ea8265a14f11a04319baa3a16daa5556065e8baea"},
+ {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1990955b11e7918d256cf3b956b10997f405b7917a3f1c7d8e69c1d15c7b1930"},
+ {file = "matplotlib-3.7.3-cp38-cp38-win32.whl", hash = "sha256:e78707b751260b42b721507ad7aa60fe4026d7f51c74cca6b9cd8b123ebb633a"},
+ {file = "matplotlib-3.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:e594ee43c59ea39ca5c6244667cac9d017a3527febc31f5532ad9135cf7469ec"},
+ {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6eaa1cf0e94c936a26b78f6d756c5fbc12e0a58c8a68b7248a2a31456ce4e234"},
+ {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0a97af9d22e8ebedc9f00b043d9bbd29a375e9e10b656982012dded44c10fd77"},
+ {file = "matplotlib-3.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f9c6c16597af660433ab330b59ee2934b832ee1fabcaf5cbde7b2add840f31e"},
+ {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7240259b4b9cbc62381f6378cff4d57af539162a18e832c1e48042fabc40b6b"},
+ {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747c6191d2e88ae854809e69aa358dbf852ff1a5738401b85c1cc9012309897a"},
+ {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec726b08a5275d827aa91bb951e68234a4423adb91cf65bc0fcdc0f2777663f7"},
+ {file = "matplotlib-3.7.3-cp39-cp39-win32.whl", hash = "sha256:40e3b9b450c6534f07278310c4e34caff41c2a42377e4b9d47b0f8d3ac1083a2"},
+ {file = "matplotlib-3.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfc118642903a23e309b1da32886bb39a4314147d013e820c86b5fb4cb2e36d0"},
+ {file = "matplotlib-3.7.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:165c8082bf8fc0360c24aa4724a22eaadbfd8c28bf1ccf7e94d685cad48261e4"},
+ {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebd8470cc2a3594746ff0513aecbfa2c55ff6f58e6cef2efb1a54eb87c88ffa2"},
+ {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]
@@ -2231,11 +2247,12 @@ contourpy = ">=1.0.1"
cycler = ">=0.10"
fonttools = ">=4.22.0"
kiwisolver = ">=1.0.1"
-numpy = ">=1.20"
+numpy = ">=1.20,<2"
packaging = ">=20.0"
pillow = ">=6.2.0"
-pyparsing = ">=2.3.1,<3.1"
+pyparsing = ">=2.3.1"
python-dateutil = ">=2.7"
+setuptools_scm = ">=7"
[[package]]
name = "mdit-py-plugins"
@@ -2766,14 +2783,7 @@ files = [
]
[package.dependencies]
-numpy = [
- {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\""},
-]
+numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""}
[[package]]
name = "opencv-python-headless"
@@ -2792,14 +2802,7 @@ files = [
]
[package.dependencies]
-numpy = [
- {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\""},
-]
+numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""}
[[package]]
name = "packaging"
@@ -3623,13 +3626,13 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"]
[[package]]
name = "pyparsing"
-version = "3.0.9"
+version = "3.1.1"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false
python-versions = ">=3.6.8"
files = [
- {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
- {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+ {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"},
+ {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"},
]
[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_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-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-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{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_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-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-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-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"},
@@ -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_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-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-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{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_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-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-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -4171,9 +4184,9 @@ setuptools = "*"
[[package]]
name = "sconscontrib"
version = "1.0"
-description = ""
+description = "Contributed builders and other useful logic for the SCons build system.,"
optional = false
-python-versions = ">=3.6, <4"
+python-versions = "<4,>=3.6"
files = []
develop = false
@@ -4338,19 +4351,39 @@ test = ["pytest"]
[[package]]
name = "setuptools"
-version = "68.2.0"
+version = "68.2.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"},
- {file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"},
+ {file = "setuptools-68.2.1-py3-none-any.whl", hash = "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae"},
+ {file = "setuptools-68.2.1.tar.gz", hash = "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1"},
]
[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"]
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]]
name = "shapely"
@@ -4899,13 +4932,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "virtualenv"
-version = "20.24.4"
+version = "20.24.5"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
- {file = "virtualenv-20.24.4-py3-none-any.whl", hash = "sha256:29c70bb9b88510f6414ac3e55c8b413a1f96239b6b789ca123437d5e892190cb"},
- {file = "virtualenv-20.24.4.tar.gz", hash = "sha256:772b05bfda7ed3b8ecd16021ca9716273ad9f4467c801f27e83ac73430246dca"},
+ {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"},
+ {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"},
]
[package.dependencies]
@@ -4919,13 +4952,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[[package]]
name = "websocket-client"
-version = "1.6.2"
+version = "1.6.3"
description = "WebSocket client for Python with low level API options"
optional = false
python-versions = ">=3.8"
files = [
- {file = "websocket-client-1.6.2.tar.gz", hash = "sha256:53e95c826bf800c4c465f50093a8c4ff091c7327023b10bfaff40cf1ef170eaa"},
- {file = "websocket_client-1.6.2-py3-none-any.whl", hash = "sha256:ce54f419dfae71f4bdba69ebe65bf7f0a93fe71bc009ad3a010aacc3eebad537"},
+ {file = "websocket-client-1.6.3.tar.gz", hash = "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f"},
+ {file = "websocket_client-1.6.3-py3-none-any.whl", hash = "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03"},
]
[package.extras]
diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py
index 8585e9f205..89c1a3596a 100644
--- a/selfdrive/car/gm/carstate.py
+++ b/selfdrive/car/gm/carstate.py
@@ -98,7 +98,7 @@ class CarState(CarStateBase):
ret.leftBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 1
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.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or
@@ -135,7 +135,7 @@ class CarState(CarStateBase):
("PSCMStatus", 10),
("ESPStatus", 10),
("BCMDoorBeltStatus", 10),
- ("VehicleIgnitionAlt", 10),
+ ("BCMGeneralPlatformStatus", 10),
("EBCMWheelSpdFront", 20),
("EBCMWheelSpdRear", 20),
("EBCMFrictionBrakeStatus", 20),
diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py
index 0083974020..3b98432615 100644
--- a/selfdrive/car/hyundai/hyundaican.py
+++ b/selfdrive/car/hyundai/hyundaican.py
@@ -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.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022,
CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022,
- CAR.SANTA_FE_PHEV_2022, CAR.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_LdwsOpt_USM"] = 2
diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py
index 9b4ae508e5..c5537993df 100644
--- a/selfdrive/car/hyundai/interface.py
+++ b/selfdrive/car/hyundai/interface.py
@@ -64,7 +64,11 @@ class CarInterface(CarInterfaceBase):
ret.steerLimitTimer = 0.4
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.wheelbase = 2.766
# Values from optimizer
@@ -211,6 +215,10 @@ class CarInterface(CarInterfaceBase):
ret.mass = 2087.
ret.wheelbase = 3.09
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
elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN:
diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py
index 2b8bb763a2..8827c7bb0c 100644
--- a/selfdrive/car/hyundai/values.py
+++ b/selfdrive/car/hyundai/values.py
@@ -69,6 +69,7 @@ class HyundaiFlags(IntFlag):
class CAR:
# Hyundai
+ AZERA_6TH_GEN = "HYUNDAI AZERA 6TH GEN"
ELANTRA = "HYUNDAI ELANTRA 2017"
ELANTRA_2021 = "HYUNDAI ELANTRA 2021"
ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021"
@@ -104,6 +105,7 @@ class CAR:
KIA_FORTE = "KIA FORTE E 2018 & GT 2021"
KIA_K5_2021 = "KIA K5 2021"
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_2ND_GEN = "KIA NIRO EV 2ND GEN"
KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019"
@@ -152,6 +154,7 @@ class HyundaiCarInfo(CarInfo):
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: [
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])),
@@ -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_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: [
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])),
@@ -523,6 +527,23 @@ FW_QUERY_CONFIG = FwQueryConfig(
)
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: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DH LKAS 1.1 -150210',
@@ -1940,6 +1961,14 @@ FW_VERSIONS = {
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 = {
@@ -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,
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_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
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,
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_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,
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 no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py
DBC = {
+ CAR.AZERA_6TH_GEN: 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_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_SORENTO_HEV_4TH_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),
}
diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py
index 4d99f594db..f7ad219f63 100755
--- a/selfdrive/car/tests/routes.py
+++ b/selfdrive/car/tests/routes.py
@@ -25,7 +25,6 @@ non_tested_cars = [
HONDA.ODYSSEY_CHN,
VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter
TOYOTA.RAV4_TSS2_2023,
- TOYOTA.RAV4H_TSS2_2023,
SUBARU.FORESTER_HYBRID,
]
@@ -94,6 +93,7 @@ routes = [
CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E),
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("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),
@@ -146,6 +146,7 @@ routes = [
CarTestRoute("9b25e8c1484a1b67|2023-04-13--10-41-45", HYUNDAI.KIA_EV6),
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("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("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),
@@ -182,6 +183,7 @@ routes = [
CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022),
CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4H_TSS2),
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("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2),
CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3),
diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py
index 1b9e39f2f1..e6460d491e 100755
--- a/selfdrive/car/tests/test_models.py
+++ b/selfdrive/car/tests/test_models.py
@@ -9,6 +9,7 @@ from parameterized import parameterized_class
from cereal import log, car
from openpilot.common.basedir import BASEDIR
+from openpilot.common.params import Params
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.car.fingerprints import all_known_cars
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.hyundai.values import CAR as HYUNDAI
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.tools.lib.logreader import LogReader
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 openpilot.selfdrive.test.helpers import SKIP_ENV_VAR
+EventName = car.CarEvent.EventName
PandaType = log.PandaState.PandaType
SafetyModel = car.CarParams.SafetyModel
@@ -163,9 +166,11 @@ class TestCarModelBase(unittest.TestCase):
del cls.can_msgs
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
+ Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled)
+
# TODO: check safetyModel is in release panda build
self.safety = libpanda_py.libpanda
@@ -315,6 +320,8 @@ class TestCarModelBase(unittest.TestCase):
controls_allowed_prev = False
CS_prev = car.CarState.new_message()
checks = defaultdict(lambda: 0)
+ controlsd = Controls(CI=self.CI)
+ controlsd.initialized = True
for idx, can in enumerate(self.can_msgs):
CS = self.CI.update(CC, (can.as_builder().to_bytes(), ))
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()
else:
# 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)
checks['controlsAllowed'] += mismatch
controls_allowed_prev = self.safety.get_controls_allowed()
diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml
index d332f7fc3d..61ed8a5a77 100644
--- a/selfdrive/car/torque_data/override.yaml
+++ b/selfdrive/car/torque_data/override.yaml
@@ -12,10 +12,6 @@ SUBARU FORESTER 2022: [.nan, 3.0, .nan]
SUBARU OUTBACK 7TH GEN: [.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 AP1 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]
HYUNDAI KONA ELECTRIC 2ND GEN: [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
mock: [10.0, 10, 0.0]
diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py
index d6f428ab1d..d51026b547 100644
--- a/selfdrive/car/toyota/interface.py
+++ b/selfdrive/car/toyota/interface.py
@@ -27,7 +27,15 @@ class CarInterface(CarInterfaceBase):
if DBC[candidate]["pt"] == "toyota_new_mc_pt_generated":
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.steerControlType = SteerControlType.angle
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
ret.steerActuatorDelay = 0.25
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
@@ -121,21 +124,25 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.3
ret.tireStiffnessFactor = 0.7933
ret.mass = 3585. * CV.LB_TO_KG # Average between ICE and Hybrid
- ret.lateralTuning.init('pid')
- ret.lateralTuning.pid.kiBP = [0.0]
- ret.lateralTuning.pid.kpBP = [0.0]
- ret.lateralTuning.pid.kpV = [0.6]
- ret.lateralTuning.pid.kiV = [0.1]
- ret.lateralTuning.pid.kf = 0.00007818594
-
- # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary.
- # See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891
- for fw in car_fw:
- 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
+
+ # Only specific EPS FW accept torque on 2023 RAV4, so they likely are all the same
+ # TODO: revisit this disparity if there is a divide for 2023
+ if candidate not in (CAR.RAV4_TSS2_2023, CAR.RAV4H_TSS2_2023):
+ ret.lateralTuning.init('pid')
+ ret.lateralTuning.pid.kiBP = [0.0]
+ ret.lateralTuning.pid.kpBP = [0.0]
+ ret.lateralTuning.pid.kpV = [0.6]
+ ret.lateralTuning.pid.kiV = [0.1]
+ ret.lateralTuning.pid.kf = 0.00007818594
+
+ # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary.
+ # See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891
+ for fw in car_fw:
+ 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):
ret.wheelbase = 2.67 # Average between 2.70 for sedan and 2.64 for hatchback
diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py
index bb9672fb0e..fd0a54b349 100644
--- a/selfdrive/car/toyota/values.py
+++ b/selfdrive/car/toyota/values.py
@@ -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
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}
EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.CHRH_TSS2, CAR.COROLLAH_TSS2,
diff --git a/selfdrive/locationd/SConscript b/selfdrive/locationd/SConscript
index 740f827a49..a6febe0170 100644
--- a/selfdrive/locationd/SConscript
+++ b/selfdrive/locationd/SConscript
@@ -7,8 +7,4 @@ locationd_sources = ["locationd.cc", "models/live_kf.cc", ekf_sym_cc]
lenv = env.Clone()
lenv["_LIBFLAGS"] += f' {libkf[0].get_labspath()}'
locationd = lenv.Program("locationd", locationd_sources, LIBS=loc_libs + transformations)
-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)
\ No newline at end of file
+lenv.Depends(locationd, libkf)
\ No newline at end of file
diff --git a/selfdrive/locationd/liblocationd.cc b/selfdrive/locationd/liblocationd.cc
deleted file mode 100644
index e2b85d93a1..0000000000
--- a/selfdrive/locationd/liblocationd.cc
+++ /dev/null
@@ -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 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();
- }
-
-}
diff --git a/selfdrive/locationd/test/_test_locationd_lib.py b/selfdrive/locationd/test/_test_locationd_lib.py
deleted file mode 100755
index bf4fcbdbe9..0000000000
--- a/selfdrive/locationd/test/_test_locationd_lib.py
+++ /dev/null
@@ -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()
-
diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py
new file mode 100755
index 0000000000..f08fe72ff1
--- /dev/null
+++ b/selfdrive/locationd/test/test_locationd_scenarios.py
@@ -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()
diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc
index bd43c337bd..e7edc0ecb9 100644
--- a/tools/cabana/binaryview.cc
+++ b/tools/cabana/binaryview.cc
@@ -280,7 +280,7 @@ void BinaryViewModel::refresh() {
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];
if (item.val != val || item.bg_color != color) {
item.val = val;
@@ -307,7 +307,7 @@ void BinaryViewModel::updateState() {
for (int i = 0; i < binary.size(); ++i) {
for (int j = 0; j < 8; ++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
double offset = !item.sigs.empty() ? 50 : 0;
auto n = last_msg.bit_change_counts[i][7 - j];
@@ -317,7 +317,7 @@ void BinaryViewModel::updateState() {
color.setAlpha(alpha);
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);
hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
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 {
@@ -392,7 +399,9 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
} else if (!item->valid) {
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) {
painter->setFont(small_font);
painter->drawText(option.rect.adjusted(8, 0, -8, -3), Qt::AlignRight | Qt::AlignBottom, item->is_msb ? "M" : "L");
diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h
index 04e1d5b2af..584910dc83 100644
--- a/tools/cabana/binaryview.h
+++ b/tools/cabana/binaryview.h
@@ -19,6 +19,8 @@ public:
void drawSignalCell(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index, const cabana::Signal *sig) const;
QFont small_font, hex_font;
+ std::array hex_text_table;
+ std::array bin_text_table;
};
class BinaryViewModel : public QAbstractTableModel {
@@ -26,7 +28,7 @@ public:
BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {}
void refresh();
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 data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; }
@@ -42,7 +44,7 @@ public:
QColor bg_color = QColor(102, 86, 169, 255);
bool is_msb = false;
bool is_lsb = false;
- QString val;
+ uint8_t val;
QList sigs;
bool valid = false;
};
diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc
index 3ee47ae0c6..93fbbaeae3 100644
--- a/tools/cabana/chart/chart.cc
+++ b/tools/cabana/chart/chart.cc
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -25,7 +26,8 @@
const int AXIS_X_TOP_MARGIN = 4;
static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; }
-ChartView::ChartView(const std::pair &x_range, ChartsWidget *parent) : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) {
+ChartView::ChartView(const std::pair &x_range, ChartsWidget *parent)
+ : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) {
series_type = (SeriesType)settings.chart_series_type;
QChart *chart = new QChart();
chart->setBackgroundVisible(false);
@@ -150,6 +152,11 @@ void ChartView::removeIf(std::function predicate) {
void ChartView::signalUpdated(const cabana::Signal *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();
updateSeries(sig);
}
@@ -280,7 +287,6 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) {
s.step_vals.clear();
s.last_value_mono_time = 0;
}
- s.series->setColor(s.sig->color);
const auto &msgs = can->events(s.msg_id);
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) {
drawTimeline(painter);
+ drawSignalValue(painter);
// draw track points
painter->setPen(Qt::NoPen);
qreal track_line_x = -1;
@@ -718,23 +725,24 @@ void ChartView::drawRubberBandTimeRange(QPainter *painter) {
void ChartView::drawTimeline(QPainter *painter) {
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());
painter->setPen(QPen(chart()->titleBrush().color(), 2));
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);
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;
path.addRoundedRect(time_str_rect, 3, 3);
painter->fillPath(path, settings.theme == DARK_THEME ? Qt::darkGray : Qt::gray);
painter->setPen(palette().color(QPalette::BrightText));
painter->setFont(axis_x->labelsFont());
painter->drawText(time_str_rect, Qt::AlignCenter, time_str);
+}
- // draw signal value
+void ChartView::drawSignalValue(QPainter *painter) {
auto item_group = qgraphicsitem_cast(chart()->legend()->childItems()[0]);
assert(item_group != nullptr);
auto legend_markers = item_group->childItems();
@@ -786,6 +794,7 @@ QXYSeries *ChartView::createSeries(SeriesType type, QColor color) {
}
void ChartView::addSeries(QXYSeries *series) {
+ setSeriesColor(series, series->color());
chart()->addSeries(series);
series->attachAxis(axis_x);
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(s)->color().hueF()) < 0.1) {
+ // use different color to distinguish it from others.
+ auto last_color = qobject_cast(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) {
if (type != series_type) {
series_type = type;
diff --git a/tools/cabana/chart/chart.h b/tools/cabana/chart/chart.h
index 71cf7058ab..de2a1b4510 100644
--- a/tools/cabana/chart/chart.h
+++ b/tools/cabana/chart/chart.h
@@ -83,11 +83,13 @@ private:
void drawForeground(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 drawSignalValue(QPainter *painter);
void drawTimeline(QPainter *painter);
void drawRubberBandTimeRange(QPainter *painter);
std::tuple getNiceAxisNumbers(qreal min, qreal max, int tick_count);
qreal niceNumber(qreal x, bool ceiling);
QXYSeries *createSeries(SeriesType type, QColor color);
+ void setSeriesColor(QXYSeries *, QColor color);
void updateSeriesPoints();
void removeIf(std::function predicate);
inline void clearTrackPoints() { for (auto &s : sigs) s.track_pt = {}; }
diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc
index d4ad9d48ad..83991d914f 100644
--- a/tools/cabana/chart/chartswidget.cc
+++ b/tools/cabana/chart/chartswidget.cc
@@ -277,6 +277,10 @@ void ChartsWidget::splitChart(ChartView *src_chart) {
for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) {
auto c = createChart();
src_chart->chart()->removeSeries(it->series);
+
+ // Restore to the original color
+ it->series->setColor(it->sig->color);
+
c->addSeries(it->series);
c->sigs.push_back(*it);
c->updateAxisY();
@@ -285,6 +289,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) {
}
src_chart->updateAxisY();
src_chart->updateTitle();
+ QTimer::singleShot(0, src_chart, &ChartView::resetChartCache);
}
}
diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc
index cf48abb4c9..52861723f4 100644
--- a/tools/cabana/commands.cc
+++ b/tools/cabana/commands.cc
@@ -4,11 +4,13 @@
// EditMsgCommand
-EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &comment, QUndoCommand *parent)
- : id(id), new_name(name), new_size(size), new_comment(comment), QUndoCommand(parent) {
+EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size,
+ 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)) {
old_name = msg->name;
old_size = msg->size;
+ old_node = msg->transmitter;
old_comment = msg->comment;
setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address));
} else {
@@ -20,11 +22,11 @@ void EditMsgCommand::undo() {
if (old_name.isEmpty())
dbc()->removeMsg(id);
else
- dbc()->updateMsg(id, old_name, old_size, old_comment);
+ dbc()->updateMsg(id, old_name, old_size, old_node, old_comment);
}
void EditMsgCommand::redo() {
- dbc()->updateMsg(id, new_name, new_size, new_comment);
+ dbc()->updateMsg(id, new_name, new_size, new_node, new_comment);
}
// RemoveMsgCommand
@@ -38,7 +40,7 @@ RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) :
void RemoveMsgCommand::undo() {
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())
dbc()->addSignal(id, *s);
}
@@ -64,7 +66,7 @@ void AddSigCommand::undo() {
void AddSigCommand::redo() {
if (auto msg = dbc()->msg(id); !msg) {
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.max = std::pow(2, signal.size) - 1;
diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h
index c7f59c4c7f..0736d9b83f 100644
--- a/tools/cabana/commands.h
+++ b/tools/cabana/commands.h
@@ -10,13 +10,14 @@
class EditMsgCommand : public QUndoCommand {
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 redo() override;
private:
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;
};
diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc
index 537749e48d..a0e523d666 100644
--- a/tools/cabana/dbc/dbc.cc
+++ b/tools/cabana/dbc/dbc.cc
@@ -78,6 +78,9 @@ QString cabana::Msg::newSignalName() {
}
void cabana::Msg::update() {
+ if (transmitter.isEmpty()) {
+ transmitter = DEFAULT_NODE_NAME;
+ }
mask.assign(size, 0x00);
multiplexor = nullptr;
@@ -125,6 +128,9 @@ void cabana::Msg::update() {
void cabana::Signal::update() {
updateMsbLsb(*this);
+ if (receiver_name.isEmpty()) {
+ receiver_name = DEFAULT_NODE_NAME;
+ }
float h = 19 * (float)lsb / 64.0;
h = fmod(h, 1.0);
diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h
index e44bc41abf..bfb26c2842 100644
--- a/tools/cabana/dbc/dbc.h
+++ b/tools/cabana/dbc/dbc.h
@@ -13,6 +13,7 @@
#include "opendbc/can/common_dbc.h"
const QString UNTITLED = "untitled";
+const QString DEFAULT_NODE_NAME = "XXX";
struct MessageId {
uint8_t source = 0;
diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc
index 2f93c1543e..063f516ead 100644
--- a/tools/cabana/dbc/dbcfile.cc
+++ b/tools/cabana/dbc/dbcfile.cc
@@ -60,11 +60,12 @@ bool DBCFile::writeContents(const QString &fn) {
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];
m.address = id.address;
m.name = name;
m.size = size;
+ m.transmitter = node.isEmpty() ? DEFAULT_NODE_NAME : node;
m.comment = comment;
}
@@ -198,7 +199,8 @@ void DBCFile::parse(const QString &content) {
QString DBCFile::generateDBC() {
QString dbc_string, signal_comment, message_comment, val_desc;
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()) {
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->max))
.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()) {
signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment);
}
diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h
index 78a73d58e4..a3ab1cebe4 100644
--- a/tools/cabana/dbc/dbcfile.h
+++ b/tools/cabana/dbc/dbcfile.h
@@ -22,7 +22,7 @@ public:
void cleanupAutoSaveFile();
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 const std::map &getMessages() const { return msgs; }
diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc
index 5736ac1e89..459ca0111d 100644
--- a/tools/cabana/dbc/dbcmanager.cc
+++ b/tools/cabana/dbc/dbcmanager.cc
@@ -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);
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);
}
diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h
index 3ac0829487..5f782fc930 100644
--- a/tools/cabana/dbc/dbcmanager.h
+++ b/tools/cabana/dbc/dbcmanager.h
@@ -28,7 +28,7 @@ public:
void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig);
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);
QString newMsgName(const MessageId &id);
diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc
index 2f6e9cbfe8..d70aeff0c8 100644
--- a/tools/cabana/detailwidget.cc
+++ b/tools/cabana/detailwidget.cc
@@ -2,7 +2,6 @@
#include
#include
-#include
#include "tools/cabana/commands.h"
#include "tools/cabana/mainwin.h"
@@ -135,11 +134,12 @@ void DetailWidget::refresh() {
for (auto s : binary_view->getOverlappingSignals()) {
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name));
}
+ name_label->setText(QString("%1 (%2)").arg(msgName(msg_id), msg->transmitter));
} else {
warnings.push_back(tr("Drag-Select in binary view to create new signal."));
+ name_label->setText(msgName(msg_id));
}
remove_btn->setEnabled(msg != nullptr);
- name_label->setText(msgName(msg_id));
if (!warnings.isEmpty()) {
warning_label->setText(warnings.join('\n'));
@@ -164,8 +164,8 @@ void DetailWidget::editMsg() {
int size = msg ? msg->size : can->lastMessage(msg_id).dat.size();
EditMessageDialog dlg(msg_id, msgName(msg_id), size, this);
if (dlg.exec()) {
- UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(),
- dlg.size_spin->value(), dlg.comment_edit->toPlainText().trimmed()));
+ UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), dlg.size_spin->value(),
+ 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);
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));
- 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?
size_spin->setMinimum(1);
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(btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel));
+
if (auto msg = dbc()->msg(msg_id)) {
+ node->setText(msg->transmitter);
comment_edit->setText(msg->comment);
}
-
- btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
validateName(name_edit->text());
- form_layout->addRow(btn_box);
-
setFixedWidth(parent->width() * 0.9);
connect(name_edit, &QLineEdit::textEdited, this, &EditMessageDialog::validateName);
connect(btn_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h
index 10c6a6c46e..ed6a865f53 100644
--- a/tools/cabana/detailwidget.h
+++ b/tools/cabana/detailwidget.h
@@ -21,6 +21,7 @@ public:
QString original_name;
QDialogButtonBox *btn_box;
QLineEdit *name_edit;
+ QLineEdit *node;
QTextEdit *comment_edit;
QLabel *error_label;
QSpinBox *size_spin;
diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc
index d3caf492a6..5cd5e70267 100644
--- a/tools/cabana/mainwin.cc
+++ b/tools/cabana/mainwin.cc
@@ -439,11 +439,11 @@ void MainWindow::saveFile(DBCFile *dbc_file) {
if (!dbc_file->filename.isEmpty()) {
dbc_file->save();
updateLoadSaveMenus();
+ UndoStack::instance()->setClean();
+ statusBar()->showMessage(tr("File saved"), 2000);
} else if (!dbc_file->isEmpty()) {
saveFileAs(dbc_file);
}
- UndoStack::instance()->setClean();
- statusBar()->showMessage(tr("File saved"), 2000);
}
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)"));
if (!fn.isEmpty()) {
dbc_file->saveAs(fn);
+ UndoStack::instance()->setClean();
+ statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000);
updateRecentFiles(fn);
updateLoadSaveMenus();
}
@@ -606,7 +608,13 @@ void MainWindow::closeEvent(QCloseEvent *event) {
settings.video_splitter_state = video_splitter->saveState();
}
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);
}
diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc
index 027dcb903f..ee345c490c 100644
--- a/tools/cabana/settings.cc
+++ b/tools/cabana/settings.cc
@@ -6,14 +6,13 @@
#include
#include
#include
-#include
#include
#include "tools/cabana/util.h"
Settings settings;
-void Settings::save() {
+QSettings::Status Settings::save() {
QSettings s(filePath(), QSettings::IniFormat);
s.setValue("fps", fps);
s.setValue("max_cached_minutes", max_cached_minutes);
@@ -35,6 +34,8 @@ void Settings::save() {
s.setValue("log_path", log_path);
s.setValue("drag_direction", drag_direction);
s.setValue("suppress_defined_signals", suppress_defined_signals);
+ s.sync();
+ return s.status();
}
void Settings::load() {
diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h
index f9eaa8ffad..42073a72de 100644
--- a/tools/cabana/settings.h
+++ b/tools/cabana/settings.h
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#define LIGHT_THEME 1
@@ -24,7 +25,7 @@ public:
};
Settings() {}
- void save();
+ QSettings::Status save();
void load();
inline static QString filePath() { return QApplication::applicationDirPath() + "/settings"; }
diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc
index 1b1c8dac22..b2798e9f3b 100644
--- a/tools/cabana/signalview.cc
+++ b/tools/cabana/signalview.cc
@@ -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) {
Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig};
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"};
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)});
@@ -134,6 +134,7 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const {
case Item::Sig: return item->sig_val;
case Item::Name: return item->sig->name;
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::MultiplexValue: return item->sig->multiplex_value;
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) {
case Item::Name: s.name = value.toString(); 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::MultiplexValue: s.multiplex_value = value.toInt(); 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->parent->extra_expanded) {
item->parent->extra_expanded = true;
- beginInsertRows(index.parent(), 7, 13);
+ beginInsertRows(index.parent(), Item::ExtraInfo - 2, Item::Desc - 2);
endInsertRows();
} else {
item->parent->extra_expanded = false;
- beginRemoveRows(index.parent(), 7, 13);
+ beginRemoveRows(index.parent(), Item::ExtraInfo - 2, Item::Desc - 2);
endRemoveRows();
}
}
@@ -267,6 +269,7 @@ void SignalModel::handleSignalRemoved(const cabana::Signal *sig) {
SignalItemDelegate::SignalItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
name_validator = new NameValidator(this);
+ node_validator = new QRegExpValidator(QRegExp("^\\w+(,\\w+)*$"), this);
double_validator = new DoubleValidator(this);
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 {
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::Min || item->type == SignalModel::Item::Max) {
QLineEdit *e = new QLineEdit(parent);
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) {
QCompleter *completer = new QCompleter(dbc()->signalNames());
@@ -395,7 +400,6 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie
completer->setFilterMode(Qt::MatchContains);
e->setCompleter(completer);
}
-
return e;
} else if (item->type == SignalModel::Item::Size) {
QSpinBox *spin = new QSpinBox(parent);
diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h
index 9d8571d0b8..bcf0019bc4 100644
--- a/tools/cabana/signalview.h
+++ b/tools/cabana/signalview.h
@@ -17,7 +17,7 @@ class SignalModel : public QAbstractItemModel {
Q_OBJECT
public:
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); }
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 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;
const int color_label_width = 18;
mutable QSize button_size;
diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h
index eae34e2cc4..3a89d4e57d 100644
--- a/tools/cabana/streams/abstractstream.h
+++ b/tools/cabana/streams/abstractstream.h
@@ -30,7 +30,7 @@ struct CanData {
std::vector> bit_change_counts;
std::vector last_delta;
std::vector same_delta_counter;
- double last_freq_update_ts = seconds_since_boot();
+ double last_freq_update_ts = 0;
};
struct CanEvent {
diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc
index 951856e32d..4c21530774 100644
--- a/tools/cabana/util.cc
+++ b/tools/cabana/util.cc
@@ -1,7 +1,6 @@
#include "tools/cabana/util.h"
#include
-#include
#include
#include
#include
@@ -56,6 +55,10 @@ std::pair SegmentTree::get_minmax(int n, int left, int right, in
MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) : multiple_lines(multiple_lines), QStyledItemDelegate(parent) {
fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
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 {
@@ -107,7 +110,7 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
} else if (option.state & QStyle::State_Selected) {
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->setPen(old_pen);
diff --git a/tools/cabana/util.h b/tools/cabana/util.h
index 9e93e74833..2c1bc5cf7b 100644
--- a/tools/cabana/util.h
+++ b/tools/cabana/util.h
@@ -1,5 +1,6 @@
#pragma once
+#include
#include
#include
#include
@@ -10,8 +11,10 @@
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -75,6 +78,7 @@ public:
int widthForBytes(int n) const;
private:
+ std::array hex_text_table;
QFont fixed_font;
QSize byte_size = {};
bool multiple_lines = false;
@@ -102,6 +106,10 @@ void setTheme(int theme);
inline QString formatSeconds(int seconds) {
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 {
diff --git a/tools/lib/auth_config.py b/tools/lib/auth_config.py
index 8863dd57a2..bd76761043 100644
--- a/tools/lib/auth_config.py
+++ b/tools/lib/auth_config.py
@@ -13,9 +13,6 @@ if PC:
else:
CONFIG_DIR = "/tmp/.comma"
-mkdirs_exists_ok(CONFIG_DIR)
-
-
def get_token():
try:
with open(os.path.join(CONFIG_DIR, 'auth.json')) as f:
@@ -26,9 +23,13 @@ def get_token():
def set_token(token):
+ mkdirs_exists_ok(CONFIG_DIR)
with open(os.path.join(CONFIG_DIR, 'auth.json'), 'w') as f:
json.dump({'access_token': token}, f)
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
diff --git a/tools/lib/cache.py b/tools/lib/cache.py
index d142955e59..fd214f6bb5 100644
--- a/tools/lib/cache.py
+++ b/tools/lib/cache.py
@@ -2,10 +2,10 @@ import os
import urllib.parse
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):
- dir_ = os.path.join(DEFAULT_CACHE_DIR, "local")
+def cache_path_for_file_path(fn, cache_dir=DEFAULT_CACHE_DIR):
+ dir_ = os.path.join(cache_dir, "local")
mkdirs_exists_ok(dir_)
fn_parsed = urllib.parse.urlparse(fn)
if fn_parsed.scheme == '':
diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py
index cbc9310790..06fee6857d 100644
--- a/tools/lib/framereader.py
+++ b/tools/lib/framereader.py
@@ -12,7 +12,7 @@ import numpy as np
from lru import LRU
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.common.file_helpers import atomic_write_in_dir
@@ -106,8 +106,8 @@ def cache_fn(func):
if kwargs.pop('no_cache', None):
cache_path = None
else:
- cache_prefix = kwargs.pop('cache_prefix', None)
- cache_path = cache_path_for_file_path(fn, cache_prefix)
+ cache_dir = kwargs.pop('cache_dir', DEFAULT_CACHE_DIR)
+ cache_path = cache_path_for_file_path(fn, cache_dir)
if cache_path and os.path.exists(cache_path):
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."""
if len(camera_paths) < 1:
raise ValueError("must provide at least one video to index")
frame_type = fingerprint_video(camera_paths[0])
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):
- cache_path = cache_path_for_file_path(fn, cache_prefix)
+def index_video(fn, frame_type=None, cache_dir=DEFAULT_CACHE_DIR):
+ cache_path = cache_path_for_file_path(fn, cache_dir)
if os.path.exists(cache_path):
return
@@ -160,16 +160,16 @@ def index_video(fn, frame_type=None, cache_prefix=None):
frame_type = fingerprint_video(fn[0])
if frame_type == FrameType.h265_stream:
- index_stream(fn, "hevc", cache_prefix=cache_prefix)
+ index_stream(fn, "hevc", cache_dir=cache_dir)
else:
raise NotImplementedError("Only h265 supported")
-def get_video_index(fn, frame_type, cache_prefix=None):
- cache_path = cache_path_for_file_path(fn, cache_prefix)
+def get_video_index(fn, frame_type, cache_dir=DEFAULT_CACHE_DIR):
+ cache_path = cache_path_for_file_path(fn, cache_dir)
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):
return None
@@ -284,13 +284,13 @@ class BaseFrameReader:
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)
if frame_type == FrameType.raw:
return RawFrameReader(fn)
elif frame_type in (FrameType.h265_stream,):
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)
else:
raise NotImplementedError(frame_type)
diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc
index 393a561c0f..05c0ca8ac4 100644
--- a/tools/replay/tests/test_replay.cc
+++ b/tools/replay/tests/test_replay.cc
@@ -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_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 {
if (httpDownload(url, local_file, chunk_size)) {
return true;
@@ -29,7 +29,7 @@ TEST_CASE("httpMultiPartDownload") {
const size_t chunk_size = 5 * 1024 * 1024;
std::string content;
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);
}
SECTION("download to buffer") {
@@ -110,23 +110,36 @@ void read_segment(int n, const SegmentFile &segment_file, uint32_t flags) {
loop.exec();
}
-TEST_CASE("Route") {
- // Create a local route from remote for testing
- Route remote_route(DEMO_ROUTE);
- REQUIRE(remote_route.load());
- char tmp_path[] = "/tmp/root_XXXXXX";
- const std::string data_dir = mkdtemp(tmp_path);
- const std::string route_name = DEMO_ROUTE.mid(17).toStdString();
- for (int i = 0; i < 2; ++i) {
- std::string log_path = util::string_format("%s/%s--%d/", data_dir.c_str(), route_name.c_str(), i);
- util::create_directories(log_path, 0755);
- REQUIRE(donload_to_file(remote_route.at(i).rlog.toStdString(), log_path + "rlog.bz2"));
- REQUIRE(donload_to_file(remote_route.at(i).road_cam.toStdString(), log_path + "fcamera.hevc"));
- REQUIRE(donload_to_file(remote_route.at(i).driver_cam.toStdString(), log_path + "dcamera.hevc"));
- REQUIRE(donload_to_file(remote_route.at(i).wide_road_cam.toStdString(), log_path + "ecamera.hevc"));
- REQUIRE(donload_to_file(remote_route.at(i).qcamera.toStdString(), log_path + "qcamera.ts"));
+std::string download_demo_route() {
+ static std::string data_dir;
+
+ if (data_dir == "") {
+ char tmp_path[] = "/tmp/root_XXXXXX";
+ data_dir = mkdtemp(tmp_path);
+
+ Route remote_route(DEMO_ROUTE);
+ assert(remote_route.load());
+
+ // Create a local route from remote for testing
+ const std::string route_name = DEMO_ROUTE.mid(17).toStdString();
+ for (int i = 0; i < 2; ++i) {
+ std::string log_path = util::string_format("%s/%s--%d/", data_dir.c_str(), route_name.c_str(), i);
+ 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") {
auto flags = GENERATE(REPLAY_FLAG_DCAM | REPLAY_FLAG_ECAM, REPLAY_FLAG_QCAMERA);
Route route(DEMO_ROUTE, QString::fromStdString(data_dir));
diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh
index d5922a4819..361f7b18ea 100755
--- a/tools/sim/launch_openpilot.sh
+++ b/tools/sim/launch_openpilot.sh
@@ -12,5 +12,8 @@ if [[ "$CI" ]]; then
export BLOCK="${BLOCK},ui"
fi
+SCRIPT_DIR=$(dirname "$0")
+OPENPILOT_DIR=$SCRIPT_DIR/../../
+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
-cd ../../selfdrive/manager && exec ./manager.py
+cd $OPENPILOT_DIR/selfdrive/manager && exec ./manager.py