diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 0a55843cfd..8b5b165536 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -54,6 +54,7 @@ jobs:
cp -r ../build/docs/html/ docs/
touch docs/.nojekyll
+ echo -n docs.comma.ai > docs/CNAME
git add -f .
git commit -m "build docs"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index eb938f1bf4..880912e271 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -41,7 +41,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.1.0
+ rev: v0.1.1
hooks:
- id: ruff
exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)'
diff --git a/RELEASES.md b/RELEASES.md
index 6adc58197b..96028a9787 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -2,6 +2,7 @@ Version 0.9.5 (2023-XX-XX)
========================
* New driving model
* Improved navigate on openpilot performance using navigation instructions as an additional model input
+* Cadillac Escalade ESV 2019 support thanks to twilsonco!
* Hyundai Azera 2022 support thanks to sunnyhaibin!
* Hyundai Azera Hybrid 2020 support thanks to chanhojung and haram-KONA!
* Hyundai Custin 2023 support thanks to sunnyhaibin and Saber422!
diff --git a/SConstruct b/SConstruct
index 2889460cd7..9a18050122 100644
--- a/SConstruct
+++ b/SConstruct
@@ -383,7 +383,6 @@ SConscript(['rednose/SConscript'])
# Build system services
SConscript([
- 'system/clocksd/SConscript',
'system/proclogd/SConscript',
'system/ubloxd/SConscript',
'system/loggerd/SConscript',
diff --git a/common/prefix.py b/common/prefix.py
index 6b7e7e2fd7..c1744e8ff7 100644
--- a/common/prefix.py
+++ b/common/prefix.py
@@ -14,6 +14,7 @@ class OpenpilotPrefix:
self.clean_dirs_on_exit = clean_dirs_on_exit
def __enter__(self):
+ self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None)
os.environ['OPENPILOT_PREFIX'] = self.prefix
try:
os.mkdir(self.msgq_path)
@@ -28,6 +29,8 @@ class OpenpilotPrefix:
self.clean_dirs()
try:
del os.environ['OPENPILOT_PREFIX']
+ if self.original_prefix is not None:
+ os.environ['OPENPILOT_PREFIX'] = self.original_prefix
except KeyError:
pass
return False
diff --git a/conftest.py b/conftest.py
index 2cc906b919..ee46436d3c 100644
--- a/conftest.py
+++ b/conftest.py
@@ -10,8 +10,13 @@ def openpilot_function_fixture():
# setup a clean environment for each test
with OpenpilotPrefix():
+ prefix = os.environ["OPENPILOT_PREFIX"]
+
yield
+ # ensure the test doesn't change the prefix
+ assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"]
+
os.environ.clear()
os.environ.update(starting_env)
diff --git a/docs/CARS.md b/docs/CARS.md
index fec2f8b840..d6961f6f56 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.
-# 265 Supported Cars
+# 267 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|
Hardware Needed
|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -20,6 +20,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Buick|LaCrosse 2017-19[4](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[](##)|[](##)|Parts
- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[](##)|[](##)|Parts
- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Cadillac|Escalade ESV 2016[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[](##)|[](##)|Parts
- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Cadillac|Escalade ESV 2019[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[](##)|[](##)|Parts
- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts
- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts
- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[](##)|[](##)|Parts
- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
@@ -74,7 +75,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 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 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Parts
- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Parts
- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 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 3X
- 1 comma power v2
- 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 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Hyundai|Elantra Hybrid 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 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
diff --git a/poetry.lock b/poetry.lock
index c27810076a..93e907a578 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -314,13 +314,13 @@ files = [
[[package]]
name = "azure-core"
-version = "1.29.4"
+version = "1.29.5"
description = "Microsoft Azure Core Library for Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "azure-core-1.29.4.tar.gz", hash = "sha256:500b3aa9bf2e90c5ccc88bb105d056114ca0ce7d0ce73afb8bc4d714b2fc7568"},
- {file = "azure_core-1.29.4-py3-none-any.whl", hash = "sha256:b03261bcba22c0b9290faf9999cedd23e849ed2577feee90515694cea6bc74bf"},
+ {file = "azure-core-1.29.5.tar.gz", hash = "sha256:52983c89d394c6f881a121e5101c5fa67278ca3b1f339c8fb2ef39230c70e9ac"},
+ {file = "azure_core-1.29.5-py3-none-any.whl", hash = "sha256:0fa04b7b1f7d44a4fb8468c4093deb2ea01fdf4faddbf802ed9205615f99d68c"},
]
[package.dependencies]
@@ -370,13 +370,13 @@ aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"]
[[package]]
name = "babel"
-version = "2.13.0"
+version = "2.13.1"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.7"
files = [
- {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"},
- {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"},
+ {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"},
+ {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"},
]
[package.extras]
@@ -554,101 +554,101 @@ files = [
[[package]]
name = "charset-normalizer"
-version = "3.3.0"
+version = "3.3.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
files = [
- {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"},
- {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"},
- {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"},
- {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"},
- {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"},
- {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"},
- {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"},
- {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"},
+ {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"},
+ {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"},
+ {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"},
+ {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"},
+ {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"},
+ {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"},
+ {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"},
+ {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"},
]
[[package]]
@@ -4197,28 +4197,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "ruff"
-version = "0.1.0"
+version = "0.1.1"
description = "An extremely fast Python linter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.1.0-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:87114e254dee35e069e1b922d85d4b21a5b61aec759849f393e1dbb308a00439"},
- {file = "ruff-0.1.0-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:764f36d2982cc4a703e69fb73a280b7c539fd74b50c9ee531a4e3fe88152f521"},
- {file = "ruff-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65f4b7fb539e5cf0f71e9bd74f8ddab74cabdd673c6fb7f17a4dcfd29f126255"},
- {file = "ruff-0.1.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:299fff467a0f163baa282266b310589b21400de0a42d8f68553422fa6bf7ee01"},
- {file = "ruff-0.1.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d412678bf205787263bb702c984012a4f97e460944c072fd7cfa2bd084857c4"},
- {file = "ruff-0.1.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a5391b49b1669b540924640587d8d24128e45be17d1a916b1801d6645e831581"},
- {file = "ruff-0.1.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee8cd57f454cdd77bbcf1e11ff4e0046fb6547cac1922cc6e3583ce4b9c326d1"},
- {file = "ruff-0.1.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa7aeed7bc23861a2b38319b636737bf11cfa55d2109620b49cf995663d3e888"},
- {file = "ruff-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04cd4298b43b16824d9a37800e4c145ba75c29c43ce0d74cad1d66d7ae0a4c5"},
- {file = "ruff-0.1.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7186ccf54707801d91e6314a016d1c7895e21d2e4cd614500d55870ed983aa9f"},
- {file = "ruff-0.1.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d88adfd93849bc62449518228581d132e2023e30ebd2da097f73059900d8dce3"},
- {file = "ruff-0.1.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ad2ccdb3bad5a61013c76a9c1240fdfadf2c7103a2aeebd7bcbbed61f363138f"},
- {file = "ruff-0.1.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b77f6cfa72c6eb19b5cac967cc49762ae14d036db033f7d97a72912770fd8e1c"},
- {file = "ruff-0.1.0-py3-none-win32.whl", hash = "sha256:480bd704e8af1afe3fd444cc52e3c900b936e6ca0baf4fb0281124330b6ceba2"},
- {file = "ruff-0.1.0-py3-none-win_amd64.whl", hash = "sha256:a76ba81860f7ee1f2d5651983f87beb835def94425022dc5f0803108f1b8bfa2"},
- {file = "ruff-0.1.0-py3-none-win_arm64.whl", hash = "sha256:45abdbdab22509a2c6052ecf7050b3f5c7d6b7898dc07e82869401b531d46da4"},
- {file = "ruff-0.1.0.tar.gz", hash = "sha256:ad6b13824714b19c5f8225871cf532afb994470eecb74631cd3500fe817e6b3f"},
+ {file = "ruff-0.1.1-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b7cdc893aef23ccc14c54bd79a8109a82a2c527e11d030b62201d86f6c2b81c5"},
+ {file = "ruff-0.1.1-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:620d4b34302538dbd8bbbe8fdb8e8f98d72d29bd47e972e2b59ce6c1e8862257"},
+ {file = "ruff-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a909d3930afdbc2e9fd893b0034479e90e7981791879aab50ce3d9f55205bd6"},
+ {file = "ruff-0.1.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3305d1cb4eb8ff6d3e63a48d1659d20aab43b49fe987b3ca4900528342367145"},
+ {file = "ruff-0.1.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c34ae501d0ec71acf19ee5d4d889e379863dcc4b796bf8ce2934a9357dc31db7"},
+ {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6aa7e63c3852cf8fe62698aef31e563e97143a4b801b57f920012d0e07049a8d"},
+ {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d68367d1379a6b47e61bc9de144a47bcdb1aad7903bbf256e4c3d31f11a87ae"},
+ {file = "ruff-0.1.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc11955f6ce3398d2afe81ad7e49d0ebf0a581d8bcb27b8c300281737735e3a3"},
+ {file = "ruff-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd8eead88ea83a250499074e2a8e9d80975f0b324b1e2e679e4594da318c25"},
+ {file = "ruff-0.1.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f4780e2bb52f3863a565ec3f699319d3493b83ff95ebbb4993e59c62aaf6e75e"},
+ {file = "ruff-0.1.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8f5b24daddf35b6c207619301170cae5d2699955829cda77b6ce1e5fc69340df"},
+ {file = "ruff-0.1.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d3f9ac658ba29e07b95c80fa742b059a55aefffa8b1e078bc3c08768bdd4b11a"},
+ {file = "ruff-0.1.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3521bf910104bf781e6753282282acc145cbe3eff79a1ce6b920404cd756075a"},
+ {file = "ruff-0.1.1-py3-none-win32.whl", hash = "sha256:ba3208543ab91d3e4032db2652dcb6c22a25787b85b8dc3aeff084afdc612e5c"},
+ {file = "ruff-0.1.1-py3-none-win_amd64.whl", hash = "sha256:3ff3006c97d9dc396b87fb46bb65818e614ad0181f059322df82bbfe6944e264"},
+ {file = "ruff-0.1.1-py3-none-win_arm64.whl", hash = "sha256:e140bd717c49164c8feb4f65c644046fe929c46f42493672853e3213d7bdbce2"},
+ {file = "ruff-0.1.1.tar.gz", hash = "sha256:c90461ae4abec261609e5ea436de4a4b5f2822921cf04c16d2cc9327182dbbcc"},
]
[[package]]
@@ -5024,13 +5024,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "virtualenv"
-version = "20.24.5"
+version = "20.24.6"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
- {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"},
- {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"},
+ {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"},
+ {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"},
]
[package.dependencies]
diff --git a/release/files_common b/release/files_common
index 2b8e231124..9c03528164 100644
--- a/release/files_common
+++ b/release/files_common
@@ -140,10 +140,6 @@ selfdrive/car/tesla/*.py
selfdrive/car/toyota/*.py
selfdrive/car/volkswagen/*.py
-system/clocksd/.gitignore
-system/clocksd/SConscript
-system/clocksd/clocksd.cc
-
selfdrive/debug/can_printer.py
selfdrive/debug/check_freq.py
selfdrive/debug/dump.py
@@ -253,7 +249,6 @@ system/ubloxd/*.cc
selfdrive/locationd/__init__.py
selfdrive/locationd/SConscript
selfdrive/locationd/.gitignore
-selfdrive/locationd/laikad.py
selfdrive/locationd/locationd.h
selfdrive/locationd/locationd.cc
selfdrive/locationd/paramsd.py
diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py
index df6900f5ff..2e03dc39f5 100755
--- a/selfdrive/car/gm/interface.py
+++ b/selfdrive/car/gm/interface.py
@@ -198,17 +198,22 @@ class CarInterface(CarInterfaceBase):
ret.centerToFront = ret.wheelbase * 0.5
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
- elif candidate == CAR.ESCALADE_ESV:
+ elif candidate in (CAR.ESCALADE_ESV, CAR.ESCALADE_ESV_2019):
ret.minEnableSpeed = -1. # engage speed is decided by pcm
ret.mass = 2739.
ret.wheelbase = 3.302
ret.steerRatio = 17.3
ret.centerToFront = ret.wheelbase * 0.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]]
- ret.lateralTuning.pid.kf = 0.000045
ret.tireStiffnessFactor = 1.0
+ if candidate == CAR.ESCALADE_ESV:
+ ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]]
+ ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]]
+ ret.lateralTuning.pid.kf = 0.000045
+ else:
+ ret.steerActuatorDelay = 0.2
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
elif candidate == CAR.BOLT_EUV:
ret.mass = 1669.
ret.wheelbase = 2.63779
diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py
index 837d664dad..e9ec8de9c5 100644
--- a/selfdrive/car/gm/values.py
+++ b/selfdrive/car/gm/values.py
@@ -71,6 +71,7 @@ class CAR(StrEnum):
BUICK_REGAL = "BUICK REGAL ESSENCE 2018"
ESCALADE = "CADILLAC ESCALADE 2017"
ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016"
+ ESCALADE_ESV_2019 = "CADILLAC ESCALADE ESV 2019"
BOLT_EUV = "CHEVROLET BOLT EUV 2022"
SILVERADO = "CHEVROLET SILVERADO 1500 2020"
EQUINOX = "CHEVROLET EQUINOX 2019"
@@ -106,6 +107,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = {
CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"),
CAR.ESCALADE: GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"),
CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"),
+ CAR.ESCALADE_ESV_2019: GMCarInfo("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS"),
CAR.BOLT_EUV: [
GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"),
GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"),
@@ -198,6 +200,10 @@ FINGERPRINTS = {
{
309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8
}],
+ CAR.ESCALADE_ESV_2019: [
+ {
+ 715: 8, 840: 5, 717: 5, 869: 4, 880: 6, 289: 8, 454: 8, 842: 5, 460: 5, 463: 3, 801: 8, 170: 8, 190: 6, 241: 6, 201: 8, 417: 7, 211: 2, 419: 1, 398: 8, 426: 7, 487: 8, 442: 8, 451: 8, 452: 8, 453: 6, 479: 3, 311: 8, 500: 6, 647: 6, 193: 8, 707: 8, 197: 8, 209: 7, 199: 4, 455: 7, 313: 8, 481: 7, 485: 8, 489: 8, 249: 8, 393: 7, 407: 7, 413: 8, 422: 4, 431: 8, 501: 8, 499: 3, 810: 8, 508: 8, 381: 8, 462: 4, 532: 6, 562: 8, 386: 8, 761: 7, 573: 1, 554: 3, 719: 5, 560: 8, 1279: 4, 388: 8, 288: 5, 1005: 6, 497: 8, 844: 8, 961: 8, 967: 4, 977: 8, 979: 8, 985: 5, 1001: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 510: 8, 866: 4, 304: 1, 969: 8, 384: 4, 1033: 7, 1009: 8, 1034: 7, 1296: 4, 1930: 7, 1105: 5, 1013: 5, 1225: 7, 1919: 7, 320: 3, 534: 2, 352: 5, 298: 8, 1223: 2, 1233: 8, 608: 8, 1265: 8, 609: 6, 1267: 1, 1417: 8, 610: 6, 1906: 7, 611: 6, 612: 8, 613: 8, 208: 8, 564: 5, 309: 8, 1221: 5, 1280: 4, 1249: 8, 1907: 7, 1257: 6, 1300: 8, 1920: 7, 563: 5, 1322: 6, 1323: 4, 1328: 4, 1917: 7, 328: 1, 1912: 7, 1914: 7, 804: 3, 1918: 7
+ }],
CAR.BOLT_EUV: [
{
189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7
diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py
index 0c91bd8db9..2af41728c4 100644
--- a/selfdrive/car/hyundai/interface.py
+++ b/selfdrive/car/hyundai/interface.py
@@ -4,8 +4,8 @@ from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, HyundaiFlagsSP, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, \
- EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, NON_SCC_CAR, \
- Buttons
+ CANFD_UNSUPPORTED_LONGITUDINAL_CAR, NON_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, \
+ UNSUPPORTED_LONGITUDINAL_CAR, Buttons
from openpilot.selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR
from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
@@ -95,7 +95,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.90
ret.steerRatio = 15.6 * 1.15
ret.tireStiffnessFactor = 0.63
- elif candidate == CAR.ELANTRA:
+ elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30):
ret.mass = 1275.
ret.wheelbase = 2.7
ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535
@@ -270,7 +270,8 @@ class CarInterface(CarInterfaceBase):
if candidate in CANFD_CAR:
ret.longitudinalTuning.kpV = [0.1]
ret.longitudinalTuning.kiV = [0.0]
- ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in (CANFD_RADAR_SCC_CAR | NON_SCC_CAR)
+ ret.experimentalLongitudinalAvailable = (candidate in (HYBRID_CAR | EV_CAR) and candidate not in
+ (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR))
else:
ret.longitudinalTuning.kpV = [0.5]
ret.longitudinalTuning.kiV = [0.0]
diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py
index 5fa92624af..dbf2b38615 100755
--- a/selfdrive/car/hyundai/tests/test_hyundai.py
+++ b/selfdrive/car/hyundai/tests/test_hyundai.py
@@ -23,6 +23,7 @@ NO_DATES_PLATFORMS = {
CAR.TUCSON_HYBRID_4TH_GEN,
# CAN
CAR.ELANTRA,
+ CAR.ELANTRA_GT_I30,
CAR.KIA_CEED,
CAR.KIA_FORTE,
CAR.KIA_OPTIMA_G4,
@@ -99,7 +100,7 @@ class TestHyundaiFingerprint(unittest.TestCase):
def test_platform_code_ecus_available(self):
# TODO: add queries for these non-CAN FD cars to get EPS
no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H,
- CAR.KIA_OPTIMA_H_G4_FL, CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80}
+ CAR.KIA_OPTIMA_H_G4_FL, CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.ELANTRA}
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
for car_model, ecus in FW_VERSIONS.items():
diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py
index 93d5d1c27a..640d54b7b0 100644
--- a/selfdrive/car/hyundai/values.py
+++ b/selfdrive/car/hyundai/values.py
@@ -37,7 +37,7 @@ class CarControllerParams:
# To determine the limit for your car, find the maximum value that the stock LKAS will request.
# If the max stock LKAS request is <384, add your car to this list.
- elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.IONIQ,
+ elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.IONIQ,
CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV,
CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.KIA_SORENTO):
self.STEER_MAX = 255
@@ -79,6 +79,7 @@ class CAR(StrEnum):
AZERA_6TH_GEN = "HYUNDAI AZERA 6TH GEN"
AZERA_HEV_6TH_GEN = "HYUNDAI AZERA HYBRID 6TH GEN"
ELANTRA = "HYUNDAI ELANTRA 2017"
+ ELANTRA_GT_I30 = "HYUNDAI I30 N LINE 2019 & GT 2018 DCT"
ELANTRA_2021 = "HYUNDAI ELANTRA 2021"
ELANTRA_2022_NON_SCC = "HYUNDAI ELANTRA SE 2022 NON-SCC"
ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021"
@@ -172,7 +173,11 @@ 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.AZERA_HEV_6TH_GEN: HyundaiCarInfo("Hyundai Azera Hybrid 2020", "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])),
+ # TODO: 2017-18 could be Hyundai G
+ HyundaiCarInfo("Hyundai Elantra 2017-18", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])),
+ HyundaiCarInfo("Hyundai Elantra 2019", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_g])),
+ ],
+ CAR.ELANTRA_GT_I30: [
HyundaiCarInfo("Hyundai Elantra GT 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])),
HyundaiCarInfo("Hyundai i30 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])),
],
@@ -317,9 +322,6 @@ class Buttons:
CANCEL = 4 # on newer models, this is a pause/resume button
FINGERPRINTS = {
- CAR.ELANTRA: [{
- 66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 897: 8, 832: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1345: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2001: 8, 2003: 8, 2004: 8, 2009: 8, 2012: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
- }],
CAR.HYUNDAI_GENESIS: [{
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4
},
@@ -1791,6 +1793,33 @@ FW_VERSIONS = {
],
},
CAR.ELANTRA: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00ADP LKAS AT USA LHD 1.00 1.03 99211-F2000 X31',
+ b'\xf1\x00AD LKAS AT USA LHD 1.01 1.01 95895-F2000 251',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD(\xfcA\x9d',
+ b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD\x00\x00\x00\x00',
+ b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\x00\x00\x00\x00',
+ b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\xc5\x92\x9e\x8a',
+ b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20SS2.~\x90\x87',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'\xf1\x8161698051\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x8161657051\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x816165D051\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x816165E051\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x7d1, None): [
+ b'\xf1\x00AD ESC \x11 11 \x18\x05\x06 58910-F2840',
+ b'\xf1\x00AD ESC \x11 12 \x15\t\t 58920-F2810',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00AD__ SCC H-CUP 1.00 1.00 99110-F2100 ',
+ b'\xf1\x00AD__ SCC H-CUP 1.00 1.01 96400-F2100 ',
+ ],
+ },
+ CAR.ELANTRA_GT_I30: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54',
b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51',
@@ -2171,7 +2200,7 @@ CHECKSUM = {
CAN_GEARS = {
# which message has the gear. hybrid and EV use ELECT_GEAR
- "use_cluster_gears": {CAR.ELANTRA, CAR.KONA},
+ "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA},
"use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON},
}
@@ -2182,7 +2211,11 @@ CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCS
# 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,
- CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.IONIQ_6}
+ CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN}
+
+# These CAN FD cars do not accept communication control to disable the ADAS ECU,
+# responds with 0x7F2822 - 'conditions not correct'
+CANFD_UNSUPPORTED_LONGITUDINAL_CAR = {CAR.IONIQ_6, CAR.KONA_EV_2ND_GEN}
# The camera does SCC on these cars, rather than the radar
CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
@@ -2200,7 +2233,7 @@ EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR
# these cars require a special panda safety mode due to missing counters and checksums in the messages
LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_LTD, CAR.KIA_OPTIMA_G4,
CAR.VELOSTER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022,
- CAR.KIA_OPTIMA_H}
+ CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30}
# these cars have not been verified to work with longitudinal yet - radar disable, sending correct messages, etc.
UNSUPPORTED_LONGITUDINAL_CAR = LEGACY_SAFETY_MODE_CAR | {CAR.KIA_NIRO_PHEV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4_FL,
@@ -2217,6 +2250,7 @@ DBC = {
CAR.AZERA_6TH_GEN: dbc_dict('hyundai_kia_generic', None),
CAR.AZERA_HEV_6TH_GEN: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None),
+ CAR.ELANTRA_GT_I30: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_2022_NON_SCC: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None),
diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py
index 298e2bfec4..da5d760dfe 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,
]
@@ -61,6 +60,7 @@ routes = [
CarTestRoute("75a6bcb9b8b40373|2023-03-11--22-47-33", GM.BUICK_LACROSSE),
CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.ESCALADE),
CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV),
+ CarTestRoute("168f8b3be57f66ae|2023-09-12--21-44-42", GM.ESCALADE_ESV_2019),
CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT),
CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1),
CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.BOLT_EUV, segment=14), # Bolt EV
@@ -159,7 +159,7 @@ routes = [
CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE),
CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN),
CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA),
- CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA), # 2019 Elantra GT
+ CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30),
CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021),
CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021),
CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID),
@@ -167,17 +167,17 @@ routes = [
CarTestRoute("6b0d44d22df18134|2023-05-06--10-36-55", HYUNDAI.GENESIS_GV80),
CarTestRoute("00c829b1b7613dea|2021-06-24--09-10-10", TOYOTA.ALPHARD_TSS2),
- CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.ALPHARDH_TSS2),
+ CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.ALPHARD_TSS2), # hybrid
CarTestRoute("000cf3730200c71c|2021-05-24--10-42-05", TOYOTA.AVALON),
CarTestRoute("0bb588106852abb7|2021-05-26--12-22-01", TOYOTA.AVALON_2019),
- CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.AVALONH_2019),
+ CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.AVALON_2019), # hybrid
CarTestRoute("e9966711cfb04ce3|2022-01-11--07-59-43", TOYOTA.AVALON_TSS2),
- CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALONH_TSS2),
+ CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALON_TSS2), # hybrid
CarTestRoute("6cdecc4728d4af37|2020-02-23--15-44-18", TOYOTA.CAMRY),
CarTestRoute("2f37c007683e85ba|2023-09-02--14-39-44", TOYOTA.CAMRY), # openpilot longitudinal, with radar CAN filter
+ CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRY), # hybrid
CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", TOYOTA.CAMRY_TSS2),
- CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRYH_TSS2),
- CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRYH),
+ CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRY_TSS2), # hybrid
CarTestRoute("4e45c89c38e8ec4d|2021-05-02--02-49-28", TOYOTA.COROLLA),
CarTestRoute("5f5afb36036506e4|2019-05-14--02-09-54", TOYOTA.COROLLA_TSS2),
CarTestRoute("5ceff72287a5c86c|2019-10-19--10-59-02", TOYOTA.COROLLA_TSS2), # hybrid
@@ -186,7 +186,7 @@ routes = [
CarTestRoute("cdf2f7de565d40ae|2019-04-25--03-53-41", TOYOTA.RAV4_TSS2),
CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022),
CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4_TSS2), # hybrid
- CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4H_TSS2_2022),
+ CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4_TSS2_2022), # hybrid
CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES),
CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2),
CarTestRoute("f49e8041283f2939|2019-05-30--11-51-51", TOYOTA.LEXUS_ES_TSS2), # hybrid
@@ -202,7 +202,7 @@ routes = [
CarTestRoute("3fd5305f8b6ca765|2021-04-28--19-26-49", TOYOTA.LEXUS_NX_TSS2),
CarTestRoute("09ae96064ed85a14|2022-06-09--12-22-31", TOYOTA.LEXUS_NX_TSS2), # hybrid
CarTestRoute("0a302ffddbb3e3d3|2020-02-08--16-19-08", TOYOTA.HIGHLANDER_TSS2),
- CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDERH_TSS2),
+ CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDER_TSS2), # hybrid
CarTestRoute("3183cd9b021e89ce|2021-05-25--10-34-44", TOYOTA.HIGHLANDER),
CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.HIGHLANDERH),
CarTestRoute("eb6acd681135480d|2019-06-20--20-00-00", TOYOTA.SIENNA),
@@ -214,8 +214,8 @@ routes = [
CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHR), # hybrid
CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2),
CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.CHR_TSS2), # openpilot longitudinal, with smartDSU
- CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHRH_TSS2),
- # CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHRH_TSS2), # openpilot longitudinal, radar disabled
+ CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHR_TSS2), # hybrid
+ # CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHR_TSS2), # hybrid, openpilot longitudinal, radar disabled
CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V),
CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1),
diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py
index 787b5d6316..5fa1a02897 100755
--- a/selfdrive/car/tests/test_car_interfaces.py
+++ b/selfdrive/car/tests/test_car_interfaces.py
@@ -133,7 +133,7 @@ class TestCarInterfaces(unittest.TestCase):
# Make sure we can combine dicts
ret = get_interface_attr('DBC', combine_brands=True)
- self.assertGreaterEqual(len(ret), 170)
+ self.assertGreaterEqual(len(ret), 160)
# We don't support combining non-dicts
ret = get_interface_attr('CAR', combine_brands=True)
diff --git a/selfdrive/car/tests/test_models_segs.txt b/selfdrive/car/tests/test_models_segs.txt
index d838c14765..27a2104cf7 100644
--- a/selfdrive/car/tests/test_models_segs.txt
+++ b/selfdrive/car/tests/test_models_segs.txt
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2e57357c95937475fe92c13709ff2ee067f036643d5d455383bd9c3335525614
-size 126281
+oid sha256:8674d9a8304d501aee2bfaea2d668d746fd424b5b0872b83ab265d15d3a3d887
+size 125301
diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml
index b72a05f93b..61f8dc2b3c 100644
--- a/selfdrive/car/torque_data/override.yaml
+++ b/selfdrive/car/torque_data/override.yaml
@@ -14,7 +14,6 @@ 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]
@@ -37,6 +36,7 @@ RAM 1500 5TH GEN: [2.0, 2.0, 0.05]
RAM HD 5TH GEN: [1.4, 1.4, 0.05]
SUBARU OUTBACK 6TH GEN: [2.0, 2.0, 0.2]
CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694]
+CADILLAC ESCALADE ESV 2019: [1.15, 1.3, 0.2]
CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05]
CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112]
CHEVROLET TRAILBLAZER 2021: [1.33, 1.9, 0.16]
diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml
index df529e812f..9386bc3bef 100644
--- a/selfdrive/car/torque_data/params.yaml
+++ b/selfdrive/car/torque_data/params.yaml
@@ -62,16 +62,13 @@ TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492]
TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605]
TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193]
TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509]
-TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.105192506]
-TOYOTA CAMRY 2021: [2.446083, 2.3476510120007434, 0.121615]
-TOYOTA CAMRY HYBRID 2018: [1.996333, 1.7996193116697359, 0.112565]
-TOYOTA CAMRY HYBRID 2021: [2.263582, 2.3901492458927986, 0.115257]
+TOYOTA CAMRY 2018: [2.0568162685952505, 1.7576185169559122, 0.108878753]
+TOYOTA CAMRY 2021: [2.3548324999999997, 2.368900128946771, 0.118436]
TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652]
TOYOTA COROLLA TSS2 2019: [1.991132339206426, 1.868866242720403, 0.19570063298031432]
TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796]
-TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457]
+TOYOTA HIGHLANDER 2020: [1.9617570834136164, 1.8611643317268927, 0.14519673256119725]
TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600]
-TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993]
TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565]
TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515]
TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968]
@@ -80,8 +77,8 @@ TOYOTA RAV4 2019: [2.279239424615458, 2.087101966779332, 0.13682208413446817]
TOYOTA RAV4 2019 8965: [2.3080951748210854, 2.1189367835820603, 0.12942102328134028]
TOYOTA RAV4 2019 x02: [2.762293266024922, 2.243615865975329, 0.11113568178327986]
TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406]
-TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.112174]
-TOYOTA RAV4 HYBRID 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736]
+TOYOTA RAV4 2022: [2.241883248393209, 1.9304407208090029, 0.112174]
+TOYOTA RAV4 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736]
TOYOTA SIENNA 2018: [1.689726, 1.3208264576110418, 0.140456]
VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032]
VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367]
diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml
index 215bd609f8..ee3a491440 100644
--- a/selfdrive/car/torque_data/substitute.yaml
+++ b/selfdrive/car/torque_data/substitute.yaml
@@ -4,18 +4,13 @@ MAZDA CX-5: MAZDA CX-9 2021
MAZDA CX-5 2022: MAZDA CX-9 2021
MAZDA CX-9: MAZDA CX-9 2021
-TOYOTA ALPHARD HYBRID 2021 : TOYOTA SIENNA 2018
TOYOTA ALPHARD 2020: TOYOTA SIENNA 2018
TOYOTA PRIUS v 2017 : TOYOTA PRIUS 2017
-TOYOTA RAV4 2022: TOYOTA RAV4 HYBRID 2022
-TOYOTA C-HR HYBRID 2022: TOYOTA C-HR 2021
LEXUS IS 2018: LEXUS NX 2018
LEXUS CT HYBRID 2018 : LEXUS NX 2018
-LEXUS ES 2018: TOYOTA CAMRY HYBRID 2018
-LEXUS ES HYBRID 2018: TOYOTA CAMRY HYBRID 2018
+LEXUS ES 2018: TOYOTA CAMRY 2018
+LEXUS ES HYBRID 2018: TOYOTA CAMRY 2018
LEXUS RC 2020: LEXUS NX 2020
-TOYOTA AVALON HYBRID 2019: TOYOTA AVALON 2019
-TOYOTA AVALON HYBRID 2022: TOYOTA AVALON 2022
KIA OPTIMA 4TH GEN: HYUNDAI SONATA 2020
KIA OPTIMA 4TH GEN FACELIFT: HYUNDAI SONATA 2020
@@ -37,6 +32,7 @@ HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019
HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019
HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019
HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019
+HYUNDAI I30 N LINE 2019 & GT 2018 DCT: HYUNDAI SONATA 2019
HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020
HYUNDAI ELANTRA SE 2022 NON-SCC: HYUNDAI ELANTRA 2021
HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019
diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py
index c1d324f729..49e347c2b9 100644
--- a/selfdrive/car/toyota/interface.py
+++ b/selfdrive/car/toyota/interface.py
@@ -96,28 +96,28 @@ class CarInterface(CarInterfaceBase):
ret.tireStiffnessFactor = 0.5533
ret.mass = 4481. * CV.LB_TO_KG # mean between min and max
- elif candidate in (CAR.CHR, CAR.CHR_TSS2, CAR.CHRH_TSS2):
+ elif candidate in (CAR.CHR, CAR.CHR_TSS2):
stop_and_go = True
ret.wheelbase = 2.63906
ret.steerRatio = 13.6
ret.tireStiffnessFactor = 0.7933
ret.mass = 3300. * CV.LB_TO_KG
- elif candidate in (CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2):
+ elif candidate in (CAR.CAMRY, CAR.CAMRY_TSS2):
stop_and_go = True
ret.wheelbase = 2.82448
ret.steerRatio = 13.7
ret.tireStiffnessFactor = 0.7933
ret.mass = 3400. * CV.LB_TO_KG # mean between normal and hybrid
- elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2):
+ elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2):
stop_and_go = True
ret.wheelbase = 2.8194 # average of 109.8 and 112.2 in
ret.steerRatio = 16.0
ret.tireStiffnessFactor = 0.8
ret.mass = 4516. * CV.LB_TO_KG # mean between normal and hybrid
- elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2, CAR.AVALONH_TSS2):
+ elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALON_TSS2):
# starting from 2019, all Avalon variants have stop and go
# https://engage.toyota.com/static/images/toyota_safety_sense/TSS_Applicability_Chart.pdf
stop_and_go = candidate != CAR.AVALON
@@ -126,8 +126,7 @@ class CarInterface(CarInterfaceBase):
ret.tireStiffnessFactor = 0.7983
ret.mass = 3505. * CV.LB_TO_KG # mean between normal and hybrid
- elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2_2022,
- CAR.RAV4_TSS2_2023, CAR.RAV4H_TSS2_2023):
+ elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023):
ret.wheelbase = 2.68986
ret.steerRatio = 14.3
ret.tireStiffnessFactor = 0.7933
@@ -202,7 +201,7 @@ class CarInterface(CarInterfaceBase):
ret.tireStiffnessFactor = 0.8
ret.mass = 4300. * CV.LB_TO_KG
- elif candidate in (CAR.ALPHARD_TSS2, CAR.ALPHARDH_TSS2):
+ elif candidate == CAR.ALPHARD_TSS2:
ret.wheelbase = 3.00
ret.steerRatio = 14.2
ret.tireStiffnessFactor = 0.444
diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py
index b0caf9c142..745faf7493 100644
--- a/selfdrive/car/toyota/values.py
+++ b/selfdrive/car/toyota/values.py
@@ -53,26 +53,19 @@ class ToyotaFlagsSP(IntFlag):
class CAR(StrEnum):
# Toyota
ALPHARD_TSS2 = "TOYOTA ALPHARD 2020"
- ALPHARDH_TSS2 = "TOYOTA ALPHARD HYBRID 2021"
AVALON = "TOYOTA AVALON 2016"
AVALON_2019 = "TOYOTA AVALON 2019"
- AVALONH_2019 = "TOYOTA AVALON HYBRID 2019"
AVALON_TSS2 = "TOYOTA AVALON 2022" # TSS 2.5
- AVALONH_TSS2 = "TOYOTA AVALON HYBRID 2022"
CAMRY = "TOYOTA CAMRY 2018"
- CAMRYH = "TOYOTA CAMRY HYBRID 2018"
CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5
- CAMRYH_TSS2 = "TOYOTA CAMRY HYBRID 2021"
CHR = "TOYOTA C-HR 2018"
CHR_TSS2 = "TOYOTA C-HR 2021"
- CHRH_TSS2 = "TOYOTA C-HR HYBRID 2022"
COROLLA = "TOYOTA COROLLA 2017"
# LSS2 Lexus UX Hybrid is same as a TSS2 Corolla Hybrid
COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019"
HIGHLANDER = "TOYOTA HIGHLANDER 2017"
HIGHLANDER_TSS2 = "TOYOTA HIGHLANDER 2020"
HIGHLANDERH = "TOYOTA HIGHLANDER HYBRID 2018"
- HIGHLANDERH_TSS2 = "TOYOTA HIGHLANDER HYBRID 2020"
PRIUS = "TOYOTA PRIUS 2017"
PRIUS_V = "TOYOTA PRIUS v 2017"
PRIUS_TSS2 = "TOYOTA PRIUS TSS2 2021"
@@ -81,8 +74,6 @@ class CAR(StrEnum):
RAV4_TSS2 = "TOYOTA RAV4 2019"
RAV4_TSS2_2022 = "TOYOTA RAV4 2022"
RAV4_TSS2_2023 = "TOYOTA RAV4 2023"
- RAV4H_TSS2_2022 = "TOYOTA RAV4 HYBRID 2022"
- RAV4H_TSS2_2023 = "TOYOTA RAV4 HYBRID 2023"
MIRAI = "TOYOTA MIRAI 2021" # TSS 2.5
SIENNA = "TOYOTA SIENNA 2018"
@@ -115,26 +106,38 @@ class ToyotaCarInfo(CarInfo):
CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
# Toyota
- CAR.ALPHARD_TSS2: ToyotaCarInfo("Toyota Alphard 2019-20"),
- CAR.ALPHARDH_TSS2: ToyotaCarInfo("Toyota Alphard Hybrid 2021"),
+ CAR.ALPHARD_TSS2: [
+ ToyotaCarInfo("Toyota Alphard 2019-20"),
+ ToyotaCarInfo("Toyota Alphard Hybrid 2021"),
+ ],
CAR.AVALON: [
ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P"),
ToyotaCarInfo("Toyota Avalon 2017-18"),
],
- CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21"),
- CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21"),
- CAR.AVALON_TSS2: ToyotaCarInfo("Toyota Avalon 2022"),
- CAR.AVALONH_TSS2: ToyotaCarInfo("Toyota Avalon Hybrid 2022"),
- CAR.CAMRY: ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]),
- CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"),
- CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-23", footnotes=[Footnote.CAMRY]),
- CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-24"),
+ CAR.AVALON_2019: [
+ ToyotaCarInfo("Toyota Avalon 2019-21"),
+ ToyotaCarInfo("Toyota Avalon Hybrid 2019-21"),
+ ],
+ CAR.AVALON_TSS2: [
+ ToyotaCarInfo("Toyota Avalon 2022"),
+ ToyotaCarInfo("Toyota Avalon Hybrid 2022"),
+ ],
+ CAR.CAMRY: [
+ ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]),
+ ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"),
+ ],
+ CAR.CAMRY_TSS2: [
+ ToyotaCarInfo("Toyota Camry 2021-23", footnotes=[Footnote.CAMRY]),
+ ToyotaCarInfo("Toyota Camry Hybrid 2021-24"),
+ ],
CAR.CHR: [
ToyotaCarInfo("Toyota C-HR 2017-20"),
ToyotaCarInfo("Toyota C-HR Hybrid 2017-20"),
],
- CAR.CHR_TSS2: ToyotaCarInfo("Toyota C-HR 2021"),
- CAR.CHRH_TSS2: ToyotaCarInfo("Toyota C-HR Hybrid 2021-22"),
+ CAR.CHR_TSS2: [
+ ToyotaCarInfo("Toyota C-HR 2021"),
+ ToyotaCarInfo("Toyota C-HR Hybrid 2021-22"),
+ ],
CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"),
CAR.COROLLA_TSS2: [
ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
@@ -147,9 +150,11 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
ToyotaCarInfo("Lexus UX Hybrid 2019-23"),
],
CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"),
- CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-23"),
+ CAR.HIGHLANDER_TSS2: [
+ ToyotaCarInfo("Toyota Highlander 2020-23"),
+ ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"),
+ ],
CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"),
- CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"),
CAR.PRIUS: [
ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
@@ -172,10 +177,14 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"),
ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"),
],
- CAR.RAV4_TSS2_2022: ToyotaCarInfo("Toyota RAV4 2022"),
- CAR.RAV4_TSS2_2023: ToyotaCarInfo("Toyota RAV4 2023"),
- CAR.RAV4H_TSS2_2022: ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"),
- CAR.RAV4H_TSS2_2023: ToyotaCarInfo("Toyota RAV4 Hybrid 2023"),
+ CAR.RAV4_TSS2_2022: [
+ ToyotaCarInfo("Toyota RAV4 2022"),
+ ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"),
+ ],
+ CAR.RAV4_TSS2_2023: [
+ ToyotaCarInfo("Toyota RAV4 2023"),
+ ToyotaCarInfo("Toyota RAV4 Hybrid 2023"),
+ ],
CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"),
CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED),
@@ -385,7 +394,7 @@ FW_QUERY_CONFIG = FwQueryConfig(
],
non_essential_ecus={
# FIXME: On some models, abs can sometimes be missing
- Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS],
+ Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS, CAR.ALPHARD_TSS2],
# On some models, the engine can show on two different addresses
Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, CAR.LEXUS_RC,
CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2, CAR.LEXUS_RX_TSS2],
@@ -457,44 +466,26 @@ FW_VERSIONS = {
b'F152607171\x00\x00\x00\x00\x00\x00',
b'F152607110\x00\x00\x00\x00\x00\x00',
b'F152607180\x00\x00\x00\x00\x00\x00',
+ b'F152641040\x00\x00\x00\x00\x00\x00',
+ b'F152641061\x00\x00\x00\x00\x00\x00',
+ b'F152641050\x00\x00\x00\x00\x00\x00',
],
(Ecu.dsu, 0x791, None): [
b'881510703200\x00\x00\x00\x00',
+ b'881510704200\x00\x00\x00\x00',
+ b'881514107100\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'8965B41080\x00\x00\x00\x00\x00\x00',
b'8965B07010\x00\x00\x00\x00\x00\x00',
b'8965B41090\x00\x00\x00\x00\x00\x00',
+ b'8965B41070\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x01896630725200\x00\x00\x00\x00',
b'\x01896630725300\x00\x00\x00\x00',
b'\x01896630735100\x00\x00\x00\x00',
b'\x01896630738000\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 0xf): [
- b'8821F4702300\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 0x6d): [
- b'8646F0702100\x00\x00\x00\x00',
- ],
- },
- CAR.AVALONH_2019: {
- (Ecu.abs, 0x7b0, None): [
- b'F152641040\x00\x00\x00\x00\x00\x00',
- b'F152641061\x00\x00\x00\x00\x00\x00',
- b'F152641050\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.dsu, 0x791, None): [
- b'881510704200\x00\x00\x00\x00',
- b'881514107100\x00\x00\x00\x00',
- ],
- (Ecu.eps, 0x7a1, None): [
- b'8965B07010\x00\x00\x00\x00\x00\x00',
- b'8965B41090\x00\x00\x00\x00\x00\x00',
- b'8965B41070\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x700, None): [
b'\x02896630724000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00',
b'\x02896630737000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00',
b'\x02896630728000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00',
@@ -511,6 +502,7 @@ FW_VERSIONS = {
b'\x01F152607240\x00\x00\x00\x00\x00\x00',
b'\x01F152607250\x00\x00\x00\x00\x00\x00',
b'\x01F152607280\x00\x00\x00\x00\x00\x00',
+ b'F152641080\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'8965B41110\x00\x00\x00\x00\x00\x00',
@@ -518,24 +510,6 @@ FW_VERSIONS = {
(Ecu.engine, 0x700, None): [
b'\x01896630742000\x00\x00\x00\x00',
b'\x01896630743000\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 0xf): [
- b'\x018821F6201200\x00\x00\x00\x00',
- b'\x018821F6201300\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 0x6d): [
- b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
- b'\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
- ],
- },
- CAR.AVALONH_TSS2: {
- (Ecu.abs, 0x7b0, None): [
- b'F152641080\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.eps, 0x7a1, None): [
- b'8965B41110\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x700, None): [
b'\x018966306Q6000\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
@@ -571,65 +545,6 @@ FW_VERSIONS = {
b'\x018966333Q6300\x00\x00\x00\x00',
b'\x018966333Q6500\x00\x00\x00\x00',
b'\x018966333W6000\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x7e0, None): [
- b'\x02333P1100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.dsu, 0x791, None): [
- b'8821F0601200 ',
- b'8821F0601300 ',
- b'8821F0602000 ',
- b'8821F0603300 ',
- b'8821F0604100 ',
- b'8821F0605200 ',
- b'8821F0607200 ',
- b'8821F0608000 ',
- b'8821F0608200 ',
- b'8821F0609100 ',
- ],
- (Ecu.abs, 0x7b0, None): [
- b'F152606210\x00\x00\x00\x00\x00\x00',
- b'F152606230\x00\x00\x00\x00\x00\x00',
- b'F152606270\x00\x00\x00\x00\x00\x00',
- b'F152606290\x00\x00\x00\x00\x00\x00',
- b'F152606410\x00\x00\x00\x00\x00\x00',
- b'F152633540\x00\x00\x00\x00\x00\x00',
- b'F152633A10\x00\x00\x00\x00\x00\x00',
- b'F152633A20\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.eps, 0x7a1, None): [
- b'8965B33540\x00\x00\x00\x00\x00\x00',
- b'8965B33542\x00\x00\x00\x00\x00\x00',
- b'8965B33580\x00\x00\x00\x00\x00\x00',
- b'8965B33581\x00\x00\x00\x00\x00\x00',
- b'8965B33621\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 0xf): [ # Same as 0x791
- b'8821F0601200 ',
- b'8821F0601300 ',
- b'8821F0602000 ',
- b'8821F0603300 ',
- b'8821F0604100 ',
- b'8821F0605200 ',
- b'8821F0607200 ',
- b'8821F0608000 ',
- b'8821F0608200 ',
- b'8821F0609100 ',
- ],
- (Ecu.fwdCamera, 0x750, 0x6d): [
- b'8646F0601200 ',
- b'8646F0601300 ',
- b'8646F0601400 ',
- b'8646F0603400 ',
- b'8646F0604100 ',
- b'8646F0605000 ',
- b'8646F0606000 ',
- b'8646F0606100 ',
- b'8646F0607100 ',
- ],
- },
- CAR.CAMRYH: {
- (Ecu.engine, 0x700, None): [
b'\x018966306Q6000\x00\x00\x00\x00',
b'\x018966333N1100\x00\x00\x00\x00',
b'\x018966333N4300\x00\x00\x00\x00',
@@ -652,7 +567,35 @@ FW_VERSIONS = {
b'\x028966306S0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00',
b'\x028966306S1100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00',
],
+ (Ecu.engine, 0x7e0, None): [
+ b'\x02333P1100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.dsu, 0x791, None): [
+ b'8821F0601200 ',
+ b'8821F0601300 ',
+ b'8821F0602000 ',
+ b'8821F0603300 ',
+ b'8821F0604100 ',
+ b'8821F0605200 ',
+ b'8821F0607200 ',
+ b'8821F0608000 ',
+ b'8821F0608200 ',
+ b'8821F0609100 ',
+ b'8821F0603400 ',
+ b'8821F0604000 ',
+ b'8821F0604200 ',
+ b'8821F0606200 ',
+ b'8821F0609000 ',
+ ],
(Ecu.abs, 0x7b0, None): [
+ b'F152606210\x00\x00\x00\x00\x00\x00',
+ b'F152606230\x00\x00\x00\x00\x00\x00',
+ b'F152606270\x00\x00\x00\x00\x00\x00',
+ b'F152606290\x00\x00\x00\x00\x00\x00',
+ b'F152606410\x00\x00\x00\x00\x00\x00',
+ b'F152633540\x00\x00\x00\x00\x00\x00',
+ b'F152633A10\x00\x00\x00\x00\x00\x00',
+ b'F152633A20\x00\x00\x00\x00\x00\x00',
b'F152633214\x00\x00\x00\x00\x00\x00',
b'F152633660\x00\x00\x00\x00\x00\x00',
b'F152633712\x00\x00\x00\x00\x00\x00',
@@ -660,70 +603,61 @@ FW_VERSIONS = {
b'F152633B51\x00\x00\x00\x00\x00\x00',
b'F152633B60\x00\x00\x00\x00\x00\x00',
],
- (Ecu.dsu, 0x791, None): [
- b'8821F0601200 ',
- b'8821F0601300 ',
- b'8821F0603400 ',
- b'8821F0604000 ',
- b'8821F0604100 ',
- b'8821F0604200 ',
- b'8821F0605200 ',
- b'8821F0606200 ',
- b'8821F0607200 ',
- b'8821F0608000 ',
- b'8821F0608200 ',
- b'8821F0609000 ',
- b'8821F0609100 ',
- ],
(Ecu.eps, 0x7a1, None): [
b'8965B33540\x00\x00\x00\x00\x00\x00',
b'8965B33542\x00\x00\x00\x00\x00\x00',
- b'8965B33550\x00\x00\x00\x00\x00\x00',
- b'8965B33551\x00\x00\x00\x00\x00\x00',
b'8965B33580\x00\x00\x00\x00\x00\x00',
b'8965B33581\x00\x00\x00\x00\x00\x00',
- b'8965B33611\x00\x00\x00\x00\x00\x00',
b'8965B33621\x00\x00\x00\x00\x00\x00',
+ b'8965B33550\x00\x00\x00\x00\x00\x00',
+ b'8965B33551\x00\x00\x00\x00\x00\x00',
+ b'8965B33611\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [ # Same as 0x791
b'8821F0601200 ',
b'8821F0601300 ',
- b'8821F0603400 ',
- b'8821F0604000 ',
+ b'8821F0602000 ',
+ b'8821F0603300 ',
b'8821F0604100 ',
- b'8821F0604200 ',
b'8821F0605200 ',
- b'8821F0606200 ',
b'8821F0607200 ',
b'8821F0608000 ',
b'8821F0608200 ',
- b'8821F0609000 ',
b'8821F0609100 ',
+ b'8821F0603400 ',
+ b'8821F0604000 ',
+ b'8821F0604200 ',
+ b'8821F0606200 ',
+ b'8821F0609000 ',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'8646F0601200 ',
b'8646F0601300 ',
b'8646F0601400 ',
b'8646F0603400 ',
- b'8646F0603500 ',
b'8646F0604100 ',
b'8646F0605000 ',
b'8646F0606000 ',
b'8646F0606100 ',
- b'8646F0607000 ',
b'8646F0607100 ',
+ b'8646F0603500 ',
+ b'8646F0607000 ',
],
},
CAR.CAMRY_TSS2: {
(Ecu.eps, 0x7a1, None): [
b'8965B33630\x00\x00\x00\x00\x00\x00',
b'8965B33640\x00\x00\x00\x00\x00\x00',
+ b'8965B33650\x00\x00\x00\x00\x00\x00',
],
(Ecu.abs, 0x7b0, None): [
b'\x01F152606370\x00\x00\x00\x00\x00\x00',
b'\x01F152606390\x00\x00\x00\x00\x00\x00',
b'\x01F152606400\x00\x00\x00\x00\x00\x00',
b'\x01F152606431\x00\x00\x00\x00\x00\x00',
+ b'F152633D00\x00\x00\x00\x00\x00\x00',
+ b'F152633D60\x00\x00\x00\x00\x00\x00',
+ b'F152633310\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x018966306Q5000\x00\x00\x00\x00',
@@ -734,10 +668,16 @@ FW_VERSIONS = {
b'\x018966306T3200\x00\x00\x00\x00',
b'\x018966306T4000\x00\x00\x00\x00',
b'\x018966306T4100\x00\x00\x00\x00',
+ b'\x018966306Q6000\x00\x00\x00\x00',
+ b'\x018966306Q7000\x00\x00\x00\x00',
+ b'\x018966306T0000\x00\x00\x00\x00',
+ b'\x018966306V1000\x00\x00\x00\x00',
+ b'\x01896633T20000\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F6201200\x00\x00\x00\x00',
b'\x018821F6201300\x00\x00\x00\x00',
+ b'\x018821F6201400\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F0602100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
@@ -747,35 +687,7 @@ FW_VERSIONS = {
b'\x028646F3305200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
- ],
- },
- CAR.CAMRYH_TSS2: {
- (Ecu.eps, 0x7a1, None): [
- b'8965B33630\x00\x00\x00\x00\x00\x00',
- b'8965B33650\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.abs, 0x7b0, None): [
- b'F152633D00\x00\x00\x00\x00\x00\x00',
- b'F152633D60\x00\x00\x00\x00\x00\x00',
- b'F152633310\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x700, None): [
- b'\x018966306Q6000\x00\x00\x00\x00',
- b'\x018966306Q7000\x00\x00\x00\x00',
- b'\x018966306T0000\x00\x00\x00\x00',
- b'\x018966306V1000\x00\x00\x00\x00',
- b'\x01896633T20000\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 15): [
- b'\x018821F6201200\x00\x00\x00\x00',
- b'\x018821F6201300\x00\x00\x00\x00',
- b'\x018821F6201400\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 109): [
- b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
- b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
- b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
],
},
CAR.CHR: {
@@ -867,13 +779,19 @@ FW_VERSIONS = {
(Ecu.abs, 0x7b0, None): [
b'F152610260\x00\x00\x00\x00\x00\x00',
b'F1526F4270\x00\x00\x00\x00\x00\x00',
+ b'F152610041\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'8965B10091\x00\x00\x00\x00\x00\x00',
b'8965B10110\x00\x00\x00\x00\x00\x00',
+ b'8965B10092\x00\x00\x00\x00\x00\x00',
+ b'8965B10111\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x0189663F459000\x00\x00\x00\x00',
+ b'\x0189663F438000\x00\x00\x00\x00',
+ b'\x02896631025000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
+ b'\x0289663F453000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'\x0331014000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00',
@@ -881,33 +799,12 @@ FW_VERSIONS = {
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821FF410200\x00\x00\x00\x00',
b'\x018821FF410300\x00\x00\x00\x00',
+ b'\x018821FF410500\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646FF410200\x00\x00\x00\x008646GF408200\x00\x00\x00\x00',
b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00',
- ],
- },
- CAR.CHRH_TSS2: {
- (Ecu.eps, 0x7a1, None): [
- b'8965B10092\x00\x00\x00\x00\x00\x00',
- b'8965B10091\x00\x00\x00\x00\x00\x00',
- b'8965B10111\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.abs, 0x7b0, None): [
- b'F152610041\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x700, None): [
- b'\x0189663F438000\x00\x00\x00\x00',
- b'\x02896631025000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
- b'\x0289663F453000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 15): [
- b'\x018821FF410500\x00\x00\x00\x00',
- b'\x018821FF410300\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 109): [
b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00',
- b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00',
],
},
CAR.COROLLA: {
@@ -1193,6 +1090,18 @@ FW_VERSIONS = {
b'\x01F15260E110\x00\x00\x00\x00\x00\x00',
b'\x01F15260E170\x00\x00\x00\x00\x00\x00',
b'\x01F15260E05300\x00\x00\x00\x00',
+ b'\x01F15264872300\x00\x00\x00\x00',
+ b'\x01F15264872400\x00\x00\x00\x00',
+ b'\x01F15264872500\x00\x00\x00\x00',
+ b'\x01F15264872600\x00\x00\x00\x00',
+ b'\x01F15264872700\x00\x00\x00\x00',
+ b'\x01F15264873500\x00\x00\x00\x00',
+ b'\x01F152648C6300\x00\x00\x00\x00',
+ b'\x01F152648J4000\x00\x00\x00\x00',
+ b'\x01F152648J5000\x00\x00\x00\x00',
+ b'\x01F152648J6000\x00\x00\x00\x00',
+ b'\x01F152648J7000\x00\x00\x00\x00',
+ b'\x01F152648L5000\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x01896630E62100\x00\x00\x00\x00',
@@ -1213,44 +1122,13 @@ FW_VERSIONS = {
b'\x01896630EE1100\x00\x00\x00\x00',
b'\x01896630EG3000\x00\x00\x00\x00',
b'\x01896630EG5000\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 0xf): [
- b'\x018821F3301400\x00\x00\x00\x00',
- b'\x018821F6201200\x00\x00\x00\x00',
- b'\x018821F6201300\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 0x6d): [
- b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
- b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
- b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
- b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
- ],
- },
- CAR.HIGHLANDERH_TSS2: {
- (Ecu.eps, 0x7a1, None): [
- b'8965B48241\x00\x00\x00\x00\x00\x00',
- b'8965B48310\x00\x00\x00\x00\x00\x00',
- b'8965B48400\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.abs, 0x7b0, None): [
- b'\x01F15264872300\x00\x00\x00\x00',
- b'\x01F15264872400\x00\x00\x00\x00',
- b'\x01F15264872500\x00\x00\x00\x00',
- b'\x01F15264872600\x00\x00\x00\x00',
- b'\x01F15264873500\x00\x00\x00\x00',
- b'\x01F152648C6300\x00\x00\x00\x00',
- b'\x01F152648J4000\x00\x00\x00\x00',
- b'\x01F152648J5000\x00\x00\x00\x00',
- b'\x01F152648J6000\x00\x00\x00\x00',
- b'\x01F15264872700\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x700, None): [
b'\x01896630E67000\x00\x00\x00\x00',
b'\x01896630EA1000\x00\x00\x00\x00',
b'\x01896630EE4000\x00\x00\x00\x00',
b'\x01896630EE4100\x00\x00\x00\x00',
b'\x01896630EE5000\x00\x00\x00\x00',
b'\x01896630EE6000\x00\x00\x00\x00',
+ b'\x01896630EE7000\x00\x00\x00\x00',
b'\x01896630EF8000\x00\x00\x00\x00',
b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
@@ -1638,46 +1516,6 @@ FW_VERSIONS = {
(Ecu.abs, 0x7b0, None): [
b'\x01F15260R350\x00\x00\x00\x00\x00\x00',
b'\x01F15260R361\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.eps, 0x7a1, None): [
- b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x700, None): [
- b'\x01896634AA0000\x00\x00\x00\x00',
- b'\x01896634AA0100\x00\x00\x00\x00',
- b'\x01896634AA1000\x00\x00\x00\x00',
- b'\x01896634A88000\x00\x00\x00\x00',
- b'\x01896634A89000\x00\x00\x00\x00',
- b'\x01896634A89100\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 0xf): [
- b'\x018821F0R01100\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 0x6d): [
- b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00',
- ],
- },
- CAR.RAV4_TSS2_2023: {
- (Ecu.abs, 0x7b0, None): [
- b'\x01F15260R450\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.eps, 0x7a1, None): [
- b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00',
- ],
- (Ecu.engine, 0x700, None): [
- b'\x01896634A88100\x00\x00\x00\x00',
- b'\x01896634AJ2000\x00\x00\x00\x00',
- b'\x01896634A89100\x00\x00\x00\x00',
- ],
- (Ecu.fwdRadar, 0x750, 0xf): [
- b'\x018821F0R03100\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 0x6d): [
- b'\x028646F0R05100\x00\x00\x00\x008646G0R02100\x00\x00\x00\x00',
- ],
- },
- CAR.RAV4H_TSS2_2022: {
- (Ecu.abs, 0x7b0, None): [
b'\x01F15264283100\x00\x00\x00\x00',
b'\x01F15264286200\x00\x00\x00\x00',
b'\x01F15264286100\x00\x00\x00\x00',
@@ -1689,6 +1527,12 @@ FW_VERSIONS = {
b'8965B42172\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
+ b'\x01896634AA0000\x00\x00\x00\x00',
+ b'\x01896634AA0100\x00\x00\x00\x00',
+ b'\x01896634AA1000\x00\x00\x00\x00',
+ b'\x01896634A88000\x00\x00\x00\x00',
+ b'\x01896634A89000\x00\x00\x00\x00',
+ b'\x01896634A89100\x00\x00\x00\x00',
b'\x01896634A02001\x00\x00\x00\x00',
b'\x01896634A03000\x00\x00\x00\x00',
b'\x01896634A08000\x00\x00\x00\x00',
@@ -1704,8 +1548,9 @@ FW_VERSIONS = {
b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00',
],
},
- CAR.RAV4H_TSS2_2023: {
+ CAR.RAV4_TSS2_2023: {
(Ecu.abs, 0x7b0, None): [
+ b'\x01F15260R450\x00\x00\x00\x00\x00\x00',
b'\x01F15264283200\x00\x00\x00\x00',
b'\x01F15264283300\x00\x00\x00\x00',
b'\x01F152642F1000\x00\x00\x00\x00',
@@ -1715,15 +1560,12 @@ FW_VERSIONS = {
b'8965B42371\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
+ b'\x01896634A88100\x00\x00\x00\x00',
+ b'\x01896634AJ2000\x00\x00\x00\x00',
+ b'\x01896634A89100\x00\x00\x00\x00',
b'\x01896634AE1001\x00\x00\x00\x00',
b'\x01896634AF0000\x00\x00\x00\x00',
],
- (Ecu.hybrid, 0x7d2, None): [
- b'\x02899830R39000\x00\x00\x00\x00899850R20000\x00\x00\x00\x00',
- b'\x02899830R41000\x00\x00\x00\x00899850R20000\x00\x00\x00\x00',
- b'\x028998342C0000\x00\x00\x00\x00899854224000\x00\x00\x00\x00',
- b'\x028998342C6000\x00\x00\x00\x00899854224000\x00\x00\x00\x00',
- ],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F0R03100\x00\x00\x00\x00',
],
@@ -2182,11 +2024,15 @@ FW_VERSIONS = {
(Ecu.engine, 0x7e0, None): [
b'\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'8965B58040\x00\x00\x00\x00\x00\x00',
b'8965B58052\x00\x00\x00\x00\x00\x00',
],
+ (Ecu.abs, 0x7b0, None): [
+ b'F152658341\x00\x00\x00\x00\x00\x00'
+ ],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301200\x00\x00\x00\x00',
b'\x018821F3301400\x00\x00\x00\x00',
@@ -2194,22 +2040,6 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F58010C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
b'\x028646F5803200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
- ],
- },
- CAR.ALPHARDH_TSS2: {
- (Ecu.engine, 0x7e0, None): [
- b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.eps, 0x7a1, None): [
- b'8965B58040\x00\x00\x00\x00\x00\x00',
- ],
- (Ecu.abs, 0x7b0, None): [
- b'F152658341\x00\x00\x00\x00\x00\x00'
- ],
- (Ecu.fwdRadar, 0x750, 0xf): [
- b'\x018821F3301400\x00\x00\x00\x00',
- ],
- (Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646FV201000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
@@ -2229,20 +2059,14 @@ DBC = {
CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None),
- CAR.CHRH_TSS2: dbc_dict('toyota_nodsu_pt_generated', None),
CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
- CAR.CAMRYH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
CAR.CAMRY_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
- CAR.CAMRYH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.HIGHLANDER: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
CAR.HIGHLANDER_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.HIGHLANDERH: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
- CAR.HIGHLANDERH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.AVALON: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
CAR.AVALON_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
- CAR.AVALONH_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
CAR.AVALON_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
- CAR.AVALONH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.RAV4_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.RAV4_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None),
CAR.RAV4_TSS2_2023: dbc_dict('toyota_nodsu_pt_generated', None),
@@ -2254,14 +2078,11 @@ DBC = {
CAR.LEXUS_IS: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
CAR.LEXUS_IS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.LEXUS_CTH: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'),
- CAR.RAV4H_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None),
- CAR.RAV4H_TSS2_2023: dbc_dict('toyota_nodsu_pt_generated', None),
CAR.LEXUS_NX: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
CAR.LEXUS_NX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.PRIUS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.MIRAI: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.ALPHARD_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
- CAR.ALPHARDH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
}
# These cars have non-standard EPS torque scale factors. All others are 73
@@ -2269,20 +2090,19 @@ EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_I
# Toyota/Lexus Safety Sense 2.0 and 2.5
TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.COROLLA_TSS2, CAR.LEXUS_ES_TSS2,
- CAR.RAV4H_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.LEXUS_RX_TSS2, CAR.HIGHLANDER_TSS2,
- CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, CAR.LEXUS_IS_TSS2, CAR.MIRAI, CAR.LEXUS_NX_TSS2,
- CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2, CAR.CHR_TSS2, CAR.CHRH_TSS2}
+ CAR.LEXUS_RX_TSS2, CAR.HIGHLANDER_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.LEXUS_IS_TSS2,
+ CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.CHR_TSS2}
-NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CAMRY, CAR.CAMRYH}
+NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CAMRY}
# the DSU uses the AEB message for longitudinal on these cars
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}
+RADAR_ACC_CAR = {CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2}
# these cars use the Lane Tracing Assist (LTA) message for lateral control
-ANGLE_CONTROL_CAR = {CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023}
+ANGLE_CONTROL_CAR = {CAR.RAV4_TSS2_2023}
# no resume button press required
NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH}
diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py
index 0b9f44df9d..6cc313b06e 100644
--- a/selfdrive/car/volkswagen/values.py
+++ b/selfdrive/car/volkswagen/values.py
@@ -842,6 +842,7 @@ FW_VERSIONS = {
b'\xf1\x8783A907115F \xf1\x890002',
b'\xf1\x8783A907115G \xf1\x890001',
b'\xf1\x8783A907115K \xf1\x890001',
+ b'\xf1\x8783A907115K \xf1\x890002',
b'\xf1\x8704E906024AP\xf1\x891461',
b'\xf1\x8783A907115 \xf1\x890007',
],
@@ -851,6 +852,7 @@ FW_VERSIONS = {
b'\xf1\x8709G927158GC\xf1\x893821',
b'\xf1\x8709G927158GD\xf1\x893820',
b'\xf1\x8709G927158GM\xf1\x893936',
+ b'\xf1\x8709G927158GN\xf1\x893938',
b'\xf1\x870D9300043 \xf1\x895202',
b'\xf1\x870DL300011N \xf1\x892001',
b'\xf1\x870DL300011N \xf1\x892012',
@@ -889,6 +891,7 @@ FW_VERSIONS = {
b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1',
b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1',
b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1',
+ b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002RA60A2ROM',
b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00',
b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1',
@@ -1081,11 +1084,13 @@ FW_VERSIONS = {
b'\xf1\x8783A906259 \xf1\x890001',
b'\xf1\x8783A906259 \xf1\x890005',
b'\xf1\x8783A906259C \xf1\x890002',
+ b'\xf1\x8783A906259D \xf1\x890001',
b'\xf1\x8783A906259F \xf1\x890001',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158CN\xf1\x893608',
b'\xf1\x8709G927158FL\xf1\x893758',
+ b'\xf1\x8709G927158GG\xf1\x893825',
b'\xf1\x8709G927158GP\xf1\x893937',
b'\xf1\x870GC300045D \xf1\x892802',
b'\xf1\x870GC300046F \xf1\x892701',
@@ -1095,6 +1100,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218C219321532111',
b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111',
b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111',
+ b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218A219321532111',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300',
diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py
deleted file mode 100755
index 10c0e4a7b0..0000000000
--- a/selfdrive/locationd/laikad.py
+++ /dev/null
@@ -1,468 +0,0 @@
-#!/usr/bin/env python3
-import math
-import os
-import time
-import shutil
-from collections import defaultdict
-from concurrent.futures import Future, ProcessPoolExecutor
-from enum import IntEnum
-from typing import List, Optional, Dict, Any
-
-import numpy as np
-
-from cereal import log, messaging
-from openpilot.common.params import Params, put_nonblocking
-from laika import AstroDog
-from laika.constants import SECS_IN_HR, SECS_IN_MIN
-from laika.downloader import DownloadFailed
-from laika.ephemeris import EphemerisType, GPSEphemeris, GLONASSEphemeris, ephemeris_structs, parse_qcom_ephem
-from laika.gps_time import GPSTime
-from laika.helpers import ConstellationId, get_sv_id
-from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox
-from laika.raw_gnss import gps_time_from_qcom_report, get_measurements_from_qcom_reports
-from laika.opt import calc_pos_fix, get_posfix_sympy_fun, calc_vel_fix, get_velfix_sympy_func
-from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind
-from openpilot.selfdrive.locationd.models.gnss_kf import GNSSKalman
-from openpilot.selfdrive.locationd.models.gnss_kf import States as GStates
-from openpilot.system.hardware.hw import Paths
-from openpilot.system.swaglog import cloudlog
-
-MAX_TIME_GAP = 10
-EPHEMERIS_CACHE = 'LaikadEphemerisV3'
-CACHE_VERSION = 0.2
-POS_FIX_RESIDUAL_THRESHOLD = 100.0
-
-
-class LogEphemerisType(IntEnum):
- nav = 0
- nasaUltraRapid = 1
- glonassIacUltraRapid = 2
- qcom = 3
-
-class EphemerisSource(IntEnum):
- gnssChip = 0
- internet = 1
- cache = 2
- unknown = 3
-
-def get_log_eph_type(ephem):
- if ephem.eph_type == EphemerisType.NAV:
- source_type = LogEphemerisType.nav
- elif ephem.eph_type == EphemerisType.QCOM_POLY:
- source_type = LogEphemerisType.qcom
- else:
- assert ephem.file_epoch is not None
- file_src = ephem.file_source
- if file_src == 'igu': # example nasa: '2214/igu22144_00.sp3.Z'
- source_type = LogEphemerisType.nasaUltraRapid
- elif file_src == 'Sta': # example nasa: '22166/ultra/Stark_1D_22061518.sp3'
- source_type = LogEphemerisType.glonassIacUltraRapid
- else:
- raise Exception(f"Didn't expect file source {file_src}")
- return source_type
-
-def get_log_eph_source(ephem):
- if ephem.file_name == 'qcom' or ephem.file_name == 'ublox':
- source = EphemerisSource.gnssChip
- elif ephem.file_name == EPHEMERIS_CACHE:
- source = EphemerisSource.cache
- else:
- source = EphemerisSource.internet
- return source
-
-
-class Laikad:
- def __init__(self, valid_const=(ConstellationId.GPS, ConstellationId.GLONASS), auto_fetch_navs=True, auto_update=False,
- valid_ephem_types=(EphemerisType.NAV, EphemerisType.QCOM_POLY),
- save_ephemeris=False, use_qcom=False):
- """
- valid_const: GNSS constellation which can be used
- auto_fetch_navs: If true fetch navs from internet when needed
- auto_update: If true download AstroDog will download all files needed. This can be ephemeris or correction data like ionosphere.
- valid_ephem_types: Valid ephemeris types to be used by AstroDog
- save_ephemeris: If true saves and loads nav and orbit ephemeris to cache.
- """
- self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types,
- clear_old_ephemeris=True, cache_dir=Paths.download_cache_root())
- self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True, erratic_clock=use_qcom)
-
- self.auto_fetch_navs = auto_fetch_navs
- self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None
- self.orbit_fetch_future: Optional[Future] = None
-
- self.last_report_time = GPSTime(0, 0)
- self.last_fetch_navs_t = GPSTime(0, 0)
- self.last_cached_t = GPSTime(0, 0)
- self.save_ephemeris = save_ephemeris
- self.load_cache()
-
- self.posfix_functions = {constellation: get_posfix_sympy_fun(constellation) for constellation in (ConstellationId.GPS, ConstellationId.GLONASS)}
- self.velfix_function = get_velfix_sympy_func()
- self.last_fix_pos = None
- self.last_fix_t = None
- self.use_qcom = use_qcom
- self.first_log_time = None
- self.ttff = -1
- self.measurement_lag = 0.630 if self.use_qcom else 0.095
-
- # qcom specific stuff
- self.qcom_reports_received = 4
- self.qcom_reports = []
-
- def load_cache(self):
- if not self.save_ephemeris:
- return
-
- cache_bytes = Params().get(EPHEMERIS_CACHE)
- if not cache_bytes:
- return
-
- nav_dict = {}
- try:
- with ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) as ephem_cache:
- glonass_navs = [GLONASSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.glonassEphemerides]
- gps_navs = [GPSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.gpsEphemerides]
- for e in sum([glonass_navs, gps_navs], []):
- if e.prn not in nav_dict:
- nav_dict[e.prn] = []
- nav_dict[e.prn].append(e)
- self.astro_dog.add_navs(nav_dict)
- except Exception:
- cloudlog.exception("Error parsing cache")
- cloudlog.debug(
- f"Loaded navs ({sum([len(nav_dict[prn]) for prn in nav_dict.keys()])}). Unique orbit and nav sats: {list(nav_dict.keys())} ")
-
- def cache_ephemeris(self):
-
- if self.save_ephemeris and (self.last_report_time - self.last_cached_t > SECS_IN_MIN):
- nav_list: List = sum([v for k,v in self.astro_dog.navs.items()], [])
- #TODO this only saves currently valid ephems, when we download future ephems we should save them too
- valid_navs = [e for e in nav_list if e.valid(self.last_report_time)]
- if len(valid_navs) > 0:
- ephem_cache = ephemeris_structs.EphemerisCache(glonassEphemerides=[e.data for e in valid_navs if e.prn[0]=='R'],
- gpsEphemerides=[e.data for e in valid_navs if e.prn[0]=='G'])
- put_nonblocking(EPHEMERIS_CACHE, ephem_cache.to_bytes())
- cloudlog.debug("Cache saved")
- self.last_cached_t = self.last_report_time
-
- def create_ephem_statuses(self):
- ephemeris_statuses = []
- eph_list: List = sum([v for k,v in self.astro_dog.navs.items()], []) + sum([v for k,v in self.astro_dog.qcom_polys.items()], [])
- for eph in eph_list:
- status = log.GnssMeasurements.EphemerisStatus.new_message()
- status.constellationId = ConstellationId.from_rinex_char(eph.prn[0]).value
- status.svId = get_sv_id(eph.prn)
- status.type = get_log_eph_type(eph).value
- status.source = get_log_eph_source(eph).value
- status.tow = eph.epoch.tow
- status.gpsWeek = eph.epoch.week
- ephemeris_statuses.append(status)
- return ephemeris_statuses
-
-
- def get_lsq_fix(self, t, measurements):
- if self.last_fix_t is None or abs(self.last_fix_t - t) > 0:
- min_measurements = 5 if any(p.constellation_id == ConstellationId.GLONASS for p in measurements) else 4
-
- position_solution, pr_residuals, pos_std = calc_pos_fix(measurements, self.posfix_functions, min_measurements=min_measurements)
- if len(position_solution) < 3:
- return None
- position_estimate = position_solution[:3]
- position_std = pos_std[:3]
-
- velocity_solution, prr_residuals, vel_std = calc_vel_fix(measurements, position_estimate, self.velfix_function, min_measurements=min_measurements)
- if len(velocity_solution) < 3:
- return None
- velocity_estimate = velocity_solution[:3]
- velocity_std = vel_std[:3]
-
- return position_estimate, position_std, velocity_estimate, velocity_std
-
- def is_good_report(self, gnss_msg):
- if gnss_msg.which() in ['drMeasurementReport', 'measurementReport'] and self.use_qcom:
- # TODO: Understand and use remaining unknown constellations
- try:
- if gnss_msg.which() == 'drMeasurementReport':
- constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source)
- else:
- constellation_id = ConstellationId.from_qcom_source(gnss_msg.measurementReport.source)
- good_constellation = constellation_id in [ConstellationId.GPS, ConstellationId.SBAS, ConstellationId.GLONASS]
- report_time = gps_time_from_qcom_report(gnss_msg)
- except NotImplementedError:
- return False
- # Garbage timestamps with week > 32767 are sometimes sent by module.
- # This is an issue with gpsTime and GLONASS time.
- good_week = report_time.week < np.iinfo(np.int16).max
- return good_constellation and good_week
- elif gnss_msg.which() == 'measurementReport' and not self.use_qcom:
- return True
- else:
- return False
-
- def read_report(self, gnss_msg):
- if self.use_qcom:
- # QCOM reports are per constellation, so we need to aggregate them
- # Additionally, the pseudoranges are broken in the measurementReports
- # and the doppler filteredSpeed is broken in the drMeasurementReports
- report_time = gps_time_from_qcom_report(gnss_msg)
- if report_time - self.last_report_time == 0:
- self.qcom_reports.append(gnss_msg)
- self.last_report_time = report_time
- elif report_time - self.last_report_time > 0:
- self.qcom_reports_received = max(1, len(self.qcom_reports))
- self.qcom_reports = [gnss_msg]
- self.last_report_time = report_time
- else:
- # Sometimes DR reports get sent one iteration late (1second), they need to be ignored
- cloudlog.warning(f"Received report with time {report_time} before last report time {self.last_report_time}")
-
- if len(self.qcom_reports) == self.qcom_reports_received:
- new_meas = get_measurements_from_qcom_reports(self.qcom_reports)
- else:
- new_meas = []
- else:
- report = gnss_msg.measurementReport
- self.last_report_time = GPSTime(report.gpsWeek, report.rcvTow)
- new_meas = read_raw_ublox(report)
- return self.last_report_time, new_meas
-
- def is_ephemeris(self, gnss_msg):
- if self.use_qcom:
- return gnss_msg.which() == 'drSvPoly'
- else:
- return gnss_msg.which() in ('ephemeris', 'glonassEphemeris')
-
- def read_ephemeris(self, gnss_msg):
- if self.use_qcom:
- try:
- ephem = parse_qcom_ephem(gnss_msg.drSvPoly)
- self.astro_dog.add_qcom_polys({ephem.prn: [ephem]})
- except Exception:
- cloudlog.exception("Error parsing qcom svPoly ephemeris from qcom module")
- return
-
- else:
- if gnss_msg.which() == 'ephemeris':
- data_struct = ephemeris_structs.Ephemeris.new_message(**gnss_msg.ephemeris.to_dict())
- try:
- ephem = GPSEphemeris(data_struct, file_name='ublox')
- except Exception:
- cloudlog.exception("Error parsing GPS ephemeris from ublox")
- return
- elif gnss_msg.which() == 'glonassEphemeris':
- data_struct = ephemeris_structs.GlonassEphemeris.new_message(**gnss_msg.glonassEphemeris.to_dict())
- try:
- ephem = GLONASSEphemeris(data_struct, file_name='ublox')
- except Exception:
- cloudlog.exception("Error parsing GLONASS ephemeris from ublox")
- return
- else:
- cloudlog.error(f"Unsupported ephemeris type: {gnss_msg.which()}")
- return
- self.astro_dog.add_navs({ephem.prn: [ephem]})
- self.cache_ephemeris()
-
- def process_report(self, new_meas, t):
- # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites
- new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7]
- processed_measurements = process_measurements(new_meas, self.astro_dog)
- if self.last_fix_pos is not None:
- est_pos = self.last_fix_pos
- correct_delay = True
- else:
- est_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist()
- correct_delay = False
- corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog, correct_delay=correct_delay)
- # If many measurements weren't corrected, position may be garbage, so reset
- if len(processed_measurements) >= 8 and len(corrected_measurements) < 5:
- cloudlog.error("Didn't correct enough measurements, resetting estimate position")
- self.last_fix_pos = None
- self.last_fix_t = None
- return corrected_measurements
-
- def calc_fix(self, t, measurements):
- instant_fix = self.get_lsq_fix(t, measurements)
- if instant_fix is None:
- return None
- else:
- position_estimate, position_std, velocity_estimate, velocity_std = instant_fix
- self.last_fix_t = t
- self.last_fix_pos = position_estimate
- self.lat_fix_pos_std = position_std
- return position_estimate, position_std, velocity_estimate, velocity_std
-
- def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False):
- out_msg = messaging.new_message("gnssMeasurements")
- t = gnss_mono_time * 1e-9
- msg_dict: Dict[str, Any] = {"measTime": gnss_mono_time - int(1e9 * self.measurement_lag)}
- if self.first_log_time is None:
- self.first_log_time = 1e-9 * gnss_mono_time
- if self.is_ephemeris(gnss_msg):
- self.read_ephemeris(gnss_msg)
- elif self.is_good_report(gnss_msg):
- report_t, new_meas = self.read_report(gnss_msg)
- if report_t.week > 0:
- if self.auto_fetch_navs:
- self.fetch_navs(report_t, block)
-
- corrected_measurements = self.process_report(new_meas, t)
- msg_dict['correctedMeasurements'] = [create_measurement_msg(m) for m in corrected_measurements]
-
- fix = self.calc_fix(t, corrected_measurements)
- measurement_msg = log.LiveLocationKalman.Measurement.new_message
- if fix is not None:
- position_estimate, position_std, velocity_estimate, velocity_std = fix
- if self.ttff <= 0:
- self.ttff = max(1e-3, t - self.first_log_time)
- msg_dict["positionECEF"] = measurement_msg(value=position_estimate, std=position_std.tolist(), valid=bool(self.last_fix_t == t))
- msg_dict["velocityECEF"] = measurement_msg(value=velocity_estimate, std=velocity_std.tolist(), valid=bool(self.last_fix_t == t))
-
- self.update_localizer(self.last_fix_pos, t, corrected_measurements)
- P_diag = self.gnss_kf.P.diagonal()
- kf_valid = all(self.kf_valid(t))
- msg_dict["kalmanPositionECEF"] = measurement_msg(value=self.gnss_kf.x[GStates.ECEF_POS].tolist(),
- std=np.sqrt(P_diag[GStates.ECEF_POS]).tolist(),
- valid=kf_valid)
- msg_dict["kalmanVelocityECEF"] = measurement_msg(value=self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist(),
- std=np.sqrt(P_diag[GStates.ECEF_VELOCITY]).tolist(),
- valid=kf_valid)
-
- msg_dict['gpsWeek'] = self.last_report_time.week
- msg_dict['gpsTimeOfWeek'] = self.last_report_time.tow
- msg_dict['timeToFirstFix'] = self.ttff
- msg_dict['ephemerisStatuses'] = self.create_ephem_statuses()
- out_msg.gnssMeasurements = msg_dict
- return out_msg
-
- def update_localizer(self, est_pos, t: float, measurements: List[GNSSMeasurement]):
- # Check time and outputs are valid
- valid = self.kf_valid(t)
- if not all(valid):
- if not valid[0]: # Filter not initialized
- pass
- elif not valid[1]:
- cloudlog.error("Time gap of over 10s detected, gnss kalman reset")
- elif not valid[2]:
- cloudlog.error("Gnss kalman filter state is nan")
- if est_pos is not None and len(est_pos) > 0:
- cloudlog.info(f"Reset kalman filter with {est_pos}")
- self.init_gnss_localizer(est_pos)
- else:
- return
- if len(measurements) > 0:
- kf_add_observations(self.gnss_kf, t, measurements)
- else:
- # Ensure gnss filter is updated even with no new measurements
- self.gnss_kf.predict(t)
-
- def kf_valid(self, t: float) -> List[bool]:
- filter_time = self.gnss_kf.filter.get_filter_time()
- return [not math.isnan(filter_time),
- abs(t - filter_time) < MAX_TIME_GAP,
- all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))]
-
- def init_gnss_localizer(self, est_pos):
- x_initial, p_initial_diag = np.copy(GNSSKalman.x_initial), np.copy(np.diagonal(GNSSKalman.P_initial))
- x_initial[GStates.ECEF_POS] = est_pos
- p_initial_diag[GStates.ECEF_POS] = 1000 ** 2
- self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag)
-
- def fetch_navs(self, t: GPSTime, block):
- # Download new navs if 1 hour of navs data left
- if t + SECS_IN_HR not in self.astro_dog.navs_fetched_times and (abs(t - self.last_fetch_navs_t) > SECS_IN_MIN):
- astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types, self.astro_dog.cache_dir
- ret = None
-
- if block: # Used for testing purposes
- ret = get_orbit_data(t, *astro_dog_vars)
- elif self.orbit_fetch_future is None:
- self.orbit_fetch_executor = ProcessPoolExecutor(max_workers=1)
- self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars)
- elif self.orbit_fetch_future.done():
- ret = self.orbit_fetch_future.result()
- self.orbit_fetch_executor = self.orbit_fetch_future = None
-
- if ret is not None:
- if ret[0] is None:
- self.last_fetch_navs_t = ret[2]
- else:
- self.astro_dog.navs, self.astro_dog.navs_fetched_times, self.last_fetch_navs_t = ret
- self.cache_ephemeris()
-
-
-def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir):
- astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, cache_dir=cache_dir)
- cloudlog.info(f"Start to download/parse navs for time {t.as_datetime()}")
- start_time = time.monotonic()
- try:
- astro_dog.get_navs(t)
- cloudlog.info(f"Done parsing navs. Took {time.monotonic() - start_time:.1f}s")
- cloudlog.debug(f"Downloaded navs ({sum([len(v) for v in astro_dog.navs])}): {list(astro_dog.navs.keys())}" +
- f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in astro_dog.orbit_fetched_times._ranges]}")
- return astro_dog.navs, astro_dog.navs_fetched_times, t
- except (DownloadFailed, RuntimeError, ValueError, IOError) as e:
- cloudlog.warning(f"No orbit data found or parsing failure: {e}")
- return None, None, t
-
-
-def create_measurement_msg(meas: GNSSMeasurement):
- c = log.GnssMeasurements.CorrectedMeasurement.new_message()
- c.constellationId = meas.constellation_id.value
- c.svId = meas.sv_id
- c.glonassFrequency = meas.glonass_freq if meas.constellation_id == ConstellationId.GLONASS else 0
- c.pseudorange = float(meas.observables_final['C1C'])
- c.pseudorangeStd = float(meas.observables_std['C1C'])
- c.pseudorangeRate = float(meas.observables_final['D1C'])
- c.pseudorangeRateStd = float(meas.observables_std['D1C'])
- c.satPos = meas.sat_pos_final.tolist()
- c.satVel = meas.sat_vel.tolist()
- c.satVel = meas.sat_vel.tolist()
- return c
-
-def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMeasurement]):
- ekf_data = defaultdict(list)
- for m in measurements:
- m_arr = m.as_array()
- if m.constellation_id == ConstellationId.GPS:
- ekf_data[ObservationKind.PSEUDORANGE_GPS].append(m_arr)
- elif m.constellation_id == ConstellationId.GLONASS:
- ekf_data[ObservationKind.PSEUDORANGE_GLONASS].append(m_arr)
- ekf_data[ObservationKind.PSEUDORANGE_RATE_GPS] = ekf_data[ObservationKind.PSEUDORANGE_GPS]
- ekf_data[ObservationKind.PSEUDORANGE_RATE_GLONASS] = ekf_data[ObservationKind.PSEUDORANGE_GLONASS]
- for kind, data in ekf_data.items():
- if len(data) > 0:
- gnss_kf.predict_and_observe(t, kind, data)
-
-
-def clear_tmp_cache():
- if os.path.exists(Paths.download_cache_root()):
- shutil.rmtree(Paths.download_cache_root())
- os.mkdir(Paths.download_cache_root())
-
-
-def main():
- #clear_tmp_cache()
-
- use_qcom = not Params().get_bool("UbloxAvailable")
- if use_qcom:
- raw_name = "qcomGnss"
- else:
- raw_name = "ubloxGnss"
- raw_gnss_sock = messaging.sub_sock(raw_name, conflate=False)
- pm = messaging.PubMaster(['gnssMeasurements'])
-
- # disable until set as main gps source, to better analyze startup time
- # TODO ensure low CPU usage before enabling
- use_internet = False # "LAIKAD_NO_INTERNET" not in os.environ
-
- replay = "REPLAY" in os.environ
- laikad = Laikad(save_ephemeris=not replay, auto_fetch_navs=use_internet, use_qcom=use_qcom)
-
- while True:
- for in_msg in messaging.drain_sock(raw_gnss_sock, wait_for_one=True):
- out_msg = laikad.process_gnss_msg(getattr(in_msg, raw_name), in_msg.logMonoTime, replay)
- pm.send('gnssMeasurements', out_msg)
-
-
-if __name__ == "__main__":
- main()
diff --git a/selfdrive/locationd/test/test_laikad.py b/selfdrive/locationd/test/test_laikad.py
deleted file mode 100755
index db8802ed36..0000000000
--- a/selfdrive/locationd/test/test_laikad.py
+++ /dev/null
@@ -1,319 +0,0 @@
-#!/usr/bin/env python3
-import pytest
-import time
-import unittest
-from cereal import log
-from openpilot.common.params import Params
-from datetime import datetime
-from unittest import mock
-
-
-from laika.constants import SECS_IN_DAY
-from laika.downloader import DownloadFailed
-from laika.ephemeris import EphemerisType
-from laika.gps_time import GPSTime
-from laika.helpers import ConstellationId
-from laika.raw_gnss import GNSSMeasurement, read_raw_ublox, read_raw_qcom
-from openpilot.selfdrive.locationd.laikad import EPHEMERIS_CACHE, Laikad
-from openpilot.selfdrive.test.openpilotci import get_url
-from openpilot.tools.lib.logreader import LogReader
-
-from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process
-
-GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC = GPSTime.from_datetime(datetime(2022, month=1, day=29, hour=12))
-UBLOX_TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"
-QCOM_TEST_ROUTE = "616dc83ca1f7f11e|2023-07-11--10-52-31"
-
-
-def get_log_ublox():
- logs = LogReader(get_url(UBLOX_TEST_ROUTE, 0))
-
- ublox_cfg = get_process_config("ubloxd")
- all_logs = replay_process(ublox_cfg, logs)
- low_gnss = []
- for m in all_logs:
- if m.which() != "ubloxGnss" or m.ubloxGnss.which() != 'measurementReport':
- continue
-
- MAX_MEAS = 7
- if m.ubloxGnss.measurementReport.numMeas > MAX_MEAS:
- mb = log.Event.new_message(ubloxGnss=m.ubloxGnss.to_dict())
- mb.logMonoTime = m.logMonoTime
- mb.ubloxGnss.measurementReport.numMeas = MAX_MEAS
- mb.ubloxGnss.measurementReport.measurements = list(m.ubloxGnss.measurementReport.measurements)[:MAX_MEAS]
- mb.ubloxGnss.measurementReport.measurements[0].pseudorange += 1000
- low_gnss.append(mb.as_reader())
- else:
- low_gnss.append(m)
- return all_logs, low_gnss
-
-
-def get_log_qcom():
- logs = LogReader(get_url(QCOM_TEST_ROUTE, 0))
- all_logs = [m for m in logs if m.which() == 'qcomGnss']
- return all_logs
-
-
-def verify_messages(lr, laikad, return_one_success=False):
- good_msgs = []
- for m in lr:
- if m.which() == 'ubloxGnss':
- gnss_msg = m.ubloxGnss
- elif m.which() == 'qcomGnss':
- gnss_msg = m.qcomGnss
- else:
- continue
- msg = laikad.process_gnss_msg(gnss_msg, m.logMonoTime, block=True)
- if msg is not None and len(msg.gnssMeasurements.correctedMeasurements) > 0:
- good_msgs.append(msg)
- if return_one_success:
- return msg
- return good_msgs
-
-
-def get_first_gps_time(logs):
- for m in logs:
- if m.which() == 'ubloxGnss':
- if m.ubloxGnss.which == 'measurementReport':
- new_meas = read_raw_ublox(m.ubloxGnss.measurementReport)
- if len(new_meas) > 0:
- return new_meas[0].recv_time
- elif m.which() == "qcomGnss":
- if m.qcomGnss.which == 'measurementReport':
- new_meas = read_raw_qcom(m.qcomGnss.measurementReport)
- if len(new_meas) > 0:
- return new_meas[0].recv_time
-
-
-def get_measurement_mock(gpstime, sat_ephemeris):
- meas = GNSSMeasurement(ConstellationId.GPS, 1, gpstime.week, gpstime.tow, {'C1C': 0., 'D1C': 0.}, {'C1C': 0., 'D1C': 0.})
- # Fake measurement being processed
- meas.observables_final = meas.observables
- meas.sat_ephemeris = sat_ephemeris
- return meas
-
-
-@pytest.mark.slow
-class TestLaikad(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- logs, low_gnss = get_log_ublox()
- cls.logs = logs
- cls.low_gnss = low_gnss
- cls.logs_qcom = get_log_qcom()
- first_gps_time = get_first_gps_time(logs)
- cls.first_gps_time = first_gps_time
-
- def setUp(self):
- Params().remove(EPHEMERIS_CACHE)
-
- def test_fetch_navs_non_blocking(self):
- gpstime = GPSTime.from_datetime(datetime(2021, month=3, day=1))
- laikad = Laikad()
- laikad.fetch_navs(gpstime, block=False)
- laikad.orbit_fetch_future.result(30)
-
- # Get results and save orbits to laikad:
- laikad.fetch_navs(gpstime, block=False)
- ephem = laikad.astro_dog.navs['G01'][0]
- self.assertIsNotNone(ephem)
-
- laikad.fetch_navs(gpstime+2*SECS_IN_DAY, block=False)
- laikad.orbit_fetch_future.result(30)
- # Get results and save orbits to laikad:
- laikad.fetch_navs(gpstime + 2 * SECS_IN_DAY, block=False)
-
- ephem2 = laikad.astro_dog.navs['G01'][0]
- self.assertIsNotNone(ephem)
- self.assertNotEqual(ephem, ephem2)
-
-
- def test_fetch_navs_with_wrong_clocks(self):
- laikad = Laikad()
-
- def check_has_navs():
- self.assertGreater(len(laikad.astro_dog.navs), 0)
- ephem = laikad.astro_dog.navs['G01'][0]
- self.assertIsNotNone(ephem)
- real_current_time = GPSTime.from_datetime(datetime(2021, month=3, day=1))
- wrong_future_clock_time = real_current_time + SECS_IN_DAY
-
- laikad.fetch_navs(wrong_future_clock_time, block=True)
- check_has_navs()
- self.assertEqual(laikad.last_fetch_navs_t, wrong_future_clock_time)
-
- # Test fetching orbits with earlier time
- assert real_current_time < laikad.last_fetch_navs_t
-
- laikad.astro_dog.orbits = {}
- laikad.fetch_navs(real_current_time, block=True)
- check_has_navs()
- self.assertEqual(laikad.last_fetch_navs_t, real_current_time)
-
- def test_laika_online(self):
- laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT)
- correct_msgs = verify_messages(self.logs, laikad)
-
- correct_msgs_expected = 560
- self.assertEqual(correct_msgs_expected, len(correct_msgs))
- self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
-
- def test_kf_becomes_valid(self):
- laikad = Laikad(auto_update=False)
- m = self.logs[0]
- self.assertFalse(all(laikad.kf_valid(m.logMonoTime * 1e-9)))
- kf_valid = False
- for m in self.logs:
- if m.which() != 'ubloxGnss':
- continue
-
- laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True)
- kf_valid = all(laikad.kf_valid(m.logMonoTime * 1e-9))
- if kf_valid:
- break
- self.assertTrue(kf_valid)
-
- def test_laika_online_nav_only(self):
- for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs], strict=True):
- laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV, use_qcom=use_qcom)
- # Disable fetch_orbits to test NAV only
- correct_msgs = verify_messages(logs, laikad)
- correct_msgs_expected = 55 if use_qcom else 560
- valid_fix_expected = 55 if use_qcom else 560
-
- self.assertEqual(correct_msgs_expected, len(correct_msgs))
- self.assertEqual(valid_fix_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
-
- @mock.patch('laika.downloader.download_and_cache_file')
- def test_laika_offline(self, downloader_mock):
- downloader_mock.side_effect = DownloadFailed("Mock download failed")
- laikad = Laikad(auto_update=False)
- laikad.fetch_navs(GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC, block=True)
-
- @mock.patch('laika.downloader.download_and_cache_file')
- def test_download_failed_russian_source(self, downloader_mock):
- downloader_mock.side_effect = DownloadFailed
- laikad = Laikad(auto_update=False)
- correct_msgs = verify_messages(self.logs, laikad)
- expected_msgs = 376
- self.assertEqual(expected_msgs, len(correct_msgs))
- self.assertEqual(expected_msgs, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
-
- def test_laika_get_orbits(self):
- laikad = Laikad(auto_update=False)
- # Pretend process has loaded the orbits on startup by using the time of the first gps message.
- laikad.fetch_navs(self.first_gps_time, block=True)
- self.dict_has_values(laikad.astro_dog.navs)
-
- @unittest.skip("Use to debug live data")
- def test_laika_get_navs_now(self):
- laikad = Laikad(auto_update=False)
- laikad.fetch_navs(GPSTime.from_datetime(datetime.utcnow()), block=True)
- prn = "G01"
- self.assertGreater(len(laikad.astro_dog.navs[prn]), 0)
- prn = "R01"
- self.assertGreater(len(laikad.astro_dog.navs[prn]), 0)
-
- def test_get_navs_in_process(self):
- for auto_fetch_navs in [True, False]:
- for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs], strict=True):
- laikad = Laikad(auto_update=False, use_qcom=use_qcom, auto_fetch_navs=auto_fetch_navs)
- has_navs = False
- has_fix = False
- seen_chip_eph = False
- seen_internet_eph = False
-
- for m in logs:
- if m.which() != 'ubloxGnss' and m.which() != 'qcomGnss':
- continue
-
- gnss_msg = m.qcomGnss if use_qcom else m.ubloxGnss
- out_msg = laikad.process_gnss_msg(gnss_msg, m.logMonoTime, block=False)
- if laikad.orbit_fetch_future is not None:
- laikad.orbit_fetch_future.result()
- vals = laikad.astro_dog.navs.values()
- has_navs = len(vals) > 0 and max([len(v) for v in vals]) > 0
- vals = laikad.astro_dog.qcom_polys.values()
- has_polys = len(vals) > 0 and max([len(v) for v in vals]) > 0
- has_fix = has_fix or out_msg.gnssMeasurements.positionECEF.valid
- if len(out_msg.gnssMeasurements.ephemerisStatuses):
- seen_chip_eph = seen_chip_eph or any(x.source == 'gnssChip' for x in out_msg.gnssMeasurements.ephemerisStatuses)
- seen_internet_eph = seen_internet_eph or any(x.source == 'internet' for x in out_msg.gnssMeasurements.ephemerisStatuses)
-
- self.assertTrue(has_navs or has_polys)
- self.assertTrue(has_fix)
- self.assertTrue(seen_chip_eph or auto_fetch_navs)
- self.assertTrue(seen_internet_eph or not auto_fetch_navs)
- self.assertEqual(len(laikad.astro_dog.navs_fetched_times._ranges), 0)
- self.assertEqual(None, laikad.orbit_fetch_future)
-
- def test_cache(self):
- use_qcom = True
- for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs], strict=True):
- Params().remove(EPHEMERIS_CACHE)
- laikad = Laikad(auto_update=True, save_ephemeris=True, use_qcom=use_qcom)
- def wait_for_cache():
- max_time = 2
- while Params().get(EPHEMERIS_CACHE) is None:
- time.sleep(0.1)
- max_time -= 0.1
- if max_time < 0:
- self.fail("Cache has not been written after 2 seconds")
-
- # Test cache with no ephemeris
- laikad.last_report_time = GPSTime(1,0)
- laikad.cache_ephemeris()
- if Params().get(EPHEMERIS_CACHE) is not None:
- self.fail("Cache should not have been written without valid ephem")
-
- #laikad.astro_dog.get_navs(self.first_gps_time)
- msg = verify_messages(logs, laikad, return_one_success=True)
- laikad.cache_ephemeris()
- wait_for_cache()
-
- # Check both nav and orbits separate
- laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV,
- save_ephemeris=True, use_qcom=use_qcom, auto_fetch_navs=False)
- # Verify navs are loaded from cache
- self.dict_has_values(laikad.astro_dog.navs)
- # Verify cache is working for only nav by running a segment
- msg = verify_messages(logs, laikad, return_one_success=True)
- self.assertTrue(len(msg.gnssMeasurements.ephemerisStatuses))
- self.assertTrue(any(x.source=='cache' for x in msg.gnssMeasurements.ephemerisStatuses))
- self.assertIsNotNone(msg)
-
- #TODO test cache with only orbits
- #with patch('selfdrive.locationd.laikad.get_orbit_data', return_value=None) as mock_method:
- # # Verify no orbit downloads even if orbit fetch times is reset since the cache has recently been saved and we don't want to download high frequently
- # laikad.astro_dog.orbit_fetched_times = TimeRangeHolder()
- # laikad.fetch_navs(self.first_gps_time, block=False)
- # mock_method.assert_not_called()
-
- # # Verify cache is working for only orbits by running a segment
- # laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT, save_ephemeris=True)
- # msg = verify_messages(self.logs, laikad, return_one_success=True)
- # self.assertIsNotNone(msg)
- # # Verify orbit data is not downloaded
- # mock_method.assert_not_called()
- #break
-
- def test_low_gnss_meas(self):
- cnt = 0
- laikad = Laikad()
- for m in self.low_gnss:
- msg = laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True)
- if msg is None:
- continue
- gm = msg.gnssMeasurements
- if len(gm.correctedMeasurements) != 0 and gm.positionECEF.valid:
- cnt += 1
- self.assertEqual(cnt, 560)
-
- def dict_has_values(self, dct):
- self.assertGreater(len(dct), 0)
- self.assertGreater(min([len(v) for v in dct.values()]), 0)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py
index cdcefd5169..81cf26fcfa 100644
--- a/selfdrive/manager/process_config.py
+++ b/selfdrive/manager/process_config.py
@@ -45,7 +45,6 @@ procs = [
DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"),
NativeProcess("camerad", "system/camerad", ["./camerad"], driverview),
- NativeProcess("clocksd", "system/clocksd", ["./clocksd"], only_onroad),
NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad),
NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad),
PythonProcess("logmessaged", "system.logmessaged", always_run),
@@ -68,7 +67,6 @@ procs = [
PythonProcess("controlsd", "selfdrive.controls.controlsd", only_onroad),
PythonProcess("deleter", "system.loggerd.deleter", always_run),
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)),
- PythonProcess("laikad", "selfdrive.locationd.laikad", only_onroad),
PythonProcess("rawgpsd", "system.sensord.rawgps.rawgpsd", qcomgps, enabled=TICI),
PythonProcess("navd", "selfdrive.navd.navd", only_onroad),
PythonProcess("pandad", "selfdrive.boardd.pandad", always_run),
diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py
index a5ac977390..e03d93d7e8 100644
--- a/selfdrive/modeld/constants.py
+++ b/selfdrive/modeld/constants.py
@@ -25,6 +25,8 @@ class ModelConstants:
# model outputs constants
FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32)
FCW_THRESHOLDS_3MS2 = np.array([.7, .7], dtype=np.float32)
+ FCW_5MS2_PROBS_WIDTH = 5
+ FCW_3MS2_PROBS_WIDTH = 2
DISENGAGE_WIDTH = 5
POSE_WIDTH = 6
diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py
index dda2e41258..55d7abc1aa 100644
--- a/selfdrive/modeld/fill_model_msg.py
+++ b/selfdrive/modeld/fill_model_msg.py
@@ -1,16 +1,19 @@
+import os
import capnp
import numpy as np
from typing import Dict
from cereal import log
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta
+SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
+
ConfidenceClass = log.ModelDataV2.ConfidenceClass
class PublishState:
def __init__(self):
self.disengage_buffer = np.zeros(ModelConstants.CONFIDENCE_BUFFER_LEN*ModelConstants.DISENGAGE_WIDTH, dtype=np.float32)
- self.prev_brake_5ms2_probs = np.zeros(ModelConstants.DISENGAGE_WIDTH, dtype=np.float32)
- self.prev_brake_3ms2_probs = np.zeros(ModelConstants.DISENGAGE_WIDTH, dtype=np.float32)
+ self.prev_brake_5ms2_probs = np.zeros(ModelConstants.FCW_5MS2_PROBS_WIDTH, dtype=np.float32)
+ self.prev_brake_3ms2_probs = np.zeros(ModelConstants.FCW_3MS2_PROBS_WIDTH, dtype=np.float32)
def fill_xyzt(builder, t, x, y, z, x_std=None, y_std=None, z_std=None):
builder.t = t
@@ -163,6 +166,10 @@ def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str,
else:
modelV2.confidence = ConfidenceClass.red
+ # raw prediction if enabled
+ if SEND_RAW_PRED:
+ modelV2.rawPredictions = net_output_data['raw_pred'].tobytes()
+
def fill_pose_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, np.ndarray],
vipc_frame_id: int, vipc_dropped_frames: int, timestamp_eof: int, live_calib_seen: bool) -> None:
msg.valid = live_calib_seen & (vipc_dropped_frames < 1)
diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py
index 8bfdaaa350..5df4e5e172 100755
--- a/selfdrive/modeld/modeld.py
+++ b/selfdrive/modeld/modeld.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import sys
+import os
import time
import pickle
import numpy as np
@@ -20,6 +21,8 @@ from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.modeld.models.commonmodel_pyx import ModelFrame, CLContext
+SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
+
MODEL_PATHS = {
ModelRunner.THNEED: Path(__file__).parent / 'models/supercombo.thneed',
ModelRunner.ONNX: Path(__file__).parent / 'models/supercombo.onnx'}
@@ -70,7 +73,10 @@ class ModelState:
self.model.addInput(k, v)
def slice_outputs(self, model_outputs: np.ndarray) -> Dict[str, np.ndarray]:
- return {k: model_outputs[np.newaxis, v] for k,v in self.output_slices.items()}
+ parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in self.output_slices.items()}
+ if SEND_RAW_PRED:
+ parsed_model_outputs['raw_pred'] = model_outputs.copy()
+ return parsed_model_outputs
def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray,
inputs: Dict[str, np.ndarray], prepare_only: bool) -> Optional[Dict[str, np.ndarray]]:
diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md
index 6992d46f1d..bec3eb9016 100644
--- a/selfdrive/test/process_replay/README.md
+++ b/selfdrive/test/process_replay/README.md
@@ -17,7 +17,6 @@ Currently the following processes are tested:
* locationd
* paramsd
* ubloxd
-* laikad
* torqued
### Usage
@@ -71,7 +70,7 @@ lr = LogReader(...)
output_logs = replay_process_with_name('locationd', lr)
# or list of names
-output_logs = replay_process_with_name(['ubloxd', 'locationd', 'laikad'], lr)
+output_logs = replay_process_with_name(['ubloxd', 'locationd'], lr)
```
Supported processes:
@@ -83,7 +82,6 @@ Supported processes:
* locationd
* paramsd
* ubloxd
-* laikad
* torqued
* modeld
* dmonitoringmodeld
diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py
index 9aab541991..0cb8ac9cac 100755
--- a/selfdrive/test/process_replay/process_replay.py
+++ b/selfdrive/test/process_replay/process_replay.py
@@ -448,16 +448,6 @@ def controlsd_config_callback(params, cfg, lr):
params.put("ReplayControlsState", controlsState.as_builder().to_bytes())
-def laikad_config_pubsub_callback(params, cfg, lr):
- ublox = params.get_bool("UbloxAvailable")
- main_key = "ubloxGnss" if ublox else "qcomGnss"
- sub_keys = ({"qcomGnss", } if ublox else {"ubloxGnss", })
-
- cfg.pubs = set(cfg.pubs) - sub_keys
- cfg.main_pub = main_key
- cfg.main_pub_drained = True
-
-
def locationd_config_pubsub_callback(params, cfg, lr):
ublox = params.get_bool("UbloxAvailable")
sub_keys = ({"gpsLocation", } if ublox else {"gpsLocationExternal", })
@@ -543,17 +533,6 @@ CONFIGS = [
subs=["ubloxGnss", "gpsLocationExternal"],
ignore=["logMonoTime"],
),
- ProcessConfig(
- proc_name="laikad",
- pubs=["ubloxGnss", "qcomGnss"],
- subs=["gnssMeasurements"],
- ignore=["logMonoTime"],
- config_callback=laikad_config_pubsub_callback,
- tolerance=NUMPY_TOLERANCE,
- processing_time=0.002,
- timeout=60*10, # first messages are blocked on internet assistance
- main_pub="ubloxGnss", # config_callback will switch this to qcom if needed
- ),
ProcessConfig(
proc_name="torqued",
pubs=["liveLocationKalman", "carState", "carControl"],
diff --git a/selfdrive/test/process_replay/test_fuzzy.py b/selfdrive/test/process_replay/test_fuzzy.py
index f3f48d0159..4b8629fc7d 100755
--- a/selfdrive/test/process_replay/test_fuzzy.py
+++ b/selfdrive/test/process_replay/test_fuzzy.py
@@ -13,7 +13,7 @@ import openpilot.selfdrive.test.process_replay.process_replay as pr
# These processes currently fail because of unrealistic data breaking assumptions
# that openpilot makes causing error with NaN, inf, int size, array indexing ...
# TODO: Make each one testable
-NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'laikad', 'dmonitoringmodeld', 'modeld']
+NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'dmonitoringmodeld', 'modeld']
TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED]
class TestFuzzProcesses(unittest.TestCase):
diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py
index 1d380ba5eb..6d0cc204c5 100755
--- a/selfdrive/test/profiling/profiler.py
+++ b/selfdrive/test/profiling/profiler.py
@@ -83,14 +83,12 @@ if __name__ == '__main__':
from openpilot.selfdrive.controls.radard import radard_thread
from openpilot.selfdrive.locationd.paramsd import main as paramsd_thread
from openpilot.selfdrive.controls.plannerd import main as plannerd_thread
- from openpilot.selfdrive.locationd.laikad import main as laikad_thread
procs = {
'radard': radard_thread,
'controlsd': controlsd_thread,
'paramsd': paramsd_thread,
'plannerd': plannerd_thread,
- 'laikad': laikad_thread,
}
proc = sys.argv[1]
diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py
index 4a9fc85d2e..e4f6bccf6c 100755
--- a/selfdrive/test/test_onroad.py
+++ b/selfdrive/test/test_onroad.py
@@ -47,7 +47,6 @@ PROCS = {
"selfdrive.monitoring.dmonitoringd": 4.0,
"./proclogd": 1.54,
"system.logmessaged": 0.2,
- "./clocksd": 0.02,
"selfdrive.tombstoned": 0,
"./logcatd": 0,
"system.micd": 10.0,
@@ -57,7 +56,6 @@ PROCS = {
"selfdrive.navd.navd": 0.4,
"system.loggerd.uploader": 3.0,
"system.loggerd.deleter": 0.1,
- "selfdrive.locationd.laikad": (1.0, 80.0), # TODO: better GPS setup in testing closet
}
PROCS.update({
diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py
index 8157066334..326cb4fa56 100755
--- a/selfdrive/test/update_ci_routes.py
+++ b/selfdrive/test/update_ci_routes.py
@@ -8,7 +8,6 @@ from azure.storage.blob import ContainerClient
from tqdm import tqdm
from openpilot.selfdrive.car.tests.routes import routes as test_car_models_routes
-from openpilot.selfdrive.locationd.test.test_laikad import UBLOX_TEST_ROUTE, QCOM_TEST_ROUTE
from openpilot.selfdrive.test.process_replay.test_processes import source_segments as replay_segments
from openpilot.selfdrive.test.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, DATA_CI_CONTAINER,
get_azure_credential, get_container_sas)
@@ -90,7 +89,6 @@ if __name__ == "__main__":
if not len(to_sync):
# sync routes from the car tests routes and process replay
- to_sync.extend([UBLOX_TEST_ROUTE, QCOM_TEST_ROUTE])
to_sync.extend([rt.route for rt in test_car_models_routes])
to_sync.extend([s[1].rsplit('--', 1)[0] for s in replay_segments])
diff --git a/system/clocksd/.gitignore b/system/clocksd/.gitignore
deleted file mode 100644
index a6d841d65e..0000000000
--- a/system/clocksd/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-clocksd
diff --git a/system/clocksd/SConscript b/system/clocksd/SConscript
deleted file mode 100644
index d1cf13e9e8..0000000000
--- a/system/clocksd/SConscript
+++ /dev/null
@@ -1,2 +0,0 @@
-Import('env', 'common', 'cereal', 'messaging')
-env.Program('clocksd.cc', LIBS=[common, cereal, messaging, 'capnp', 'zmq', 'kj'])
diff --git a/system/clocksd/clocksd.cc b/system/clocksd/clocksd.cc
deleted file mode 100644
index a5912cf51a..0000000000
--- a/system/clocksd/clocksd.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-#include
-#include
-#include
-
-#include
-#include
-
-// Apple doesn't have timerfd
-#ifdef __APPLE__
-#include
-#else
-#include
-#endif
-
-#include
-#include
-
-#include "cereal/messaging/messaging.h"
-#include "common/timing.h"
-#include "common/util.h"
-
-ExitHandler do_exit;
-
-int main() {
- setpriority(PRIO_PROCESS, 0, -13);
- PubMaster pm({"clocks"});
-
-#ifndef __APPLE__
- int timerfd = timerfd_create(CLOCK_BOOTTIME, 0);
- assert(timerfd >= 0);
-
- struct itimerspec spec = {0};
- spec.it_interval.tv_sec = 1;
- spec.it_interval.tv_nsec = 0;
- spec.it_value.tv_sec = 1;
- spec.it_value.tv_nsec = 0;
-
- int err = timerfd_settime(timerfd, 0, &spec, 0);
- assert(err == 0);
-
- uint64_t expirations = 0;
- while (!do_exit && (err = read(timerfd, &expirations, sizeof(expirations)))) {
- if (err < 0) {
- if (errno == EINTR) continue;
- break;
- }
-#else
- // Just run at 1Hz on apple
- while (!do_exit) {
- util::sleep_for(1000);
-#endif
-
- uint64_t boottime = nanos_since_boot();
- uint64_t monotonic = nanos_monotonic();
- uint64_t monotonic_raw = nanos_monotonic_raw();
- uint64_t wall_time = nanos_since_epoch();
-
- MessageBuilder msg;
- auto clocks = msg.initEvent().initClocks();
-
- clocks.setBootTimeNanos(boottime);
- clocks.setMonotonicNanos(monotonic);
- clocks.setMonotonicRawNanos(monotonic_raw);
- clocks.setWallTimeNanos(wall_time);
-
- pm.send("clocks", msg);
- }
-
-#ifndef __APPLE__
- close(timerfd);
-#endif
- return 0;
-}
diff --git a/system/sensord/rawgps/test_rawgps.py b/system/sensord/rawgps/test_rawgps.py
index 02777d5a1d..2a0ee656e2 100755
--- a/system/sensord/rawgps/test_rawgps.py
+++ b/system/sensord/rawgps/test_rawgps.py
@@ -5,13 +5,11 @@ import time
import datetime
import unittest
import subprocess
-import numpy as np
import cereal.messaging as messaging
from openpilot.system.hardware import TICI
from openpilot.system.sensord.rawgps.rawgpsd import at_cmd, wait_for_modem
from openpilot.selfdrive.manager.process_config import managed_processes
-from openpilot.common.transformations.coordinates import ecef_from_geodetic
GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0')))
@@ -123,22 +121,5 @@ class TestRawgpsd(unittest.TestCase):
managed_processes['rawgpsd'].stop()
self.check_assistance(True)
- @unittest.skipIf(not GOOD_SIGNAL, "No good GPS signal")
- def test_fix(self):
- managed_processes['rawgpsd'].start()
- managed_processes['laikad'].start()
- assert self._wait_for_output(60)
- assert self.sm.updated['qcomGnss']
- assert self.sm.updated['gpsLocation']
- assert self.sm['gpsLocation'].flags == 1
- module_fix = ecef_from_geodetic([self.sm['gpsLocation'].latitude,
- self.sm['gpsLocation'].longitude,
- self.sm['gpsLocation'].altitude])
- assert self.sm['gnssMeasurements'].positionECEF.valid
- total_diff = np.array(self.sm['gnssMeasurements'].positionECEF.value) - module_fix
- self.assertLess(np.linalg.norm(total_diff), 100)
- managed_processes['laikad'].stop()
- managed_processes['rawgpsd'].stop()
-
if __name__ == "__main__":
unittest.main(failfast=True)
diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc
index 2bd6cfd6cc..dee7cc5248 100644
--- a/tools/cabana/chart/chart.cc
+++ b/tools/cabana/chart/chart.cc
@@ -279,38 +279,50 @@ void ChartView::updateSeriesPoints() {
}
}
-void ChartView::updateSeries(const cabana::Signal *sig, bool clear) {
+void ChartView::appendCanEvents(const cabana::Signal *sig, const std::vector &events,
+ std::vector &vals, std::vector &step_vals) {
+ vals.reserve(vals.size() + events.capacity());
+ step_vals.reserve(step_vals.size() + events.capacity() * 2);
+
+ double value = 0;
+ const uint64_t begin_mono_time = can->routeStartTime() * 1e9;
+ for (const CanEvent *e : events) {
+ if (sig->getValue(e->dat, e->size, &value)) {
+ const double ts = (e->mono_time - std::min(e->mono_time, begin_mono_time)) / 1e9;
+ vals.emplace_back(ts, value);
+ if (!step_vals.empty())
+ step_vals.emplace_back(ts, step_vals.back().y());
+ step_vals.emplace_back(ts, value);
+ }
+ }
+}
+
+void ChartView::updateSeries(const cabana::Signal *sig, const MessageEventsMap *msg_new_events) {
for (auto &s : sigs) {
if (!sig || s.sig == sig) {
- if (clear) {
+ if (!msg_new_events) {
s.vals.clear();
s.step_vals.clear();
- s.last_value_mono_time = 0;
+ }
+ auto events = msg_new_events ? msg_new_events : &can->eventsMap();
+ auto it = events->find(s.msg_id);
+ if (it == events->end() || it->second.empty()) continue;
+
+ if (s.vals.empty() || (it->second.back()->mono_time / 1e9 - can->routeStartTime()) > s.vals.back().x()) {
+ appendCanEvents(s.sig, it->second, s.vals, s.step_vals);
+ } else {
+ std::vector vals, step_vals;
+ appendCanEvents(s.sig, it->second, vals, step_vals);
+ s.vals.insert(std::lower_bound(s.vals.begin(), s.vals.end(), vals.front().x(), xLessThan),
+ vals.begin(), vals.end());
+ s.step_vals.insert(std::lower_bound(s.step_vals.begin(), s.step_vals.end(), step_vals.front().x(), xLessThan),
+ step_vals.begin(), step_vals.end());
}
- const auto &msgs = can->events(s.msg_id);
- s.vals.reserve(msgs.capacity());
- s.step_vals.reserve(msgs.capacity() * 2);
-
- auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), s.last_value_mono_time, CompareCanEvent());
- const double route_start_time = can->routeStartTime();
- for (auto end = msgs.cend(); first != end; ++first) {
- const CanEvent *e = *first;
- double value = 0;
- if (s.sig->getValue(e->dat, e->size, &value)) {
- double ts = e->mono_time / 1e9 - route_start_time; // seconds
- s.vals.append({ts, value});
- if (!s.step_vals.empty()) {
- s.step_vals.append({ts, s.step_vals.back().y()});
- }
- s.step_vals.append({ts, value});
- s.last_value_mono_time = e->mono_time;
- }
- }
if (!can->liveStreaming()) {
s.segment_tree.build(s.vals);
}
- s.series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals);
+ s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals));
}
}
updateAxisY();
@@ -320,7 +332,7 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) {
// auto zoom on yaxis
void ChartView::updateAxisY() {
- if (sigs.isEmpty()) return;
+ if (sigs.empty()) return;
double min = std::numeric_limits::max();
double max = std::numeric_limits::lowest();
@@ -344,9 +356,7 @@ void ChartView::updateAxisY() {
if (it->y() > s.max) s.max = it->y();
}
} else {
- auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last));
- s.min = min_y;
- s.max = max_y;
+ std::tie(s.min, s.max) = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last));
}
min = std::min(min, s.min);
max = std::max(max, s.max);
@@ -365,7 +375,7 @@ void ChartView::updateAxisY() {
axis_y->setRange(min_y, max_y);
axis_y->setTickCount(tick_count);
- int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1;
+ int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0);
int max_label_width = 0;
QFontMetrics fm(axis_y->labelsFont());
for (int i = 0; i < tick_count; i++) {
@@ -453,7 +463,7 @@ static QPixmap getDropPixmap(const QPixmap &src) {
return px;
}
-void ChartView::contextMenuEvent(QContextMenuEvent *event) {
+void ChartView::contextMenuEvent(QContextMenuEvent *event) {
QMenu context_menu(this);
context_menu.addActions(menu->actions());
context_menu.addSeparator();
@@ -492,13 +502,9 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) {
rubber->hide();
auto rect = rubber->geometry().normalized();
- double min = chart()->mapToValue(rect.topLeft()).x();
- double max = chart()->mapToValue(rect.bottomRight()).x();
-
// Prevent zooming/seeking past the end of the route
- min = std::clamp(min, 0., can->totalSeconds());
- max = std::clamp(max, 0., can->totalSeconds());
-
+ double min = std::clamp(chart()->mapToValue(rect.topLeft()).x(), 0., can->totalSeconds());
+ double max = std::clamp(chart()->mapToValue(rect.bottomRight()).x(), 0., can->totalSeconds());
if (rubber->width() <= 0) {
// no rubber dragged, seek to mouse position
can->seekTo(min);
@@ -623,7 +629,7 @@ void ChartView::dropEvent(QDropEvent *event) {
source_chart->chart()->removeSeries(s.series);
addSeries(s.series);
}
- sigs.append(source_chart->sigs);
+ sigs.insert(sigs.end(), std::move_iterator(source_chart->sigs.begin()), std::move_iterator(source_chart->sigs.end()));
updateAxisY();
updateTitle();
startAnimation();
@@ -763,13 +769,8 @@ void ChartView::drawSignalValue(QPainter *painter) {
painter->setPen(chart()->legend()->labelColor());
int i = 0;
for (auto &s : sigs) {
- QString value = "--";
- if (s.series->isVisible()) {
- auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; });
- if (it != s.vals.crend() && it->x() >= axis_x->min()) {
- value = s.sig->formatValue(it->y());
- }
- }
+ auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; });
+ QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? s.sig->formatValue(it->y()) : "--";
QRectF marker_rect = legend_markers[i++]->sceneBoundingRect();
QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size());
QString elided_val = painter->fontMetrics().elidedText(value, Qt::ElideRight, value_rect.width());
@@ -841,9 +842,8 @@ void ChartView::setSeriesType(SeriesType type) {
s.series->deleteLater();
}
for (auto &s : sigs) {
- auto series = createSeries(series_type, s.sig->color);
- series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals);
- s.series = series;
+ s.series = createSeries(series_type, s.sig->color);
+ s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals));
}
updateSeriesPoints();
updateTitle();
diff --git a/tools/cabana/chart/chart.h b/tools/cabana/chart/chart.h
index f91b81cc91..896eaaf2a3 100644
--- a/tools/cabana/chart/chart.h
+++ b/tools/cabana/chart/chart.h
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
@@ -31,7 +32,7 @@ public:
ChartView(const std::pair &x_range, ChartsWidget *parent = nullptr);
void addSignal(const MessageId &msg_id, const cabana::Signal *sig);
bool hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const;
- void updateSeries(const cabana::Signal *sig = nullptr, bool clear = true);
+ void updateSeries(const cabana::Signal *sig = nullptr, const MessageEventsMap *msg_new_events = nullptr);
void updatePlot(double cur, double min, double max);
void setSeriesType(SeriesType type);
void updatePlotArea(int left, bool force = false);
@@ -43,9 +44,8 @@ public:
MessageId msg_id;
const cabana::Signal *sig = nullptr;
QXYSeries *series = nullptr;
- QVector vals;
- QVector step_vals;
- uint64_t last_value_mono_time = 0;
+ std::vector vals;
+ std::vector step_vals;
QPointF track_pt{};
SegmentTree segment_tree;
double min = 0;
@@ -64,6 +64,8 @@ private slots:
void signalRemoved(const cabana::Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); }
private:
+ void appendCanEvents(const cabana::Signal *sig, const std::vector &events,
+ std::vector &vals, std::vector &step_vals);
void createToolButtons();
void addSeries(QXYSeries *series);
void contextMenuEvent(QContextMenuEvent *event) override;
@@ -107,7 +109,7 @@ private:
QGraphicsProxyWidget *close_btn_proxy;
QGraphicsProxyWidget *manage_btn_proxy;
TipLabel tip_label;
- QList sigs;
+ std::vector sigs;
double cur_sec = 0;
SeriesType series_type = SeriesType::Line;
bool is_scrubbing = false;
diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc
index 64eb99325c..63e103793c 100644
--- a/tools/cabana/chart/chartswidget.cc
+++ b/tools/cabana/chart/chartswidget.cc
@@ -12,7 +12,7 @@
#include "tools/cabana/chart/chart.h"
const int MAX_COLUMN_COUNT = 4;
-const int CHART_SPACING = 10;
+const int CHART_SPACING = 4;
ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_timer(this), QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
@@ -78,8 +78,9 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_tim
// charts
charts_container = new ChartsContainer(this);
-
+ charts_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
charts_scroll = new QScrollArea(this);
+ charts_scroll->viewport()->setBackgroundRole(QPalette::Base);
charts_scroll->setFrameStyle(QFrame::NoFrame);
charts_scroll->setWidgetResizable(true);
charts_scroll->setWidget(charts_container);
@@ -149,11 +150,10 @@ void ChartsWidget::updateTabBar() {
}
}
-void ChartsWidget::eventsMerged() {
+void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) {
QFutureSynchronizer future_synchronizer;
- bool clear = !can->liveStreaming();
for (auto c : charts) {
- future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, clear));
+ future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, &new_events));
}
}
@@ -190,7 +190,7 @@ void ChartsWidget::updateState() {
if (pos < 0 || pos > 0.8) {
display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1);
}
- double max_sec = std::min(std::floor(display_range.first + max_chart_range), can->totalSeconds());
+ double max_sec = std::min(display_range.first + max_chart_range, can->totalSeconds());
display_range.first = std::max(0.0, max_sec - max_chart_range);
display_range.second = display_range.first + max_chart_range;
} else if (cur_sec < (zoomed_range.first - 0.1) || cur_sec >= zoomed_range.second) {
@@ -282,7 +282,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) {
it->series->setColor(it->sig->color);
c->addSeries(it->series);
- c->sigs.push_back(*it);
+ c->sigs.emplace_back(std::move(*it));
c->updateAxisY();
c->updateTitle();
it = src_chart->sigs.erase(it);
@@ -322,7 +322,7 @@ void ChartsWidget::updateLayout(bool force) {
}
for (int i = 0; i < current_charts.size(); ++i) {
charts_layout->addWidget(current_charts[i], i / n, i % n);
- if (current_charts[i]->sigs.isEmpty()) {
+ if (current_charts[i]->sigs.empty()) {
// the chart will be resized after add signal. delay setVisible to reduce flicker.
QTimer::singleShot(0, [c = current_charts[i]]() { c->setVisible(true); });
} else {
@@ -474,8 +474,9 @@ bool ChartsWidget::event(QEvent *event) {
ChartsContainer::ChartsContainer(ChartsWidget *parent) : charts_widget(parent), QWidget(parent) {
setAcceptDrops(true);
+ setBackgroundRole(QPalette::Window);
QVBoxLayout *charts_main_layout = new QVBoxLayout(this);
- charts_main_layout->setContentsMargins(0, 10, 0, 0);
+ charts_main_layout->setContentsMargins(0, CHART_SPACING, 0, CHART_SPACING);
charts_layout = new QGridLayout();
charts_layout->setSpacing(CHART_SPACING);
charts_main_layout->addLayout(charts_layout);
@@ -519,15 +520,11 @@ void ChartsContainer::paintEvent(QPaintEvent *ev) {
r.setHeight(CHART_SPACING);
}
- const int margin = (CHART_SPACING - 2) / 2;
- QPainterPath path;
- path.addPolygon(QPolygonF({r.topLeft(), QPointF(r.left() + CHART_SPACING, r.top() + r.height() / 2), r.bottomLeft()}));
- path.addPolygon(QPolygonF({r.topRight(), QPointF(r.right() - CHART_SPACING, r.top() + r.height() / 2), r.bottomRight()}));
-
QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing);
- p.fillPath(path, palette().highlight());
- p.fillRect(r.adjusted(2, margin, -2, -margin), palette().highlight());
+ p.setPen(QPen(palette().highlight(), 2));
+ p.drawLine(r.topLeft() + QPoint(1, 0), r.bottomLeft() + QPoint(1, 0));
+ p.drawLine(r.topLeft() + QPoint(0, r.height() / 2), r.topRight() + QPoint(0, r.height() / 2));
+ p.drawLine(r.topRight(), r.bottomRight());
}
}
diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h
index 6a7b535543..c85cd09963 100644
--- a/tools/cabana/chart/chartswidget.h
+++ b/tools/cabana/chart/chartswidget.h
@@ -63,7 +63,7 @@ private:
void removeChart(ChartView *chart);
void splitChart(ChartView *chart);
QRect chartVisibleRect(ChartView *chart);
- void eventsMerged();
+ void eventsMerged(const MessageEventsMap &new_events);
void updateState();
void zoomReset();
void startAutoScroll();
diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc
index 87fa267d5a..472ffce104 100644
--- a/tools/cabana/detailwidget.cc
+++ b/tools/cabana/detailwidget.cc
@@ -2,6 +2,7 @@
#include
#include
+#include
#include "tools/cabana/commands.h"
#include "tools/cabana/mainwin.h"
@@ -22,19 +23,15 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
// message title
QHBoxLayout *title_layout = new QHBoxLayout();
title_layout->setContentsMargins(3, 6, 3, 0);
- time_label = new QLabel(this);
- time_label->setToolTip(tr("Current time"));
- time_label->setStyleSheet("QLabel{font-weight:bold;}");
- title_layout->addWidget(time_label);
- name_label = new ElidedLabel(this);
+ auto spacer = new QSpacerItem(0, 1);
+ title_layout->addItem(spacer);
+ title_layout->addWidget(name_label = new ElidedLabel(this), 1);
name_label->setStyleSheet("QLabel{font-weight:bold;}");
name_label->setAlignment(Qt::AlignCenter);
- name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
- title_layout->addWidget(name_label);
auto edit_btn = new ToolButton("pencil", tr("Edit Message"));
title_layout->addWidget(edit_btn);
- remove_btn = new ToolButton("x-lg", tr("Remove Message"));
- title_layout->addWidget(remove_btn);
+ title_layout->addWidget(remove_btn = new ToolButton("x-lg", tr("Remove Message")));
+ spacer->changeSize(edit_btn->sizeHint().width() * 2 + 9, 1);
main_layout->addLayout(title_layout);
// warning
@@ -151,7 +148,6 @@ void DetailWidget::refresh() {
}
void DetailWidget::updateState(const QHash *msgs) {
- time_label->setText(QString::number(can->currentSec(), 'f', 3));
if ((msgs && !msgs->contains(msg_id)))
return;
diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h
index ed6a865f53..5bb4b7f305 100644
--- a/tools/cabana/detailwidget.h
+++ b/tools/cabana/detailwidget.h
@@ -42,7 +42,7 @@ private:
void updateState(const QHash * msgs = nullptr);
MessageId msg_id;
- QLabel *time_label, *warning_icon, *warning_label;
+ QLabel *warning_icon, *warning_label;
ElidedLabel *name_label;
QWidget *warning_widget;
TabBar *tabbar;
diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc
index 5cd5e70267..9ac88032d5 100644
--- a/tools/cabana/mainwin.cc
+++ b/tools/cabana/mainwin.cc
@@ -22,12 +22,6 @@
#include "tools/cabana/streamselector.h"
#include "tools/cabana/tools/findsignal.h"
-static MainWindow *main_win = nullptr;
-void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
- if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl;
- if (main_win) emit main_win->showMessage(msg, 2000);
-}
-
MainWindow::MainWindow() : QMainWindow() {
createDockWindows();
setCentralWidget(center_widget = new CenterWidget(this));
@@ -45,20 +39,23 @@ MainWindow::MainWindow() : QMainWindow() {
}
restoreState(settings.window_state);
+ // install handlers
+ static auto static_main_win = this;
qRegisterMetaType("uint64_t");
qRegisterMetaType("SourceSet");
qRegisterMetaType("ReplyMsgType");
- installMessageHandler([this](ReplyMsgType type, const std::string msg) {
- // use queued connection to recv the log messages from replay.
- emit showMessage(QString::fromStdString(msg), 2000);
+ installDownloadProgressHandler([](uint64_t cur, uint64_t total, bool success) {
+ emit static_main_win->updateProgressBar(cur, total, success);
});
- installDownloadProgressHandler([this](uint64_t cur, uint64_t total, bool success) {
- emit updateProgressBar(cur, total, success);
+ qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &msg) {
+ if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl;
+ emit static_main_win->showMessage(msg, 2000);
+ });
+ installMessageHandler([](ReplyMsgType type, const std::string msg) {
+ qInfo() << QString::fromStdString(msg);
});
- main_win = this;
- qInstallMessageHandler(qLogMessageHandler);
-
+ // load fingerprints
QFile json_file(QApplication::applicationDirPath() + "/dbc/car_fingerprint_to_dbc.json");
if (json_file.open(QIODevice::ReadOnly)) {
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
@@ -597,7 +594,9 @@ void MainWindow::closeEvent(QCloseEvent *event) {
cleanupAutoSaveFile();
remindSaveChanges();
- main_win = nullptr;
+ installDownloadProgressHandler(nullptr);
+ qInstallMessageHandler(nullptr);
+
if (floating_window)
floating_window->deleteLater();
diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc
index 1a83a0c446..6a94e013f6 100644
--- a/tools/cabana/messageswidget.cc
+++ b/tools/cabana/messageswidget.cc
@@ -1,9 +1,6 @@
#include "tools/cabana/messageswidget.h"
-#include
#include
-#include
-#include
#include
#include
@@ -13,34 +10,24 @@
#include "tools/cabana/commands.h"
-MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
+static QString msg_node_from_id(const MessageId &id) {
+ auto msg = dbc()->msg(id);
+ return msg ? msg->transmitter : QString();
+}
+
+MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
-
- QHBoxLayout *title_layout = new QHBoxLayout();
- num_msg_label = new QLabel(this);
- title_layout->addSpacing(10);
- title_layout->addWidget(num_msg_label);
-
- title_layout->addStretch();
- title_layout->addWidget(multiple_lines_bytes = new QCheckBox(tr("Multiple Lines &Bytes"), this));
- multiple_lines_bytes->setToolTip(tr("Display bytes in multiple lines"));
- multiple_lines_bytes->setChecked(settings.multiple_lines_bytes);
- QPushButton *clear_filters = new QPushButton(tr("&Clear Filters"));
- clear_filters->setEnabled(false);
- title_layout->addWidget(clear_filters);
- main_layout->addLayout(title_layout);
-
+ main_layout->setSpacing(0);
+ // toolbar
+ main_layout->addWidget(createToolBar());
// message table
view = new MessageView(this);
model = new MessageListModel(this);
header = new MessageViewHeader(this);
- auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes);
-
- view->setItemDelegate(delegate);
+ view->setItemDelegate(delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes));
view->setHeader(header);
view->setModel(model);
- view->setHeader(header);
view->setSortingEnabled(true);
view->sortByColumn(MessageListModel::Column::NAME, Qt::AscendingOrder);
view->setAllColumnsShowFocus(true);
@@ -51,13 +38,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
// Must be called before setting any header parameters to avoid overriding
restoreHeaderState(settings.message_header_state);
- view->header()->setSectionsMovable(true);
- view->header()->setSectionResizeMode(MessageListModel::Column::DATA, QHeaderView::Fixed);
- view->header()->setStretchLastSection(true);
-
- // Header context menu
- view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
- QObject::connect(view->header(), &QHeaderView::customContextMenuRequested, view, &MessageView::headerContextMenuEvent);
+ header->setSectionsMovable(true);
+ header->setSectionResizeMode(MessageListModel::Column::DATA, QHeaderView::Fixed);
+ header->setStretchLastSection(true);
+ header->setContextMenuPolicy(Qt::CustomContextMenu);
main_layout->addWidget(view);
@@ -73,20 +57,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
main_layout->addLayout(suppress_layout);
// signals/slots
+ QObject::connect(menu, &QMenu::aboutToShow, this, &MessagesWidget::menuAboutToShow);
QObject::connect(header, &MessageViewHeader::filtersUpdated, model, &MessageListModel::setFilterStrings);
- QObject::connect(header, &MessageViewHeader::filtersUpdated, [=](const QMap &filters) {
- clear_filters->setEnabled(!filters.isEmpty());
- });
+ QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent);
QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions);
- QObject::connect(clear_filters, &QPushButton::clicked, header, &MessageViewHeader::clearFilters);
- QObject::connect(multiple_lines_bytes, &QCheckBox::stateChanged, [=](int state) {
- settings.multiple_lines_bytes = (state == Qt::Checked);
- delegate->setMultipleLines(settings.multiple_lines_bytes);
- view->setUniformRowHeights(!settings.multiple_lines_bytes);
-
- // Reset model to force recalculation of the width of the bytes column
- model->forceResetModel();
- });
QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, [=](int state) {
settings.suppress_defined_signals = (state == Qt::Checked);
emit settings.changed();
@@ -130,6 +104,21 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
)"));
}
+QToolBar *MessagesWidget::createToolBar() {
+ QToolBar *toolbar = new QToolBar(this);
+ toolbar->setIconSize({12, 12});
+ toolbar->addWidget(num_msg_label = new QLabel(this));
+ num_msg_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ auto views_btn = toolbar->addAction(utils::icon("three-dots"), tr("View..."));
+ views_btn->setMenu(menu);
+ auto view_button = qobject_cast(toolbar->widgetForAction(views_btn));
+ view_button->setPopupMode(QToolButton::InstantPopup);
+ view_button->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ view_button->setStyleSheet("QToolButton::menu-indicator { image: none; }");
+ return toolbar;
+}
+
void MessagesWidget::dbcModified() {
num_msg_label->setText(tr("%1 Messages, %2 Signals").arg(dbc()->msgCount()).arg(dbc()->signalCount()));
model->dbcModified();
@@ -152,6 +141,34 @@ void MessagesWidget::updateSuppressedButtons() {
}
}
+void MessagesWidget::headerContextMenuEvent(const QPoint &pos) {
+ menu->exec(header->mapToGlobal(pos));
+}
+
+void MessagesWidget::menuAboutToShow() {
+ menu->clear();
+ for (int i = 0; i < header->count(); ++i) {
+ int logical_index = header->logicalIndex(i);
+ auto action = menu->addAction(model->headerData(logical_index, Qt::Horizontal).toString(),
+ [=](bool checked) { header->setSectionHidden(logical_index, !checked); });
+ action->setCheckable(true);
+ action->setChecked(!header->isSectionHidden(logical_index));
+ // Can't hide the name column
+ action->setEnabled(logical_index > 0);
+ }
+ menu->addSeparator();
+ auto action = menu->addAction(tr("Mutlti-Line bytes"), this, &MessagesWidget::setMultiLineBytes);
+ action->setCheckable(true);
+ action->setChecked(settings.multiple_lines_bytes);
+}
+
+void MessagesWidget::setMultiLineBytes(bool multi) {
+ settings.multiple_lines_bytes = multi;
+ delegate->setMultipleLines(multi);
+ view->updateBytesSectionSize();
+ view->doItemsLayout();
+}
+
// MessageListModel
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
@@ -160,6 +177,7 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
case Column::NAME: return tr("Name");
case Column::SOURCE: return tr("Bus");
case Column::ADDRESS: return tr("ID");
+ case Column::NODE: return tr("Node");
case Column::FREQ: return tr("Freq");
case Column::COUNT: return tr("Count");
case Column::DATA: return tr("Bytes");
@@ -171,22 +189,22 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
QVariant MessageListModel::data(const QModelIndex &index, int role) const {
if (!index.isValid() || index.row() >= msgs.size()) return {};
- const auto &id = msgs[index.row()];
- auto &can_data = can->lastMessage(id);
-
- auto getFreq = [](const CanData &d) -> QString {
+ auto getFreq = [](const CanData &d) {
if (d.freq > 0 && (can->currentSec() - d.ts - 1.0 / settings.fps) < (5.0 / d.freq)) {
return d.freq >= 0.95 ? QString::number(std::nearbyint(d.freq)) : QString::number(d.freq, 'f', 2);
} else {
- return "--";
+ return QStringLiteral("--");
}
};
+ const auto &id = msgs[index.row()];
+ auto &can_data = can->lastMessage(id);
if (role == Qt::DisplayRole) {
switch (index.column()) {
case Column::NAME: return msgName(id);
case Column::SOURCE: return id.source != INVALID_SOURCE ? QString::number(id.source) : "N/A";
case Column::ADDRESS: return QString::number(id.address, 16);
+ case Column::NODE: return msg_node_from_id(id);
case Column::FREQ: return id.source != INVALID_SOURCE ? getFreq(can_data) : "N/A";
case Column::COUNT: return id.source != INVALID_SOURCE ? QString::number(can_data.count) : "N/A";
case Column::DATA: return id.source != INVALID_SOURCE ? toHex(can_data.dat) : "N/A";
@@ -214,7 +232,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
void MessageListModel::setFilterStrings(const QMap &filters) {
filter_str = filters;
- fetchData();
+ filterAndSort();
}
void MessageListModel::dbcModified() {
@@ -222,40 +240,22 @@ void MessageListModel::dbcModified() {
for (const auto &[_, m] : dbc()->getMessages(-1)) {
dbc_address.insert(m.address);
}
- fetchData();
+ filterAndSort();
}
void MessageListModel::sortMessages(std::vector &new_msgs) {
- if (sort_column == Column::NAME) {
- std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
- auto ll = std::pair{msgName(l), l};
- auto rr = std::pair{msgName(r), r};
- return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
- });
- } else if (sort_column == Column::SOURCE) {
- std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
- auto ll = std::pair{l.source, l};
- auto rr = std::pair{r.source, r};
- return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
- });
- } else if (sort_column == Column::ADDRESS) {
- std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
- auto ll = std::pair{l.address, l};
- auto rr = std::pair{r.address, r};
- return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
- });
- } else if (sort_column == Column::FREQ) {
- std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
- auto ll = std::pair{can->lastMessage(l).freq, l};
- auto rr = std::pair{can->lastMessage(r).freq, r};
- return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
- });
- } else if (sort_column == Column::COUNT) {
- std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) {
- auto ll = std::pair{can->lastMessage(l).count, l};
- auto rr = std::pair{can->lastMessage(r).count, r};
- return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
+ auto do_sort = [order = sort_order](std::vector &m, auto proj) {
+ std::sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) {
+ return order == Qt::AscendingOrder ? proj(l) < proj(r) : proj(l) > proj(r);
});
+ };
+ switch (sort_column) {
+ case Column::NAME: do_sort(new_msgs, [](auto &id) { return std::make_pair(msgName(id), id); }); break;
+ case Column::SOURCE: do_sort(new_msgs, [](auto &id) { return std::tie(id.source, id); }); break;
+ case Column::ADDRESS: do_sort(new_msgs, [](auto &id) { return std::tie(id.address, id);}); break;
+ case Column::NODE: do_sort(new_msgs, [](auto &id) { return std::make_pair(msg_node_from_id(id), id);}); break;
+ case Column::FREQ: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).freq, id); }); break;
+ case Column::COUNT: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).count, id); }); break;
}
}
@@ -295,6 +295,9 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co
match = match || parseRange(txt, id.address, 16);
break;
}
+ case Column::NODE:
+ match = re.match(msg_node_from_id(id)).hasMatch();
+ break;
case Column::FREQ:
// TODO: Hide stale messages?
match = parseRange(txt, data.freq);
@@ -313,7 +316,7 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co
return match;
}
-void MessageListModel::fetchData() {
+void MessageListModel::filterAndSort() {
std::vector new_msgs;
new_msgs.reserve(can->last_msgs.size() + dbc_address.size());
@@ -344,7 +347,7 @@ void MessageListModel::fetchData() {
void MessageListModel::msgsReceived(const QHash *new_msgs, bool has_new_ids) {
if (has_new_ids || filter_str.contains(Column::FREQ) || filter_str.contains(Column::COUNT) || filter_str.contains(Column::DATA)) {
- fetchData();
+ filterAndSort();
}
for (int i = 0; i < msgs.size(); ++i) {
if (new_msgs->contains(msgs[i])) {
@@ -358,7 +361,7 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
if (column != columnCount() - 1) {
sort_column = column;
sort_order = order;
- fetchData();
+ filterAndSort();
}
}
@@ -380,11 +383,6 @@ void MessageListModel::clearSuppress() {
suppressed_bytes.clear();
}
-void MessageListModel::forceResetModel() {
- beginResetModel();
- endResetModel();
-}
-
// MessageView
void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
@@ -425,35 +423,7 @@ void MessageView::updateBytesSectionSize() {
}
}
-void MessageView::headerContextMenuEvent(const QPoint &pos) {
- QMenu menu(this);
- int cur_index = header()->logicalIndexAt(pos);
-
- QAction *action;
- for (int visual_index = 0; visual_index < header()->count(); visual_index++) {
- int logical_index = header()->logicalIndex(visual_index);
- QString column_name = model()->headerData(logical_index, Qt::Horizontal).toString();
-
- // Hide show action
- if (header()->isSectionHidden(logical_index)) {
- action = menu.addAction(tr(" %1").arg(column_name), [=]() { header()->showSection(logical_index); });
- } else {
- action = menu.addAction(tr("✓ %1").arg(column_name), [=]() { header()->hideSection(logical_index); });
- }
-
- // Can't hide the name column
- action->setEnabled(logical_index > 0);
-
- // Make current column bold
- if (logical_index == cur_index) {
- QFont font = action->font();
- font.setBold(true);
- action->setFont(font);
- }
- }
-
- menu.exec(header()->mapToGlobal(pos));
-}
+// MessageViewHeader
MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) {
QObject::connect(this, &QHeaderView::sectionResized, this, &MessageViewHeader::updateHeaderPositions);
@@ -463,22 +433,13 @@ MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizont
void MessageViewHeader::updateFilters() {
QMap filters;
for (int i = 0; i < count(); i++) {
- if (editors[i]) {
- QString filter = editors[i]->text();
- if (!filter.isEmpty()) {
- filters[i] = filter;
- }
+ if (editors[i] && !editors[i]->text().isEmpty()) {
+ filters[i] = editors[i]->text();
}
}
emit filtersUpdated(filters);
}
-void MessageViewHeader::clearFilters() {
- for (QLineEdit *editor : editors) {
- editor->clear();
- }
-}
-
void MessageViewHeader::updateHeaderPositions() {
QSize sz = QHeaderView::sizeHint();
for (int i = 0; i < count(); i++) {
@@ -508,11 +469,7 @@ void MessageViewHeader::updateGeometries() {
updateHeaderPositions();
}
-
QSize MessageViewHeader::sizeHint() const {
QSize sz = QHeaderView::sizeHint();
- if (editors[0]) {
- sz.setHeight(sz.height() + editors[0]->minimumSizeHint().height() + 1);
- }
- return sz;
+ return editors[0] ? QSize(sz.width(), sz.height() + editors[0]->height() + 1) : sz;
}
diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h
index f6d71a5a2e..11d95dd3f2 100644
--- a/tools/cabana/messageswidget.h
+++ b/tools/cabana/messageswidget.h
@@ -5,13 +5,12 @@
#include
#include
-#include
-#include
#include
#include
#include
#include
#include
+#include
#include
#include "tools/cabana/dbc/dbcmanager.h"
@@ -21,11 +20,11 @@ class MessageListModel : public QAbstractTableModel {
Q_OBJECT
public:
-
enum Column {
NAME = 0,
SOURCE,
ADDRESS,
+ NODE,
FREQ,
COUNT,
DATA,
@@ -39,10 +38,9 @@ public:
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
void setFilterStrings(const QMap &filters);
void msgsReceived(const QHash *new_msgs, bool has_new_ids);
- void fetchData();
+ void filterAndSort();
void suppress();
void clearSuppress();
- void forceResetModel();
void dbcModified();
std::vector msgs;
QSet> suppressed_bytes;
@@ -65,23 +63,17 @@ public:
void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override {}
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override;
void updateBytesSectionSize();
- void headerContextMenuEvent(const QPoint &pos);
};
class MessageViewHeader : public QHeaderView {
// https://stackoverflow.com/a/44346317
-
Q_OBJECT
public:
MessageViewHeader(QWidget *parent);
void updateHeaderPositions();
-
void updateGeometries() override;
QSize sizeHint() const override;
-public slots:
- void clearFilters();
-
signals:
void filtersUpdated(const QMap &filters);
@@ -108,12 +100,18 @@ signals:
void msgSelectionChanged(const MessageId &message_id);
protected:
+ QToolBar *createToolBar();
+ void headerContextMenuEvent(const QPoint &pos);
+ void menuAboutToShow();
+ void setMultiLineBytes(bool multi);
+
MessageView *view;
MessageViewHeader *header;
+ MessageBytesDelegate *delegate;
std::optional current_msg_id;
- QCheckBox *multiple_lines_bytes;
MessageListModel *model;
QPushButton *suppress_add;
QPushButton *suppress_clear;
QLabel *num_msg_label;
+ QMenu *menu;
};
diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc
index ee345c490c..c408179fdd 100644
--- a/tools/cabana/settings.cc
+++ b/tools/cabana/settings.cc
@@ -14,6 +14,7 @@ Settings settings;
QSettings::Status Settings::save() {
QSettings s(filePath(), QSettings::IniFormat);
+ s.setValue("absolute_time", absolute_time);
s.setValue("fps", fps);
s.setValue("max_cached_minutes", max_cached_minutes);
s.setValue("chart_height", chart_height);
@@ -40,6 +41,7 @@ QSettings::Status Settings::save() {
void Settings::load() {
QSettings s(filePath(), QSettings::IniFormat);
+ absolute_time = s.value("absolute_time", false).toBool();
fps = s.value("fps", 10).toInt();
max_cached_minutes = s.value("max_cached_minutes", 30).toInt();
chart_height = s.value("chart_height", 200).toInt();
diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h
index 42073a72de..da7781e6e4 100644
--- a/tools/cabana/settings.h
+++ b/tools/cabana/settings.h
@@ -29,6 +29,7 @@ public:
void load();
inline static QString filePath() { return QApplication::applicationDirPath() + "/settings"; }
+ bool absolute_time = false;
int fps = 10;
int max_cached_minutes = 30;
int chart_height = 200;
diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc
index 4a07e25beb..934609a439 100644
--- a/tools/cabana/signalview.cc
+++ b/tools/cabana/signalview.cc
@@ -447,7 +447,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
QRegularExpression re("\\S+");
filter_edit->setValidator(new QRegularExpressionValidator(re, this));
filter_edit->setClearButtonEnabled(true);
- filter_edit->setPlaceholderText(tr("filter signals"));
+ filter_edit->setPlaceholderText(tr("Filter Signal"));
hl->addWidget(filter_edit);
hl->addStretch(1);
@@ -502,6 +502,12 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); });
QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); });
QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalView::updateState);
+ QObject::connect(tree->header(), &QHeaderView::sectionResized, [this](int logicalIndex, int oldSize, int newSize) {
+ if (logicalIndex == 1) {
+ value_column_width = newSize - delegate->button_size.width();
+ updateState();
+ }
+ });
setWhatsThis(tr(R"(
Signal view
@@ -638,11 +644,10 @@ void SignalView::updateState(const QHash *msgs) {
last_visible_row = bottom.parent().isValid() ? bottom.parent().row() : bottom.row();
}
- QSize size(tree->columnWidth(1) - delegate->button_size.width(), delegate->button_size.height());
- int min_max_width = std::min(size.width() - 10, QFontMetrics(delegate->minmax_font).width("-000.00") + 5);
- int value_width = std::min(max_value_width, size.width() * 0.35);
- size -= {value_width + min_max_width, style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2};
-
+ const static int min_max_width = QFontMetrics(delegate->minmax_font).width("-000.00") + 5;
+ int value_width = std::min(max_value_width + min_max_width, value_column_width / 2);
+ QSize size(value_column_width - value_width,
+ delegate->button_size.height() - style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2);
QFutureSynchronizer synchronizer;
for (int i = first_visible_row; i <= last_visible_row; ++i) {
auto item = model->getItem(model->index(i, 1));
diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h
index bcf0019bc4..c08579bc55 100644
--- a/tools/cabana/signalview.h
+++ b/tools/cabana/signalview.h
@@ -136,6 +136,7 @@ private:
}
};
int max_value_width = 0;
+ int value_column_width = 0;
TreeView *tree;
QLabel *sparkline_label;
QSlider *sparkline_range_slider;
diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc
index 6fa479815d..eb2859e5a3 100644
--- a/tools/cabana/streams/abstractstream.cc
+++ b/tools/cabana/streams/abstractstream.cc
@@ -1,7 +1,6 @@
#include "tools/cabana/streams/abstractstream.h"
#include
-#include
#include
@@ -139,9 +138,10 @@ void AbstractStream::updateLastMsgsTo(double sec) {
}
void AbstractStream::mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last) {
- static std::unordered_map> new_events_map;
+ static MessageEventsMap msg_events;
static std::vector new_events;
- new_events_map.clear();
+
+ std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); });
new_events.clear();
for (auto it = first; it != last; ++it) {
@@ -156,25 +156,25 @@ void AbstractStream::mergeEvents(std::vector::const_iterator first, std
e->size = dat.size();
memcpy(e->dat, (uint8_t *)dat.begin(), e->size);
- new_events_map[{.source = e->src, .address = e->address}].push_back(e);
+ msg_events[{.source = e->src, .address = e->address}].push_back(e);
new_events.push_back(e);
}
}
}
- for (auto &[id, new_e] : new_events_map) {
- auto &e = events_[id];
- auto insert_pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent());
- e.insert(insert_pos, new_e.cbegin(), new_e.cend());
- }
-
if (!new_events.empty()) {
- auto insert_pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent());
- all_events_.insert(insert_pos, new_events.cbegin(), new_events.cend());
+ for (auto &[id, new_e] : msg_events) {
+ if (!new_e.empty()) {
+ auto &e = events_[id];
+ auto pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent());
+ e.insert(pos, new_e.cbegin(), new_e.cend());
+ }
+ }
+ auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent());
+ all_events_.insert(pos, new_events.cbegin(), new_events.cend());
+ emit eventsMerged(msg_events);
}
-
lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time;
- emit eventsMerged();
}
// CanData
diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h
index 3a89d4e57d..4a17affebe 100644
--- a/tools/cabana/streams/abstractstream.h
+++ b/tools/cabana/streams/abstractstream.h
@@ -2,13 +2,13 @@
#include
#include
-#include
#include
#include
#include
#include
#include
+#include
#include
#include "common/timing.h"
@@ -52,6 +52,8 @@ struct BusConfig {
bool can_fd = false;
};
+typedef std::unordered_map> MessageEventsMap;
+
class AbstractStream : public QObject {
Q_OBJECT
@@ -63,6 +65,7 @@ public:
virtual void seekTo(double ts) {}
virtual QString routeName() const = 0;
virtual QString carFingerprint() const { return ""; }
+ virtual QDateTime beginDateTime() const { return {}; }
virtual double routeStartTime() const { return 0; }
virtual double currentSec() const = 0;
virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); }
@@ -73,6 +76,7 @@ public:
virtual double getSpeed() { return 1; }
virtual bool isPaused() const { return false; }
virtual void pause(bool pause) {}
+ const MessageEventsMap &eventsMap() const { return events_; }
const std::vector &allEvents() const { return all_events_; }
const std::vector &events(const MessageId &id) const;
virtual const std::vector> getTimeline() { return {}; }
@@ -82,7 +86,7 @@ signals:
void resume();
void seekedTo(double sec);
void streamStarted();
- void eventsMerged();
+ void eventsMerged(const MessageEventsMap &events_map);
void updated();
void msgsReceived(const QHash *new_msgs, bool has_new_ids);
void sourcesUpdated(const SourceSet &s);
@@ -104,7 +108,7 @@ protected:
std::atomic processing = false;
std::unique_ptr> new_msgs;
QHash all_msgs;
- std::unordered_map> events_;
+ MessageEventsMap events_;
std::vector all_events_;
std::unique_ptr event_buffer;
std::mutex mutex;
diff --git a/tools/cabana/streams/livestream.cc b/tools/cabana/streams/livestream.cc
index 17805a0b66..fd140eb8bf 100644
--- a/tools/cabana/streams/livestream.cc
+++ b/tools/cabana/streams/livestream.cc
@@ -46,6 +46,7 @@ void LiveStream::start() {
emit streamStarted();
stream_thread->start();
startUpdateTimer();
+ begin_date_time = QDateTime::currentDateTime();
}
LiveStream::~LiveStream() {
diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h
index 38ef2c67f9..d72112c604 100644
--- a/tools/cabana/streams/livestream.h
+++ b/tools/cabana/streams/livestream.h
@@ -15,6 +15,7 @@ public:
LiveStream(QObject *parent);
virtual ~LiveStream();
void start() override;
+ inline QDateTime beginDateTime() const { return begin_date_time; }
inline double routeStartTime() const override { return begin_event_ts / 1e9; }
inline double currentSec() const override { return (current_event_ts - begin_event_ts) / 1e9; }
void setSpeed(float speed) override { speed_ = speed; }
@@ -49,6 +50,7 @@ private:
int timer_id;
QBasicTimer update_timer;
+ QDateTime begin_date_time;
uint64_t begin_event_ts = 0;
uint64_t current_event_ts = 0;
uint64_t first_event_ts = 0;
diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc
index 2e580c0f0d..8a6b806a8a 100644
--- a/tools/cabana/streams/replaystream.cc
+++ b/tools/cabana/streams/replaystream.cc
@@ -8,10 +8,13 @@
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
unsetenv("ZMQ");
+ setenv("COMMA_CACHE", "/tmp/comma_download_cache", 1);
+
// TODO: Remove when OpenpilotPrefix supports ZMQ
#ifndef __APPLE__
op_prefix = std::make_unique();
#endif
+
QObject::connect(&settings, &Settings::changed, [this]() {
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);
});
@@ -37,6 +40,7 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint
replay->installEventFilter(event_filter, this);
QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo);
QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments);
+ QObject::connect(replay.get(), &Replay::qLogLoaded, this, &ReplayStream::qLogLoaded, Qt::QueuedConnection);
return replay->load();
}
diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h
index de69d9e86c..d69922a432 100644
--- a/tools/cabana/streams/replaystream.h
+++ b/tools/cabana/streams/replaystream.h
@@ -22,16 +22,21 @@ public:
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
double totalSeconds() const override { return replay->totalSeconds(); }
inline VisionStreamType visionStreamType() const override { return replay->hasFlag(REPLAY_FLAG_ECAM) ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD; }
+ inline QDateTime beginDateTime() const { return replay->route()->datetime(); }
inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; }
inline double currentSec() const override { return replay->currentSeconds(); }
inline const Route *route() const override { return replay->route(); }
inline void setSpeed(float speed) override { replay->setSpeed(speed); }
inline float getSpeed() const { return replay->getSpeed(); }
+ inline Replay *getReplay() const { return replay.get(); }
inline bool isPaused() const override { return replay->isPaused(); }
void pause(bool pause) override;
inline const std::vector> getTimeline() override { return replay->getTimeline(); }
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
+signals:
+ void qLogLoaded(int segnum, std::shared_ptr qlog);
+
private:
void mergeSegments();
std::unique_ptr replay = nullptr;
diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc
index 4c21530774..38190da301 100644
--- a/tools/cabana/util.cc
+++ b/tools/cabana/util.cc
@@ -19,7 +19,7 @@
// SegmentTree
-void SegmentTree::build(const QVector &arr) {
+void SegmentTree::build(const std::vector &arr) {
size = arr.size();
tree.resize(4 * size); // size of the tree is 4 times the size of the array
if (size > 0) {
@@ -27,7 +27,7 @@ void SegmentTree::build(const QVector &arr) {
}
}
-void SegmentTree::build_tree(const QVector &arr, int n, int left, int right) {
+void SegmentTree::build_tree(const std::vector &arr, int n, int left, int right) {
if (left == right) {
const double y = arr[left].y();
tree[n] = {y, y};
@@ -242,6 +242,13 @@ void setTheme(int theme) {
}
}
+QString formatSeconds(double sec, bool include_milliseconds, bool absolute_time) {
+ QString format = absolute_time ? "yyyy-MM-dd hh:mm:ss"
+ : (sec > 60 * 60 ? "hh:mm:ss" : "mm:ss");
+ if (include_milliseconds) format += ".zzz";
+ return QDateTime::fromMSecsSinceEpoch(sec * 1000).toString(format);
+}
+
} // namespace utils
QString toHex(uint8_t byte) {
diff --git a/tools/cabana/util.h b/tools/cabana/util.h
index 2c1bc5cf7b..da476ab31a 100644
--- a/tools/cabana/util.h
+++ b/tools/cabana/util.h
@@ -57,12 +57,12 @@ enum {
class SegmentTree {
public:
SegmentTree() = default;
- void build(const QVector &arr);
+ void build(const std::vector &arr);
inline std::pair minmax(int left, int right) const { return get_minmax(1, 0, size - 1, left, right); }
private:
std::pair get_minmax(int n, int left, int right, int range_left, int range_right) const;
- void build_tree(const QVector &arr, int n, int left, int right);
+ void build_tree(const std::vector &arr, int n, int left, int right);
std::vector> tree;
int size = 0;
};
@@ -103,9 +103,7 @@ public:
namespace utils {
QPixmap icon(const QString &id);
void setTheme(int theme);
-inline QString formatSeconds(int seconds) {
- return QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
-}
+QString formatSeconds(double sec, bool include_milliseconds = false, bool absolute_time = false);
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);
diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc
index 7e051ef3b9..0a1d2855a5 100644
--- a/tools/cabana/videowidget.cc
+++ b/tools/cabana/videowidget.cc
@@ -1,17 +1,15 @@
#include "tools/cabana/videowidget.h"
#include
-#include
-#include
#include
-#include
-#include
+#include
+#include
+#include
#include
#include
#include
#include
-#include
#include
#include
@@ -30,50 +28,18 @@ static const QColor timeline_colors[] = {
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
auto main_layout = new QVBoxLayout(this);
- if (!can->liveStreaming()) {
+ if (!can->liveStreaming())
main_layout->addWidget(createCameraWidget());
- }
+ main_layout->addLayout(createPlaybackController());
- // btn controls
- QButtonGroup *group = new QButtonGroup(this);
- group->setExclusive(true);
-
- QHBoxLayout *control_layout = new QHBoxLayout();
- play_btn = new QToolButton();
- play_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
- control_layout->addWidget(play_btn);
- if (can->liveStreaming()) {
- control_layout->addWidget(skip_to_end_btn = new QToolButton(this));
- skip_to_end_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
- skip_to_end_btn->setIcon(utils::icon("skip-end-fill"));
- skip_to_end_btn->setToolTip(tr("Skip to the end"));
- QObject::connect(skip_to_end_btn, &QToolButton::clicked, [group]() {
- // set speed to 1.0
- group->buttons()[2]->click();
- can->pause(false);
- can->seekTo(can->totalSeconds() + 1);
- });
- }
-
- for (float speed : {0.1, 0.5, 1., 2.}) {
- QToolButton *btn = new QToolButton(this);
- btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
- btn->setText(QString("%1x").arg(speed));
- btn->setCheckable(true);
- QObject::connect(btn, &QToolButton::clicked, [speed]() { can->setSpeed(speed); });
- control_layout->addWidget(btn);
- group->addButton(btn);
- if (speed == 1.0) btn->setChecked(true);
- }
- main_layout->addLayout(control_layout);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
- QObject::connect(play_btn, &QToolButton::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState);
+ QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
QObject::connect(&settings, &Settings::changed, this, &VideoWidget::updatePlayBtnState);
- updatePlayBtnState();
+ updatePlayBtnState();
setWhatsThis(tr(R"(
Video
@@ -96,6 +62,71 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
timeline_colors[(int)TimelineType::AlertCritical].name()));
}
+QHBoxLayout *VideoWidget::createPlaybackController() {
+ QHBoxLayout *layout = new QHBoxLayout();
+ layout->addWidget(seek_backward_btn = new ToolButton("rewind", tr("Seek backward")));
+ layout->addWidget(play_btn = new ToolButton("play", tr("Play")));
+ layout->addWidget(seek_forward_btn = new ToolButton("fast-forward", tr("Seek forward")));
+
+ if (can->liveStreaming()) {
+ layout->addWidget(skip_to_end_btn = new ToolButton("skip-end", tr("Skip to the end"), this));
+ QObject::connect(skip_to_end_btn, &QToolButton::clicked, [this]() {
+ // set speed to 1.0
+ speed_btn->menu()->actions()[7]->setChecked(true);
+ can->pause(false);
+ can->seekTo(can->totalSeconds() + 1);
+ });
+ }
+
+ layout->addWidget(time_btn = new QToolButton);
+ time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time"));
+ time_btn->setAutoRaise(true);
+ layout->addStretch(0);
+
+ if (!can->liveStreaming()) {
+ layout->addWidget(loop_btn = new ToolButton("repeat", tr("Loop playback")));
+ QObject::connect(loop_btn, &QToolButton::clicked, this, &VideoWidget::loopPlaybackClicked);
+ }
+
+ // speed selector
+ layout->addWidget(speed_btn = new QToolButton(this));
+ speed_btn->setAutoRaise(true);
+ speed_btn->setMenu(new QMenu(speed_btn));
+ speed_btn->setPopupMode(QToolButton::InstantPopup);
+ QActionGroup *speed_group = new QActionGroup(this);
+ speed_group->setExclusive(true);
+
+ int max_width = 0;
+ QFont font = speed_btn->font();
+ font.setBold(true);
+ speed_btn->setFont(font);
+ QFontMetrics fm(font);
+ for (float speed : {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 0.8, 1., 2., 3., 5.}) {
+ QString name = QString("%1x").arg(speed);
+ max_width = std::max(max_width, fm.width(name) + fm.horizontalAdvance(QLatin1Char(' ')) * 2);
+
+ QAction *act = new QAction(name, speed_group);
+ act->setCheckable(true);
+ QObject::connect(act, &QAction::toggled, [this, speed]() {
+ can->setSpeed(speed);
+ speed_btn->setText(QString("%1x ").arg(speed));
+ });
+ speed_btn->menu()->addAction(act);
+ if (speed == 1.0)act->setChecked(true);
+ }
+ speed_btn->setMinimumWidth(max_width + style()->pixelMetric(QStyle::PM_MenuButtonIndicator));
+
+ QObject::connect(play_btn, &QToolButton::clicked, []() { can->pause(!can->isPaused()); });
+ QObject::connect(seek_backward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() - 1); });
+ QObject::connect(seek_forward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() + 1); });
+ QObject::connect(time_btn, &QToolButton::clicked, [this]() {
+ settings.absolute_time = !settings.absolute_time;
+ time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time"));
+ updateState();
+ });
+ return layout;
+}
+
QWidget *VideoWidget::createCameraWidget() {
QWidget *w = new QWidget(this);
QVBoxLayout *l = new QVBoxLayout(w);
@@ -109,29 +140,30 @@ QWidget *VideoWidget::createCameraWidget() {
stacked->addWidget(alert_label = new InfoLabel(this));
l->addLayout(stacked);
- // slider controls
- auto slider_layout = new QHBoxLayout();
- slider_layout->addWidget(time_label = new QLabel("00:00"));
-
- slider = new Slider(this);
+ l->addWidget(slider = new Slider(this));
slider->setSingleStep(0);
- slider_layout->addWidget(slider);
-
- slider_layout->addWidget(end_time_label = new QLabel(this));
- l->addLayout(slider_layout);
setMaximumTime(can->totalSeconds());
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); });
- QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(slider->currentSecond())); });
QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection);
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
- QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
+ QObject::connect(static_cast(can), &ReplayStream::qLogLoaded, slider, &Slider::parseQLog);
return w;
}
+void VideoWidget::loopPlaybackClicked() {
+ auto replay = qobject_cast(can)->getReplay();
+ if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) {
+ replay->removeFlag(REPLAY_FLAG_NO_LOOP);
+ loop_btn->setIcon("repeat");
+ } else {
+ replay->addFlag(REPLAY_FLAG_NO_LOOP);
+ loop_btn->setIcon("repeat-1");
+ }
+}
+
void VideoWidget::setMaximumTime(double sec) {
maximum_time = sec;
- end_time_label->setText(utils::formatSeconds(sec));
slider->setTimeRange(0, sec);
}
@@ -145,19 +177,29 @@ void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) {
min = 0;
max = maximum_time;
}
- end_time_label->setText(utils::formatSeconds(max));
slider->setTimeRange(min, max);
}
+QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
+ if (settings.absolute_time)
+ sec = can->beginDateTime().addMSecs(sec * 1000).toMSecsSinceEpoch() / 1000.0;
+ return utils::formatSeconds(sec, include_milliseconds, settings.absolute_time);
+}
+
void VideoWidget::updateState() {
- if (!slider->isSliderDown()) {
- slider->setCurrentSecond(can->currentSec());
+ if (slider) {
+ if (!slider->isSliderDown())
+ slider->setCurrentSecond(can->currentSec());
+ alert_label->showAlert(slider->alertInfo(can->currentSec()));
+ time_btn->setText(QString("%1 / %2").arg(formatTime(can->currentSec(), true),
+ formatTime(slider->maximum() / slider->factor)));
+ } else {
+ time_btn->setText(formatTime(can->currentSec(), true));
}
- alert_label->showAlert(slider->alertInfo(can->currentSec()));
}
void VideoWidget::updatePlayBtnState() {
- play_btn->setIcon(utils::icon(can->isPaused() ? "play" : "pause"));
+ play_btn->setIcon(can->isPaused() ? "play" : "pause");
play_btn->setToolTip(can->isPaused() ? tr("Play") : tr("Pause"));
}
@@ -165,29 +207,9 @@ void VideoWidget::updatePlayBtnState() {
Slider::Slider(QWidget *parent) : thumbnail_label(parent), QSlider(Qt::Horizontal, parent) {
setMouseTracking(true);
- auto timer = new QTimer(this);
- timer->callOnTimeout([this]() {
- timeline = can->getTimeline();
- std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); });
- update();
- });
- timer->start(2000);
- QObject::connect(can, &AbstractStream::eventsMerged, [this]() {
- if (!qlog_future) {
- qlog_future = std::make_unique>(QtConcurrent::run(this, &Slider::parseQLog));
- }
- });
- QObject::connect(qApp, &QApplication::aboutToQuit, [this]() {
- abort_parse_qlog = true;
- if (qlog_future && qlog_future->isRunning()) {
- qDebug() << "stopping thumbnail thread";
- qlog_future->waitForFinished();
- }
- });
}
AlertInfo Slider::alertInfo(double seconds) {
- std::lock_guard lk(thumbnail_lock);
uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9;
auto alert_it = alerts.lower_bound(mono_time);
bool has_alert = (alert_it != alerts.end()) && ((alert_it->first - mono_time) <= 1e8);
@@ -195,7 +217,6 @@ AlertInfo Slider::alertInfo(double seconds) {
}
QPixmap Slider::thumbnail(double seconds) {
- std::lock_guard lk(thumbnail_lock);
uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9;
auto it = thumbnails.lowerBound(mono_time);
return it != thumbnails.end() ? it.value() : QPixmap();
@@ -206,36 +227,32 @@ void Slider::setTimeRange(double min, double max) {
setRange(min * factor, max * factor);
}
-void Slider::parseQLog() {
- const auto &segments = can->route()->segments();
- for (auto it = segments.rbegin(); it != segments.rend() && !abort_parse_qlog; ++it) {
- LogReader log;
- std::string qlog = it->second.qlog.toStdString();
- if (!qlog.empty() && log.load(qlog, &abort_parse_qlog, {cereal::Event::Which::THUMBNAIL, cereal::Event::Which::CONTROLS_STATE}, true, 0, 3)) {
- if (it == segments.rbegin() && !log.events.empty()) {
- double max_time = log.events.back()->mono_time / 1e9 - can->routeStartTime();
- emit updateMaximumTime(max_time);
+void Slider::parseQLog(int segnum, std::shared_ptr qlog) {
+ const auto &segments = qobject_cast(can)->route()->segments();
+ if (segments.size() > 0 && segnum == segments.rbegin()->first && !qlog->events.empty()) {
+ emit updateMaximumTime(qlog->events.back()->mono_time / 1e9 - can->routeStartTime());
+ }
+
+ std::mutex mutex;
+ QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [&mutex, this](const Event *e) {
+ if (e->which == cereal::Event::Which::THUMBNAIL) {
+ auto thumb = e->event.getThumbnail();
+ auto data = thumb.getThumbnail();
+ if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) {
+ QPixmap scaled = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation);
+ std::lock_guard lk(mutex);
+ thumbnails[thumb.getTimestampEof()] = scaled;
}
- for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_parse_qlog; ++ev) {
- if ((*ev)->which == cereal::Event::Which::THUMBNAIL) {
- auto thumb = (*ev)->event.getThumbnail();
- auto data = thumb.getThumbnail();
- if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) {
- pm = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation);
- std::lock_guard lk(thumbnail_lock);
- thumbnails[thumb.getTimestampEof()] = pm;
- }
- } else if ((*ev)->which == cereal::Event::Which::CONTROLS_STATE) {
- auto cs = (*ev)->event.getControlsState();
- if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 &&
- cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) {
- std::lock_guard lk(thumbnail_lock);
- alerts.emplace((*ev)->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()});
- }
- }
+ } else if (e->which == cereal::Event::Which::CONTROLS_STATE) {
+ auto cs = e->event.getControlsState();
+ if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 &&
+ cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) {
+ std::lock_guard lk(mutex);
+ alerts.emplace(e->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()});
}
}
- }
+ });
+ update();
}
void Slider::paintEvent(QPaintEvent *ev) {
@@ -245,7 +262,7 @@ void Slider::paintEvent(QPaintEvent *ev) {
double min = minimum() / factor;
double max = maximum() / factor;
- for (auto [begin, end, type] : timeline) {
+ for (auto [begin, end, type] : qobject_cast(can)->getTimeline()) {
if (begin > max || end < min)
continue;
r.setLeft(((std::max(min, begin) - min) / (max - min)) * width());
@@ -311,8 +328,7 @@ void InfoLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap &
second = sec;
pixmap = pm;
alert_info = alert;
- resize(pm.size());
- move(pt);
+ setGeometry(QRect(pt, pm.size()));
setVisible(true);
update();
}
diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h
index aca44d0ac1..68e0461117 100644
--- a/tools/cabana/videowidget.h
+++ b/tools/cabana/videowidget.h
@@ -1,19 +1,14 @@
#pragma once
-#include
#include