mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-26 22:03:46 +08:00
Merge branch 'master' into sync-priv-20231025
# Conflicts: # selfdrive/car/hyundai/interface.py
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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/)'
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -383,7 +383,6 @@ SConscript(['rednose/SConscript'])
|
||||
|
||||
# Build system services
|
||||
SConscript([
|
||||
'system/clocksd/SConscript',
|
||||
'system/proclogd/SConscript',
|
||||
'system/ubloxd/SConscript',
|
||||
'system/loggerd/SConscript',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+4
-2
@@ -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|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -20,6 +20,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Buick|LaCrosse 2017-19[<sup>4</sup>](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-II connector<br>- 1 comma 3X<br>- 2 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Buick&model=LaCrosse 2017-19">Buy Here</a></sub></details>||
|
||||
|Cadillac|Escalade 2017[<sup>4</sup>](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-II connector<br>- 1 comma 3X<br>- 2 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Cadillac&model=Escalade 2017">Buy Here</a></sub></details>||
|
||||
|Cadillac|Escalade ESV 2016[<sup>4</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-II connector<br>- 1 comma 3X<br>- 2 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Cadillac&model=Escalade ESV 2016">Buy Here</a></sub></details>||
|
||||
|Cadillac|Escalade ESV 2019[<sup>4</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-II connector<br>- 1 comma 3X<br>- 2 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Cadillac&model=Escalade ESV 2019">Buy Here</a></sub></details>||
|
||||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Chevrolet&model=Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Chevrolet&model=Bolt EV 2022-23">Buy Here</a></sub></details>||
|
||||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Chevrolet&model=Silverado 1500 2020-21">Buy Here</a></sub></details>||
|
||||
@@ -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[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera 2022">Buy Here</a></sub></details>||
|
||||
|Hyundai|Azera Hybrid 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera Hybrid 2020">Buy Here</a></sub></details>||
|
||||
|Hyundai|Custin 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Custin 2023">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-19">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-18">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2019">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra GT 2017-19">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|
||||
Generated
+118
-118
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2e57357c95937475fe92c13709ff2ee067f036643d5d455383bd9c3335525614
|
||||
size 126281
|
||||
oid sha256:8674d9a8304d501aee2bfaea2d668d746fd424b5b0872b83ab265d15d3a3d887
|
||||
size 125301
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+134
-314
@@ -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}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]]:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
clocksd
|
||||
@@ -1,2 +0,0 @@
|
||||
Import('env', 'common', 'cereal', 'messaging')
|
||||
env.Program('clocksd.cc', LIBS=[common, cereal, messaging, 'capnp', 'zmq', 'kj'])
|
||||
@@ -1,73 +0,0 @@
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
// Apple doesn't have timerfd
|
||||
#ifdef __APPLE__
|
||||
#include <thread>
|
||||
#else
|
||||
#include <sys/timerfd.h>
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
+46
-46
@@ -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<const CanEvent *> &events,
|
||||
std::vector<QPointF> &vals, std::vector<QPointF> &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<QPointF> 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<QPointF>::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<double>::max();
|
||||
double max = std::numeric_limits<double>::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<QPointF>::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals));
|
||||
}
|
||||
updateSeriesPoints();
|
||||
updateTitle();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QGraphicsPixmapItem>
|
||||
@@ -31,7 +32,7 @@ public:
|
||||
ChartView(const std::pair<double, double> &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<QPointF> vals;
|
||||
QVector<QPointF> step_vals;
|
||||
uint64_t last_value_mono_time = 0;
|
||||
std::vector<QPointF> vals;
|
||||
std::vector<QPointF> 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<const CanEvent *> &events,
|
||||
std::vector<QPointF> &vals, std::vector<QPointF> &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<SigItem> sigs;
|
||||
std::vector<SigItem> sigs;
|
||||
double cur_sec = 0;
|
||||
SeriesType series_type = SeriesType::Line;
|
||||
bool is_scrubbing = false;
|
||||
|
||||
@@ -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<void> 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QMenu>
|
||||
#include <QSpacerItem>
|
||||
|
||||
#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<MessageId, CanData> *msgs) {
|
||||
time_label->setText(QString::number(can->currentSec(), 'f', 3));
|
||||
if ((msgs && !msgs->contains(msg_id)))
|
||||
return;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ private:
|
||||
void updateState(const QHash<MessageId, CanData> * 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;
|
||||
|
||||
+14
-15
@@ -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>("uint64_t");
|
||||
qRegisterMetaType<SourceSet>("SourceSet");
|
||||
qRegisterMetaType<ReplyMsgType>("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();
|
||||
|
||||
|
||||
+88
-131
@@ -1,9 +1,6 @@
|
||||
#include "tools/cabana/messageswidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QPainter>
|
||||
@@ -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<int, QString> &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<QToolButton *>(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<int, QString> &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<MessageId> &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<MessageId> &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<MessageId> new_msgs;
|
||||
new_msgs.reserve(can->last_msgs.size() + dbc_address.size());
|
||||
|
||||
@@ -344,7 +347,7 @@ void MessageListModel::fetchData() {
|
||||
|
||||
void MessageListModel::msgsReceived(const QHash<MessageId, CanData> *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<int, QString> 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;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QCheckBox>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QSet>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
|
||||
#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<int, QString> &filters);
|
||||
void msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids);
|
||||
void fetchData();
|
||||
void filterAndSort();
|
||||
void suppress();
|
||||
void clearSuppress();
|
||||
void forceResetModel();
|
||||
void dbcModified();
|
||||
std::vector<MessageId> msgs;
|
||||
QSet<std::pair<MessageId, int>> 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<int> &roles = QVector<int>()) 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<int, QString> &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<MessageId> current_msg_id;
|
||||
QCheckBox *multiple_lines_bytes;
|
||||
MessageListModel *model;
|
||||
QPushButton *suppress_add;
|
||||
QPushButton *suppress_clear;
|
||||
QLabel *num_msg_label;
|
||||
QMenu *menu;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"(
|
||||
<b>Signal view</b><br />
|
||||
@@ -638,11 +644,10 @@ void SignalView::updateState(const QHash<MessageId, CanData> *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<int>(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<int>(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<void> synchronizer;
|
||||
for (int i = first_visible_row; i <= last_visible_row; ++i) {
|
||||
auto item = model->getItem(model->index(i, 1));
|
||||
|
||||
@@ -136,6 +136,7 @@ private:
|
||||
}
|
||||
};
|
||||
int max_value_width = 0;
|
||||
int value_column_width = 0;
|
||||
TreeView *tree;
|
||||
QLabel *sparkline_label;
|
||||
QSlider *sparkline_range_slider;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
@@ -139,9 +138,10 @@ void AbstractStream::updateLastMsgsTo(double sec) {
|
||||
}
|
||||
|
||||
void AbstractStream::mergeEvents(std::vector<Event *>::const_iterator first, std::vector<Event *>::const_iterator last) {
|
||||
static std::unordered_map<MessageId, std::deque<const CanEvent *>> new_events_map;
|
||||
static MessageEventsMap msg_events;
|
||||
static std::vector<const CanEvent *> 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<Event *>::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
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <QColor>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
|
||||
#include "common/timing.h"
|
||||
@@ -52,6 +52,8 @@ struct BusConfig {
|
||||
bool can_fd = false;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<MessageId, std::vector<const CanEvent *>> 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<const CanEvent *> &allEvents() const { return all_events_; }
|
||||
const std::vector<const CanEvent *> &events(const MessageId &id) const;
|
||||
virtual const std::vector<std::tuple<double, double, TimelineType>> 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<MessageId, CanData> *new_msgs, bool has_new_ids);
|
||||
void sourcesUpdated(const SourceSet &s);
|
||||
@@ -104,7 +108,7 @@ protected:
|
||||
std::atomic<bool> processing = false;
|
||||
std::unique_ptr<QHash<MessageId, CanData>> new_msgs;
|
||||
QHash<MessageId, CanData> all_msgs;
|
||||
std::unordered_map<MessageId, std::vector<const CanEvent *>> events_;
|
||||
MessageEventsMap events_;
|
||||
std::vector<const CanEvent *> all_events_;
|
||||
std::unique_ptr<MonotonicBuffer> event_buffer;
|
||||
std::mutex mutex;
|
||||
|
||||
@@ -46,6 +46,7 @@ void LiveStream::start() {
|
||||
emit streamStarted();
|
||||
stream_thread->start();
|
||||
startUpdateTimer();
|
||||
begin_date_time = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
LiveStream::~LiveStream() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<OpenpilotPrefix>();
|
||||
#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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<std::tuple<double, double, TimelineType>> getTimeline() override { return replay->getTimeline(); }
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
|
||||
signals:
|
||||
void qLogLoaded(int segnum, std::shared_ptr<LogReader> qlog);
|
||||
|
||||
private:
|
||||
void mergeSegments();
|
||||
std::unique_ptr<Replay> replay = nullptr;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
// SegmentTree
|
||||
|
||||
void SegmentTree::build(const QVector<QPointF> &arr) {
|
||||
void SegmentTree::build(const std::vector<QPointF> &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<QPointF> &arr) {
|
||||
}
|
||||
}
|
||||
|
||||
void SegmentTree::build_tree(const QVector<QPointF> &arr, int n, int left, int right) {
|
||||
void SegmentTree::build_tree(const std::vector<QPointF> &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) {
|
||||
|
||||
+3
-5
@@ -57,12 +57,12 @@ enum {
|
||||
class SegmentTree {
|
||||
public:
|
||||
SegmentTree() = default;
|
||||
void build(const QVector<QPointF> &arr);
|
||||
void build(const std::vector<QPointF> &arr);
|
||||
inline std::pair<double, double> minmax(int left, int right) const { return get_minmax(1, 0, size - 1, left, right); }
|
||||
|
||||
private:
|
||||
std::pair<double, double> get_minmax(int n, int left, int right, int range_left, int range_right) const;
|
||||
void build_tree(const QVector<QPointF> &arr, int n, int left, int right);
|
||||
void build_tree(const std::vector<QPointF> &arr, int n, int left, int right);
|
||||
std::vector<std::pair<double, double>> 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);
|
||||
|
||||
+125
-109
@@ -1,17 +1,15 @@
|
||||
#include "tools/cabana/videowidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QHBoxLayout>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QStackedLayout>
|
||||
#include <QStyleOptionSlider>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtConcurrent>
|
||||
|
||||
@@ -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"(
|
||||
<b>Video</b><br />
|
||||
<!-- TODO: add descprition here -->
|
||||
@@ -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<ReplayStream*>(can), &ReplayStream::qLogLoaded, slider, &Slider::parseQLog);
|
||||
return w;
|
||||
}
|
||||
|
||||
void VideoWidget::loopPlaybackClicked() {
|
||||
auto replay = qobject_cast<ReplayStream *>(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<QFuture<void>>(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<LogReader> qlog) {
|
||||
const auto &segments = qobject_cast<ReplayStream *>(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<ReplayStream *>(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();
|
||||
}
|
||||
|
||||
+17
-19
@@ -1,19 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <QFuture>
|
||||
#include <QLabel>
|
||||
#include <QHBoxLayout>
|
||||
#include <QSlider>
|
||||
#include <QToolButton>
|
||||
|
||||
#include "selfdrive/ui/qt/widgets/cameraview.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
struct AlertInfo {
|
||||
cereal::ControlsState::AlertStatus status;
|
||||
@@ -42,6 +37,9 @@ public:
|
||||
void setTimeRange(double min, double max);
|
||||
AlertInfo alertInfo(double sec);
|
||||
QPixmap thumbnail(double sec);
|
||||
void parseQLog(int segnum, std::shared_ptr<LogReader> qlog);
|
||||
|
||||
const double factor = 1000.0;
|
||||
|
||||
signals:
|
||||
void updateMaximumTime(double);
|
||||
@@ -51,15 +49,9 @@ private:
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
bool event(QEvent *event) override;
|
||||
void paintEvent(QPaintEvent *ev) override;
|
||||
void parseQLog();
|
||||
|
||||
const double factor = 1000.0;
|
||||
std::vector<std::tuple<double, double, TimelineType>> timeline;
|
||||
std::mutex thumbnail_lock;
|
||||
std::atomic<bool> abort_parse_qlog = false;
|
||||
QMap<uint64_t, QPixmap> thumbnails;
|
||||
std::map<uint64_t, AlertInfo> alerts;
|
||||
std::unique_ptr<QFuture<void>> qlog_future;
|
||||
InfoLabel thumbnail_label;
|
||||
};
|
||||
|
||||
@@ -72,16 +64,22 @@ public:
|
||||
void setMaximumTime(double sec);
|
||||
|
||||
protected:
|
||||
QString formatTime(double sec, bool include_milliseconds = false);
|
||||
void updateState();
|
||||
void updatePlayBtnState();
|
||||
QWidget *createCameraWidget();
|
||||
QHBoxLayout *createPlaybackController();
|
||||
void loopPlaybackClicked();
|
||||
|
||||
CameraWidget *cam_widget;
|
||||
double maximum_time = 0;
|
||||
QLabel *end_time_label;
|
||||
QLabel *time_label;
|
||||
QToolButton *play_btn;
|
||||
QToolButton *skip_to_end_btn = nullptr;
|
||||
InfoLabel *alert_label;
|
||||
Slider *slider;
|
||||
QToolButton *time_btn = nullptr;
|
||||
ToolButton *seek_backward_btn = nullptr;
|
||||
ToolButton *play_btn = nullptr;
|
||||
ToolButton *seek_forward_btn = nullptr;
|
||||
ToolButton *loop_btn = nullptr;
|
||||
QToolButton *speed_btn = nullptr;
|
||||
ToolButton *skip_to_end_btn = nullptr;
|
||||
InfoLabel *alert_label = nullptr;
|
||||
Slider *slider = nullptr;
|
||||
};
|
||||
|
||||
@@ -33,8 +33,7 @@ def run_remote_checker(lat, lon, alt, duration, ip_addr):
|
||||
return False, None, None
|
||||
|
||||
matched, log, info = con.root.exposed_run_checker(lat, lon, alt,
|
||||
timeout=duration,
|
||||
use_laikad=True)
|
||||
timeout=duration)
|
||||
con.close() # TODO: might wanna fetch more logs here
|
||||
con = None
|
||||
|
||||
@@ -43,7 +42,7 @@ def run_remote_checker(lat, lon, alt, duration, ip_addr):
|
||||
|
||||
|
||||
stats = defaultdict(int) # type: ignore
|
||||
keys = ['success', 'failed', 'ublox_fail', 'laikad_fail', 'proc_crash', 'checker_crash']
|
||||
keys = ['success', 'failed', 'ublox_fail', 'proc_crash', 'checker_crash']
|
||||
|
||||
def print_report():
|
||||
print("\nFuzzy testing report summary:")
|
||||
@@ -62,10 +61,7 @@ def update_stats(matched, log, info):
|
||||
if log == "CHECKER CRASHED":
|
||||
stats['checker_crash'] += 1
|
||||
if log == "TIMEOUT":
|
||||
if "LAIKAD" in info:
|
||||
stats['laikad_fail'] += 1
|
||||
else: # "UBLOX" in info
|
||||
stats['ublox_fail'] += 1
|
||||
stats['ublox_fail'] += 1
|
||||
|
||||
|
||||
def main(ip_addr, continuous_mode, timeout, pos):
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
import rpyc
|
||||
from rpyc.utils.server import ThreadedServer
|
||||
|
||||
#from openpilot.common.params import Params
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.selfdrive.manager.process_config import managed_processes
|
||||
from laika.lib.coordinates import ecef2geodetic
|
||||
|
||||
DELTA = 0.001
|
||||
ALT_DELTA = 30
|
||||
MATCH_NUM = 10
|
||||
REPORT_STATS = 10
|
||||
|
||||
EPHEM_CACHE = "/data/params/d/LaikadEphemerisV3"
|
||||
|
||||
SERVER_LOG_FILE = "/tmp/fuzzy_server.log"
|
||||
server_log = open(SERVER_LOG_FILE, "w+")
|
||||
|
||||
def slog(msg):
|
||||
server_log.write(f"{datetime.now().strftime('%H:%M:%S.%f')} | {msg}\n")
|
||||
server_log.flush()
|
||||
|
||||
def handle_laikad(msg):
|
||||
if not hasattr(msg, 'correctedMeasurements'):
|
||||
return None
|
||||
|
||||
num_corr = len(msg.correctedMeasurements)
|
||||
pos_ecef = msg.positionECEF.value
|
||||
pos_geo = []
|
||||
if len(pos_ecef) > 0:
|
||||
pos_geo = ecef2geodetic(pos_ecef)
|
||||
|
||||
pos_std = msg.positionECEF.std
|
||||
pos_valid = msg.positionECEF.valid
|
||||
|
||||
slog(f"{num_corr} {pos_geo} {pos_ecef} {pos_std} {pos_valid}")
|
||||
return pos_geo, (num_corr, pos_geo, list(pos_ecef), list(msg.positionECEF.std))
|
||||
|
||||
hw_msgs = 0
|
||||
ephem_msgs: dict = defaultdict(int)
|
||||
def handle_ublox(msg):
|
||||
global hw_msgs
|
||||
|
||||
d = msg.to_dict()
|
||||
|
||||
if 'hwStatus2' in d:
|
||||
hw_msgs += 1
|
||||
|
||||
if 'ephemeris' in d:
|
||||
ephem_msgs[msg.ephemeris.svId] += 1
|
||||
|
||||
num_meas = None
|
||||
if 'measurementReport' in d:
|
||||
num_meas = msg.measurementReport.numMeas
|
||||
|
||||
return [hw_msgs, ephem_msgs, num_meas]
|
||||
|
||||
|
||||
def start_procs(procs):
|
||||
for p in procs:
|
||||
managed_processes[p].start()
|
||||
time.sleep(1)
|
||||
|
||||
def kill_procs(procs, no_retry=False):
|
||||
for p in procs:
|
||||
managed_processes[p].stop()
|
||||
time.sleep(1)
|
||||
|
||||
if not no_retry:
|
||||
for p in procs:
|
||||
mp = managed_processes[p].proc
|
||||
if mp is not None and mp.is_alive():
|
||||
managed_processes[p].stop()
|
||||
time.sleep(3)
|
||||
|
||||
def check_alive_procs(procs):
|
||||
for p in procs:
|
||||
mp = managed_processes[p].proc
|
||||
if mp is None or not mp.is_alive():
|
||||
return False, p
|
||||
return True, None
|
||||
|
||||
|
||||
class RemoteCheckerService(rpyc.Service):
|
||||
def on_connect(self, conn):
|
||||
pass
|
||||
|
||||
def on_disconnect(self, conn):
|
||||
#kill_procs(self.procs, no_retry=False)
|
||||
# this execution is delayed, it will kill the next run of laikad
|
||||
# TODO: add polling to wait for everything is killed
|
||||
pass
|
||||
|
||||
def run_checker(self, slat, slon, salt, sockets, procs, timeout):
|
||||
global hw_msgs, ephem_msgs
|
||||
hw_msgs = 0
|
||||
ephem_msgs = defaultdict(int)
|
||||
|
||||
slog(f"Run test: {slat} {slon} {salt}")
|
||||
|
||||
# quectel_mod = Params().get_bool("UbloxAvailable")
|
||||
|
||||
match_cnt = 0
|
||||
msg_cnt = 0
|
||||
stats_laikad = []
|
||||
stats_ublox = []
|
||||
|
||||
self.procs = procs
|
||||
start_procs(procs)
|
||||
sm = messaging.SubMaster(sockets)
|
||||
|
||||
start_time = time.monotonic()
|
||||
while True:
|
||||
sm.update()
|
||||
|
||||
if sm.updated['ubloxGnss']:
|
||||
stats_ublox.append(handle_ublox(sm['ubloxGnss']))
|
||||
|
||||
if sm.updated['gnssMeasurements']:
|
||||
pos_geo, stats = handle_laikad(sm['gnssMeasurements'])
|
||||
if pos_geo is None or len(pos_geo) == 0:
|
||||
continue
|
||||
|
||||
match = all(abs(g-s) < DELTA for g,s in zip(pos_geo[:2], [slat, slon], strict=True))
|
||||
match &= abs(pos_geo[2] - salt) < ALT_DELTA
|
||||
if match:
|
||||
match_cnt += 1
|
||||
if match_cnt >= MATCH_NUM:
|
||||
return True, "MATCH", f"After: {round(time.monotonic() - start_time, 4)}"
|
||||
|
||||
# keep some stats for error reporting
|
||||
stats_laikad.append(stats)
|
||||
|
||||
if (msg_cnt % 10) == 0:
|
||||
a, p = check_alive_procs(procs)
|
||||
if not a:
|
||||
return False, "PROC CRASH", f"{p}"
|
||||
msg_cnt += 1
|
||||
|
||||
if (time.monotonic() - start_time) > timeout:
|
||||
h = f"LAIKAD: {stats_laikad[-REPORT_STATS:]}"
|
||||
if len(h) == 0:
|
||||
h = f"UBLOX: {stats_ublox[-REPORT_STATS:]}"
|
||||
return False, "TIMEOUT", h
|
||||
|
||||
|
||||
def exposed_run_checker(self, slat, slon, salt, timeout=180, use_laikad=True):
|
||||
try:
|
||||
procs = []
|
||||
sockets = []
|
||||
|
||||
if use_laikad:
|
||||
procs.append("laikad") # pigeond, ubloxd # might wanna keep them running
|
||||
sockets += ['ubloxGnss', 'gnssMeasurements']
|
||||
|
||||
if os.path.exists(EPHEM_CACHE):
|
||||
os.remove(EPHEM_CACHE)
|
||||
shutil.rmtree(Paths.download_cache_root(), ignore_errors=True)
|
||||
|
||||
ret = self.run_checker(slat, slon, salt, sockets, procs, timeout)
|
||||
kill_procs(procs)
|
||||
return ret
|
||||
|
||||
except Exception as e:
|
||||
# always make sure processes get killed
|
||||
kill_procs(procs)
|
||||
return False, "CHECKER CRASHED", f"{str(e)}"
|
||||
|
||||
|
||||
def exposed_kill_procs(self):
|
||||
kill_procs(self.procs, no_retry=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Sever Log written to: {SERVER_LOG_FILE}")
|
||||
t = ThreadedServer(RemoteCheckerService, port=18861)
|
||||
t.start()
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import cereal.messaging as messaging
|
||||
import openpilot.system.sensord.pigeond as pd
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.hardware import TICI
|
||||
from openpilot.selfdrive.manager.process_config import managed_processes
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
|
||||
|
||||
def wait_for_location(sm, timeout, con=10):
|
||||
cons_meas = 0
|
||||
start_time = time.monotonic()
|
||||
while (time.monotonic() - start_time) < timeout:
|
||||
sm.update()
|
||||
if not sm.updated["gnssMeasurements"]:
|
||||
continue
|
||||
|
||||
msg = sm["gnssMeasurements"]
|
||||
cons_meas = (cons_meas + 1) if 'positionECEF' in msg.to_dict() else 0
|
||||
if cons_meas >= con:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class TestLaikad(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
if not TICI:
|
||||
raise unittest.SkipTest
|
||||
|
||||
ublox_available = Params().get_bool("UbloxAvailable")
|
||||
if not ublox_available:
|
||||
raise unittest.SkipTest
|
||||
|
||||
def setUp(self):
|
||||
# ensure laikad cold start
|
||||
Params().remove("LaikadEphemerisV3")
|
||||
os.environ["LAIKAD_NO_INTERNET"] = "1"
|
||||
managed_processes['laikad'].start()
|
||||
|
||||
def tearDown(self):
|
||||
managed_processes['laikad'].stop()
|
||||
|
||||
|
||||
@with_processes(['pigeond', 'ubloxd'])
|
||||
def test_laikad_cold_start(self):
|
||||
time.sleep(5)
|
||||
|
||||
start_time = time.monotonic()
|
||||
sm = messaging.SubMaster(["gnssMeasurements"])
|
||||
|
||||
success = wait_for_location(sm, 60*2, con=10)
|
||||
duration = time.monotonic() - start_time
|
||||
|
||||
assert success, "Waiting for location timed out (2min)!"
|
||||
assert duration < 60, f"Received Location {duration}!"
|
||||
|
||||
|
||||
@with_processes(['ubloxd'])
|
||||
def test_laikad_ublox_reset_start(self):
|
||||
time.sleep(2)
|
||||
|
||||
pigeon, pm = pd.create_pigeon()
|
||||
pd.init_baudrate(pigeon)
|
||||
assert pigeon.reset_device(), "Could not reset device!"
|
||||
|
||||
laikad_sock = messaging.sub_sock("gnssMeasurements", timeout=0.1)
|
||||
ublox_gnss_sock = messaging.sub_sock("ubloxGnss", timeout=0.1)
|
||||
|
||||
pd.init_baudrate(pigeon)
|
||||
pd.initialize_pigeon(pigeon)
|
||||
pd.run_receiving(pigeon, pm, 180)
|
||||
|
||||
ublox_msgs = messaging.drain_sock(ublox_gnss_sock)
|
||||
laikad_msgs = messaging.drain_sock(laikad_sock)
|
||||
|
||||
gps_ephem_cnt = 0
|
||||
glonass_ephem_cnt = 0
|
||||
for um in ublox_msgs:
|
||||
if um.ubloxGnss.which() == 'ephemeris':
|
||||
gps_ephem_cnt += 1
|
||||
elif um.ubloxGnss.which() == 'glonassEphemeris':
|
||||
glonass_ephem_cnt += 1
|
||||
|
||||
assert gps_ephem_cnt > 0, "NO gps ephemeris collected!"
|
||||
assert glonass_ephem_cnt > 0, "NO glonass ephemeris collected!"
|
||||
|
||||
pos_meas = 0
|
||||
duration = -1
|
||||
for lm in laikad_msgs:
|
||||
pos_meas = (pos_meas + 1) if 'positionECEF' in lm.gnssMeasurements.to_dict() else 0
|
||||
if pos_meas > 5:
|
||||
duration = (lm.logMonoTime - laikad_msgs[0].logMonoTime)*1e-9
|
||||
break
|
||||
|
||||
assert pos_meas > 5, "NOT enough positions at end of read!"
|
||||
assert duration < 120, "Laikad took too long to get a Position!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -153,12 +153,10 @@ void Replay::buildTimeline() {
|
||||
|
||||
const auto &route_segments = route_->segments();
|
||||
for (auto it = route_segments.cbegin(); it != route_segments.cend() && !exit_; ++it) {
|
||||
LogReader log;
|
||||
if (!log.load(it->second.qlog.toStdString(), &exit_,
|
||||
{cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG},
|
||||
!hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue;
|
||||
std::shared_ptr<LogReader> log(new LogReader());
|
||||
if (!log->load(it->second.qlog.toStdString(), &exit_, {}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue;
|
||||
|
||||
for (const Event *e : log.events) {
|
||||
for (const Event *e : log->events) {
|
||||
if (e->which == cereal::Event::Which::CONTROLS_STATE) {
|
||||
auto cs = e->event.getControlsState();
|
||||
|
||||
@@ -186,6 +184,8 @@ void Replay::buildTimeline() {
|
||||
timeline.push_back({toSeconds(e->mono_time), toSeconds(e->mono_time), TimelineType::UserFlag});
|
||||
}
|
||||
}
|
||||
std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); });
|
||||
emit qLogLoaded(it->first, log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ enum class FindFlag {
|
||||
|
||||
enum class TimelineType { None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserFlag };
|
||||
typedef bool (*replayEventFilter)(const Event *, void *);
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<LogReader>);
|
||||
|
||||
class Replay : public QObject {
|
||||
Q_OBJECT
|
||||
@@ -91,6 +92,7 @@ signals:
|
||||
void streamStarted();
|
||||
void segmentsMerged();
|
||||
void seekedTo(double sec);
|
||||
void qLogLoaded(int segnum, std::shared_ptr<LogReader> qlog);
|
||||
|
||||
protected slots:
|
||||
void segmentLoadFinished(bool success);
|
||||
|
||||
Reference in New Issue
Block a user