diff --git a/Jenkinsfile b/Jenkinsfile index b9b9eda667..0d624954ea 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -137,10 +137,10 @@ pipeline { } } - stage('camerad') { + stage('camerad-ar') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici-party", [ + phone_steps("tici-ar0321", [ ["build", "cd selfdrive/manager && ./build.py"], ["test camerad", "python system/camerad/test/test_camerad.py"], ["test exposure", "python system/camerad/test/test_exposure.py"], @@ -148,6 +148,27 @@ pipeline { } } + stage('camerad-ox') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici-ox03c10", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test camerad", "python system/camerad/test/test_camerad.py"], + ["test exposure", "python system/camerad/test/test_exposure.py"], + ]) + } + } + + stage('sensord (LSM-C)') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici-lsmc", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test sensord", "cd selfdrive/sensord/tests && python -m unittest test_sensord.py"], + ]) + } + } + stage('replay') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { diff --git a/RELEASES.md b/RELEASES.md index 744168c8fb..83b3ac46f4 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -15,6 +15,8 @@ Version 0.8.17 (2022-XX-XX) * Added button to bookmark events while driving; view them later in comma connect * AGNOS 6 * tools: new and improved cabana thanks to deanlee! +* Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin! +* Kia Sportage 2023 support thanks to sunnyhaibin! * Kia Sportage Hybrid 2023 support thanks to sunnyhaibin! Version 0.8.16 (2022-08-26) diff --git a/docs/CARS.md b/docs/CARS.md index b5e0e39027..c36fb6f54a 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,23 +4,23 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 211 Supported Cars +# 213 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| |Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Acura|RDX 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|Q3 2020-21|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Cadillac|Escalade ESV 2016[1](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Chevrolet|Silverado 1500 2020-21|Safety Package II|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Chevrolet|Volt 2017-18[1](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| +|Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| +|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| +|Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| @@ -28,59 +28,60 @@ A supported vehicle is one that just works when you install a comma three. All s |Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None| |Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Genesis|G70 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| +|Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Genesis|G90 2017-18|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|GMC|Acadia 2018[1](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Honda|Accord 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Accord Hybrid 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| +|GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Civic 2019-21|All|openpilot|0 mph|2 mph[2](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| -|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic Hatchback 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|Civic 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| +|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|Civic Hatchback 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| |Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|CR-V 2017-22|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|e 2020|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Insight 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Inspire 2018|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| +|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| -|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| +|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| |Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Ioniq 5 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| +|Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| |Hyundai|Ioniq 5 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| +|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Plug-in Hybrid 2020-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| |Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai O| -|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I| -|Hyundai|Palisade 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Santa Fe 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D| -|Hyundai|Santa Fe 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Santa Fe Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| +|Hyundai|Santa Cruz 2021-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D| +|Hyundai|Santa Fe 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Hyundai|Santa Fe Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Sonata 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Hyundai|Sonata Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Hyundai|Sonata 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| +|Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| +|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Tucson Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| @@ -88,38 +89,39 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| |Kia|EV6 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Kia|Niro EV 2019|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro EV 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Kia|Niro EV 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Niro EV 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| +|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| +|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| +|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| +|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| +|Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Telluride 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Kia|Telluride 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|ES Hybrid 2017-18|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|ES Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|ES Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|NX 2018-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|NX Hybrid 2018-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2016|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|RX Hybrid 2016|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX Hybrid 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| @@ -129,8 +131,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram| -|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| +|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| @@ -141,94 +143,95 @@ A supported vehicle is one that just works when you install a comma three. All s |Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B| |Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Škoda|Kamiq 2021[5](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Karoq 2019-21[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Superb 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| +|Škoda|Kamiq 2021[6](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| +|Škoda|Karoq 2019-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| +|Škoda|Superb 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Avalon 2016|Toyota Safety Sense P|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2017-18|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2019-21|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Avalon Hybrid 2019-21|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry 2018-20|All|Stock|0 mph[4](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry 2021-22|All|openpilot|0 mph[4](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|Camry 2018-20|All|Stock|0 mph[5](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Camry 2021-22|All|openpilot|0 mph[5](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla 2017-19|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Cross (Non-US only) 2020-21|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander Hybrid 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius 2016|Toyota Safety Sense P|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius 2017-20|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius Prime 2017-20|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius v 2017|Toyota Safety Sense P|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 2016|Toyota Safety Sense P|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 2017-18|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2017-18|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Sienna 2018-20|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Volkswagen|Arteon 2018-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Arteon eHybrid 2020-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Arteon R 2020-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Atlas 2018-23[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Atlas Cross Sport 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|California 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Caravelle 2020[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|CC 2018-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf 2015-20[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf R 2015-19[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Jetta 2018-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Jetta GLI 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat 2015-22[6,7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat Alltrack 2015-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat GTE 2015-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Polo 2020-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Polo GTI 2020-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|T-Cross 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|T-Roc 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Taos 2022[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont 2018-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont Cross Sport 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont X 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Tiguan 2019-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Passat 2015-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Polo 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| +|Volkswagen|Polo GTI 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| +|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| +|Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Tiguan 2019-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -1Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
-22019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-3When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
-4openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-5Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-6Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-7Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black (older design) or light brown (newer design). In the interim, if your car has a J533 connector CAN gateway inside the dashboard, choose "VW J533 Development" from the vehicle drop-down for a suitable harness. (Some newer models are also observed to not have a J533 connector.)
-8Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)
+1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `master-ci`. Using openpilot longitudinal may disable Automatic Emergency Braking (AEB).
+2When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
+3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
+42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
+5openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+6Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+7Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+8Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+9Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/opendbc b/opendbc index b3dc569994..296f190000 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit b3dc569994fd10e4de04afd650980c51ddfce5e1 +Subproject commit 296f190000a2e71408e207ba21a2257cc853ec15 diff --git a/poetry.lock b/poetry.lock index 2dff4fecf1..d79a3f035f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -230,7 +230,7 @@ python-versions = ">=3.5" dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "av" @@ -531,7 +531,7 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode_backport = ["unicodedata2"] +unicode-backport = ["unicodedata2"] [[package]] name = "cleo" @@ -1410,7 +1410,7 @@ notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test_extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] [[package]] name = "ipython-genutils" @@ -1459,9 +1459,9 @@ python-versions = ">=3.6.1,<4.0" [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] -requirements_deprecated_finder = ["pip-api", "pipreqs"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "itsdangerous" @@ -1892,7 +1892,7 @@ mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] -code_style = ["pre-commit (==2.6)"] +code-style = ["pre-commit (==2.6)"] compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] linkify = ["linkify-it-py (>=1.0,<2.0)"] plugins = ["mdit-py-plugins"] @@ -1959,7 +1959,7 @@ python-versions = ">=3.7" markdown-it-py = ">=1.0.0,<3.0.0" [package.extras] -code_style = ["pre-commit"] +code-style = ["pre-commit"] rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] @@ -2169,7 +2169,7 @@ sphinx = ">=4,<6" typing-extensions = "*" [package.extras] -code_style = ["pre-commit (>=2.12,<3.0)"] +code-style = ["pre-commit (>=2.12,<3.0)"] linkify = ["linkify-it-py (>=1.0,<2.0)"] rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx (<5.2)", "sphinx-pytest"] @@ -3193,7 +3193,7 @@ optional = false python-versions = ">=3.6" [package.extras] -asyncio_client = ["aiohttp (>=3.4)"] +asyncio-client = ["aiohttp (>=3.4)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] [[package]] @@ -3217,7 +3217,7 @@ bidict = ">=0.21.0" python-engineio = ">=4.3.0" [package.extras] -asyncio_client = ["aiohttp (>=3.4)"] +asyncio-client = ["aiohttp (>=3.4)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] [[package]] @@ -3393,7 +3393,7 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-oauthlib" @@ -3584,7 +3584,7 @@ falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)"] httpx = ["httpx (>=0.16.0)"] -pure_eval = ["asttokens", "executing", "pure-eval"] +pure-eval = ["asttokens", "executing", "pure-eval"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] rq = ["rq (>=0.6)"] @@ -3834,6 +3834,14 @@ python-versions = ">=3.5" lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] +[[package]] +name = "spidev" +version = "3.6" +description = "Python bindings for Linux SPI access through spidev" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "sqlalchemy" version = "1.4.42" @@ -3850,19 +3858,19 @@ aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] mssql = ["pyodbc"] -mssql_pymssql = ["pymssql"] -mssql_pyodbc = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql_connector = ["mysql-connector-python"] +mysql-connector = ["mysql-connector-python"] oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] -postgresql_asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] -postgresql_psycopg2binary = ["psycopg2-binary"] -postgresql_psycopg2cffi = ["psycopg2cffi"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3_binary"] @@ -4378,7 +4386,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "76f06d46b1c308e59968994e42193599d8ba3cab6d552460c9c48f282313b64d" +content-hash = "d2854112975a9d83a9540175b2d430487e40e0292d48a1ba6c591db60a08c136" [metadata.files] adal = [ @@ -5384,6 +5392,7 @@ gevent = [ {file = "gevent-22.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2ea4ce36c09355379bc038be2bd50118f97d2eb6381b7096de4d05aa4c3e241"}, {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e73c9f71aa2a6795ecbec9b57282b002375e863e283558feb87b62840c8c1ac"}, {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc3758f0dc95007c1780d28a9fd2150416a79c50f308f62a674d78a845ea1b9"}, + {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c10ca0beeab0c6be516030471ea630447ddd1f649d3335e5b162097cd4130a"}, {file = "gevent-22.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fe2c0ff095171c49f78f1d4e6dc89fa58253783c7b6dccab9f1d76e2ee391f10"}, {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d18fcc324f39a3b21795022eb47c7752d6e4f4ed89d8cca41f1cc604553265b3"}, {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ea39c70ce166c4a1d4386c7fae96cb8d84ad799527b3378406051104d15443"}, @@ -6554,6 +6563,11 @@ pillow-avif-plugin = [ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017e5e52cb4320414e8ce3e2089eae2cb87c22c73ff6012b17ae326fc5753b20"}, {file = "pillow_avif_plugin-1.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a57136d4866de5dc80cfb24d66655955fbdd87acf1d11d88c8dc2ab41023e46"}, {file = "pillow_avif_plugin-1.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:f339511d0fccb69e3a5e3af39f8fe6700b0a07279015006ea56f8f49e7fecff4"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05e821ecd90bb0b8d2dc7610625372cc47de9cb893d09662528bad572f669d1c"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33886a5f9796fe9a8a3bc25ccfdeba7db119adb50b7004f1928a14b07d0213a"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75b7ed186c2f740dd26e556f6a966c59a170b70263e429a2c81920fe444da8a7"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11aef6b79078b8dad25c928e5871c146ab94424472851d5bf539ba62abde9ac"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10696c536d68a14cefea3b98edb8d5a7ae29e8e07458f1d59c5d1cd780a8bf2a"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1a7291d6a5fb7336e72685a31d193e0b3a6bee9986c9ac4d8bd4b68dbe6d4f7f"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:14b9c5dbf237e7dc12f69819ea181a457b3bd4f59f8cd71d028d3635fd3bcab4"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:799cbfbeee831332d280c80df9ce16b5c3b1224c318264e97e89df8da32e870e"}, @@ -6942,11 +6956,13 @@ pyprof2calltree = [ ] pyproj = [ {file = "pyproj-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f343725566267a296b09ee7e591894f1fdc90f84f8ad5ec476aeb53bd4479c07"}, + {file = "pyproj-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5816807ca0bdc7256558770c6206a6783a3f02bcf844f94ee245f197bb5f7285"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e609903572a56cca758bbaee5c1663c3e829ddce5eec4f368e68277e37022b"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fd425ee8b6781c249c7adb7daa2e6c41ce573afabe4f380f5eecd913b56a3be"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954b068136518b3174d0a99448056e97af62b63392a95c420894f7de2229dae6"}, {file = "pyproj-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:4a23d84c5ffc383c7d9f0bde3a06fc1f6697b1b96725597f8f01e7b4bef0a2b5"}, {file = "pyproj-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f9c100fd0fd80edbc7e4daa303600a8cbef6f0de43d005617acb38276b88dc0"}, + {file = "pyproj-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa5171f700f174777a9e9ed8f4655583243967c0f9cf2c90e3f54e54ff740134"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a496d9057b2128db9d733e66b206f2d5954bbae6b800d412f562d780561478c"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e54796e2d9554a5eb8f11df4748af1fbbc47f76aa234d6faf09216a84554c5"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a454a7c4423faa2a14e939d08ef293ee347fa529c9df79022b0585a6e1d8310c"}, @@ -6957,6 +6973,7 @@ pyproj = [ {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f80adda8c54b84271a93829477a01aa57bc178c834362e9f74e1de1b5033c74c"}, {file = "pyproj-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:221d8939685e0c43ee594c9f04b6a73a10e8e1cc0e85f28be0b4eb2f1bc8777d"}, {file = "pyproj-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d94afed99f31673d3d19fe750283621e193e2a53ca9e0443bf9d092c3905833b"}, + {file = "pyproj-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fff9c3a991508f16027be27d153f6c5583d03799443639d13c681e60f49e2d7"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b85acf09e5a9e35cd9ee72989793adb7089b4e611be02a43d3d0bda50ad116b"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45554f47d1a12a84b0620e4abc08a2a1b5d9f273a4759eaef75e74788ec7162a"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12f62c20656ac9b6076ebb213e9a635d52f4f01fef95310121d337e62e910cb6"}, @@ -7537,6 +7554,10 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] +spidev = [ + {file = "spidev-3.6-cp39-cp39-linux_armv7l.whl", hash = "sha256:280abc00a1ef7780ef62c3f294f52a2527b6c47d8c269fea98664970bcaf6da5"}, + {file = "spidev-3.6.tar.gz", hash = "sha256:14dbc37594a4aaef85403ab617985d3c3ef464d62bc9b769ef552db53701115b"}, +] sqlalchemy = [ {file = "SQLAlchemy-1.4.42-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:28e881266a172a4d3c5929182fde6bb6fba22ac93f137d5380cc78a11a9dd124"}, {file = "SQLAlchemy-1.4.42-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ca9389a00f639383c93ed00333ed763812f80b5ae9e772ea32f627043f8c9c88"}, diff --git a/pyproject.toml b/pyproject.toml index ebb7954536..7e76b9cdfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ tqdm = "^4.64.0" urllib3 = "^1.26.10" utm = "^0.7.0" websocket_client = "^1.3.3" +spidev = "^3.6" [tool.poetry.group.dev.dependencies] diff --git a/release/files_common b/release/files_common index e3c417040c..61d16a2088 100644 --- a/release/files_common +++ b/release/files_common @@ -90,6 +90,8 @@ selfdrive/boardd/boardd_api_impl.pyx selfdrive/boardd/can_list_to_can_capnp.cc selfdrive/boardd/panda.cc selfdrive/boardd/panda.h +selfdrive/boardd/panda_comms.h +selfdrive/boardd/panda_comms.cc selfdrive/boardd/set_time.py selfdrive/boardd/pandad.py diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript index dcbea03d3c..356b5de663 100644 --- a/selfdrive/boardd/SConscript +++ b/selfdrive/boardd/SConscript @@ -1,9 +1,9 @@ Import('env', 'envCython', 'common', 'cereal', 'messaging') libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] -env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc'], LIBS=libs) +env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs) env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) if GetOption('test'): - env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc'], LIBS=libs) + env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 2d613b68ce..5496902252 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -19,8 +19,6 @@ #include #include -#include - #include "cereal/gen/cpp/car.capnp.h" #include "cereal/messaging/messaging.h" #include "common/params.h" @@ -67,7 +65,7 @@ static std::string get_time_str(const struct tm &time) { bool check_all_connected(const std::vector &pandas) { for (const auto& panda : pandas) { - if (!panda->connected) { + if (!panda->connected()) { do_exit = true; return false; } @@ -184,7 +182,7 @@ bool safety_setter_thread(std::vector pandas) { return true; } -Panda *usb_connect(std::string serial="", uint32_t index=0) { +Panda *connect(std::string serial="", uint32_t index=0) { std::unique_ptr panda; try { panda = std::make_unique(serial, (index * PANDA_BUS_CNT)); @@ -227,9 +225,9 @@ void can_send_thread(std::vector pandas, bool fake_send) { //Dont send if older than 1 second if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { for (const auto& panda : pandas) { - LOGT("sending sendcan to panda: %s", (panda->usb_serial).c_str()); + LOGT("sending sendcan to panda: %s", (panda->hw_serial).c_str()); panda->can_send(event.getSendcan()); - LOGT("sendcan sent to panda: %s", (panda->usb_serial).c_str()); + LOGT("sendcan sent to panda: %s", (panda->hw_serial).c_str()); } } } @@ -357,7 +355,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector } #endif - if (!panda->comms_healthy) { + if (!panda->comms_healthy()) { evt.setValid(false); } @@ -433,7 +431,7 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) { // build msg MessageBuilder msg; auto evt = msg.initEvent(); - evt.setValid(panda->comms_healthy); + evt.setValid(panda->comms_healthy()); auto ps = evt.initPeripheralState(); ps.setPandaType(panda->hw_type); @@ -526,7 +524,7 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) { FirstOrderFilter integ_lines_filter(0, 30.0, 0.05); - while (!do_exit && panda->connected) { + while (!do_exit && panda->connected()) { cnt++; sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update? @@ -595,7 +593,7 @@ void boardd_main_thread(std::vector serials) { // connect to all provided serials std::vector pandas; for (int i = 0; i < serials.size() && !do_exit; /**/) { - Panda *p = usb_connect(serials[i], i); + Panda *p = connect(serials[i], i); if (!p) { // send empty pandaState & peripheralState and try again send_empty_panda_state(&pm); diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 0b8630b0c0..e68558632e 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -4,75 +4,15 @@ #include #include -#include #include "cereal/messaging/messaging.h" #include "panda/board/dlc_to_len.h" -#include "common/gpio.h" #include "common/swaglog.h" #include "common/util.h" -static int init_usb_ctx(libusb_context **context) { - assert(context != nullptr); - - int err = libusb_init(context); - if (err != 0) { - LOGE("libusb initialization error"); - return err; - } - -#if LIBUSB_API_VERSION >= 0x01000106 - libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); -#else - libusb_set_debug(*context, 3); -#endif - - return err; -} - - Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { - // init libusb - ssize_t num_devices; - libusb_device **dev_list = NULL; - int err = init_usb_ctx(&ctx); - if (err != 0) { goto fail; } - - // connect by serial - num_devices = libusb_get_device_list(ctx, &dev_list); - if (num_devices < 0) { goto fail; } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device_descriptor desc; - libusb_get_device_descriptor(dev_list[i], &desc); - if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { - int ret = libusb_open(dev_list[i], &dev_handle); - if (dev_handle == NULL || ret < 0) { goto fail; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - if (ret < 0) { goto fail; } - - usb_serial = std::string((char *)desc_serial, ret).c_str(); - if (serial.empty() || serial == usb_serial) { - break; - } - libusb_close(dev_handle); - dev_handle = NULL; - } - } - if (dev_handle == NULL) goto fail; - libusb_free_device_list(dev_list, 1); - dev_list = nullptr; - - if (libusb_kernel_driver_active(dev_handle, 0) == 1) { - libusb_detach_kernel_driver(dev_handle, 0); - } - - err = libusb_set_configuration(dev_handle, 1); - if (err != 0) { goto fail; } - - err = libusb_claim_interface(dev_handle, 0); - if (err != 0) { goto fail; } + // TODO: support SPI here one day... + handle = std::make_unique(serial); hw_type = get_hw_type(); @@ -83,194 +23,44 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { (hw_type == cereal::PandaState::PandaType::DOS); return; - -fail: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - cleanup(); - throw std::runtime_error("Error connecting to panda"); } -Panda::~Panda() { - std::lock_guard lk(usb_lock); - cleanup(); - connected = false; +bool Panda::connected() { + return handle->connected; } -void Panda::cleanup() { - if (dev_handle) { - libusb_release_interface(dev_handle, 0); - libusb_close(dev_handle); - } - - if (ctx) { - libusb_exit(ctx); - } +bool Panda::comms_healthy() { + return handle->comms_healthy; } std::vector Panda::list() { - // init libusb - ssize_t num_devices; - libusb_context *context = NULL; - libusb_device **dev_list = NULL; - std::vector serials; - - int err = init_usb_ctx(&context); - if (err != 0) { return serials; } - - num_devices = libusb_get_device_list(context, &dev_list); - if (num_devices < 0) { - LOGE("libusb can't get device list"); - goto finish; - } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device *device = dev_list[i]; - libusb_device_descriptor desc; - libusb_get_device_descriptor(device, &desc); - if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { - libusb_device_handle *handle = NULL; - int ret = libusb_open(device, &handle); - if (ret < 0) { goto finish; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - libusb_close(handle); - if (ret < 0) { goto finish; } - - serials.push_back(std::string((char *)desc_serial, ret).c_str()); - } - } - -finish: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - if (context) { - libusb_exit(context); - } - return serials; -} - -void Panda::handle_usb_issue(int err, const char func[]) { - LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func); - if (err == LIBUSB_ERROR_NO_DEVICE) { - LOGE("lost connection"); - connected = false; - } - // TODO: check other errors, is simply retrying okay? -} - -int Panda::usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(usb_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int Panda::usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(usb_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int Panda::usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(usb_lock); - do { - // Try sending can messages. If the receive buffer on the panda is full it will NAK - // and libusb will try again. After 5ms, it will time out. We will drop the messages. - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - LOGW("Transmit buffer full"); - break; - } else if (err != 0 || length != transferred) { - handle_usb_issue(err, __func__); - } - } while(err != 0 && connected); - - return transferred; -} - -int Panda::usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(usb_lock); - - do { - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - break; // timeout is okay to exit, recv still happened - } else if (err == LIBUSB_ERROR_OVERFLOW) { - comms_healthy = false; - LOGE_100("overflow got 0x%x", transferred); - } else if (err != 0) { - handle_usb_issue(err, __func__); - } - - } while(err != 0 && connected); - - return transferred; + return PandaUsbHandle::list(); } void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) { - usb_write(0xdc, (uint16_t)safety_model, safety_param); + handle->control_write(0xdc, (uint16_t)safety_model, safety_param); } void Panda::set_alternative_experience(uint16_t alternative_experience) { - usb_write(0xdf, alternative_experience, 0); + handle->control_write(0xdf, alternative_experience, 0); } cereal::PandaState::PandaType Panda::get_hw_type() { unsigned char hw_query[1] = {0}; - usb_read(0xc1, 0, 0, hw_query, 1); + handle->control_read(0xc1, 0, 0, hw_query, 1); return (cereal::PandaState::PandaType)(hw_query[0]); } void Panda::set_rtc(struct tm sys_time) { // tm struct has year defined as years since 1900 - usb_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0); - usb_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0); - usb_write(0xa3, (uint16_t)sys_time.tm_mday, 0); - // usb_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0); - usb_write(0xa5, (uint16_t)sys_time.tm_hour, 0); - usb_write(0xa6, (uint16_t)sys_time.tm_min, 0); - usb_write(0xa7, (uint16_t)sys_time.tm_sec, 0); + handle->control_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0); + handle->control_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0); + handle->control_write(0xa3, (uint16_t)sys_time.tm_mday, 0); + // handle->control_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0); + handle->control_write(0xa5, (uint16_t)sys_time.tm_hour, 0); + handle->control_write(0xa6, (uint16_t)sys_time.tm_min, 0); + handle->control_write(0xa7, (uint16_t)sys_time.tm_sec, 0); } struct tm Panda::get_rtc() { @@ -284,7 +74,7 @@ struct tm Panda::get_rtc() { uint8_t second; } rtc_time = {0}; - usb_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time)); + handle->control_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time)); struct tm new_time = { 0 }; new_time.tm_year = rtc_time.year - 1900; // tm struct has year defined as years since 1900 @@ -298,70 +88,70 @@ struct tm Panda::get_rtc() { } void Panda::set_fan_speed(uint16_t fan_speed) { - usb_write(0xb1, fan_speed, 0); + handle->control_write(0xb1, fan_speed, 0); } uint16_t Panda::get_fan_speed() { uint16_t fan_speed_rpm = 0; - usb_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm)); + handle->control_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm)); return fan_speed_rpm; } void Panda::set_ir_pwr(uint16_t ir_pwr) { - usb_write(0xb0, ir_pwr, 0); + handle->control_write(0xb0, ir_pwr, 0); } std::optional Panda::get_state() { health_t health {0}; - int err = usb_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health)); + int err = handle->control_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health)); return err >= 0 ? std::make_optional(health) : std::nullopt; } std::optional Panda::get_can_state(uint16_t can_number) { can_health_t can_health {0}; - int err = usb_read(0xc2, can_number, 0, (unsigned char*)&can_health, sizeof(can_health)); + int err = handle->control_read(0xc2, can_number, 0, (unsigned char*)&can_health, sizeof(can_health)); return err >= 0 ? std::make_optional(can_health) : std::nullopt; } void Panda::set_loopback(bool loopback) { - usb_write(0xe5, loopback, 0); + handle->control_write(0xe5, loopback, 0); } std::optional> Panda::get_firmware_version() { std::vector fw_sig_buf(128); - int read_1 = usb_read(0xd3, 0, 0, &fw_sig_buf[0], 64); - int read_2 = usb_read(0xd4, 0, 0, &fw_sig_buf[64], 64); + int read_1 = handle->control_read(0xd3, 0, 0, &fw_sig_buf[0], 64); + int read_2 = handle->control_read(0xd4, 0, 0, &fw_sig_buf[64], 64); return ((read_1 == 64) && (read_2 == 64)) ? std::make_optional(fw_sig_buf) : std::nullopt; } std::optional Panda::get_serial() { char serial_buf[17] = {'\0'}; - int err = usb_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16); + int err = handle->control_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16); return err >= 0 ? std::make_optional(serial_buf) : std::nullopt; } void Panda::set_power_saving(bool power_saving) { - usb_write(0xe7, power_saving, 0); + handle->control_write(0xe7, power_saving, 0); } void Panda::enable_deepsleep() { - usb_write(0xfb, 0, 0); + handle->control_write(0xfb, 0, 0); } void Panda::send_heartbeat(bool engaged) { - usb_write(0xf3, engaged, 0); + handle->control_write(0xf3, engaged, 0); } void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) { - usb_write(0xde, bus, (speed * 10)); + handle->control_write(0xde, bus, (speed * 10)); } void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) { - usb_write(0xf9, bus, (speed * 10)); + handle->control_write(0xf9, bus, (speed * 10)); } void Panda::set_canfd_non_iso(uint16_t bus, bool non_iso) { - usb_write(0xfc, bus, non_iso); + handle->control_write(0xfc, bus, non_iso); } static uint8_t len_to_dlc(uint8_t len) { @@ -399,8 +189,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data } auto can_data = cmsg.getDat(); uint8_t data_len_code = len_to_dlc(can_data.size()); - assert(can_data.size() <= ((hw_type == cereal::PandaState::PandaType::RED_PANDA || - hw_type == cereal::PandaState::PandaType::RED_PANDA_V2) ? 64 : 8)); + assert(can_data.size() <= 64); assert(can_data.size() == dlc_to_len[data_len_code]); can_header header; @@ -423,14 +212,14 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data void Panda::can_send(capnp::List::Reader can_data_list) { pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) { - usb_bulk_write(3, data, size, 5); + handle->bulk_write(3, data, size, 5); }); } bool Panda::can_receive(std::vector& out_vec) { uint8_t data[RECV_SIZE]; - int recv = usb_bulk_read(0x81, (uint8_t*)data, RECV_SIZE); - if (!comms_healthy) { + int recv = handle->bulk_read(0x81, (uint8_t*)data, RECV_SIZE); + if (!comms_healthy()) { return false; } if (recv == RECV_SIZE) { @@ -445,7 +234,7 @@ bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &o for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) { if (data[i] != i / USBPACKET_MAX_SIZE) { LOGE("CAN: MALFORMED USB RECV PACKET"); - comms_healthy = false; + handle->comms_healthy = false; return false; } int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i)); diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index c7a0e7a6c1..5b3cbb9a3e 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -1,26 +1,26 @@ #pragma once -#include #include #include #include #include -#include +#include #include #include -#include - #include "cereal/gen/cpp/car.capnp.h" #include "cereal/gen/cpp/log.capnp.h" #include "panda/board/health.h" +#include "selfdrive/boardd/panda_comms.h" + -#define TIMEOUT 0 #define PANDA_CAN_CNT 3 #define PANDA_BUS_CNT 4 -#define RECV_SIZE (0x4000U) + #define USB_TX_SOFT_LIMIT (0x100U) #define USBPACKET_MAX_SIZE (0x40) + +#define RECV_SIZE (0x4000U) #define CANPACKET_HEAD_SIZE 5U #define CANPACKET_MAX_SIZE 72U #define CANPACKET_REJECTED (0xC0U) @@ -37,41 +37,32 @@ struct __attribute__((packed)) can_header { }; struct can_frame { - long address; - std::string dat; - long busTime; - long src; + long address; + std::string dat; + long busTime; + long src; }; + class Panda { - private: - libusb_context *ctx = NULL; - libusb_device_handle *dev_handle = NULL; - std::mutex usb_lock; +private: + std::unique_ptr handle; std::vector recv_buf; - void handle_usb_issue(int err, const char func[]); - void cleanup(); - public: +public: Panda(std::string serial="", uint32_t bus_offset=0); - ~Panda(); - std::string usb_serial; - std::atomic connected = true; - std::atomic comms_healthy = true; + std::string hw_serial; cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN; bool has_rtc = false; const uint32_t bus_offset; + bool connected(); + bool comms_healthy(); + // Static functions static std::vector list(); - // HW communication - int usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout=TIMEOUT); - int usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout=TIMEOUT); - int usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - int usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - // Panda functionality cereal::PandaState::PandaType get_hw_type(); void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U); diff --git a/selfdrive/boardd/panda_comms.cc b/selfdrive/boardd/panda_comms.cc new file mode 100644 index 0000000000..e73cb69318 --- /dev/null +++ b/selfdrive/boardd/panda_comms.cc @@ -0,0 +1,232 @@ +#include "selfdrive/boardd/panda.h" + +#include +#include + +#include "common/swaglog.h" + +static int init_usb_ctx(libusb_context **context) { + assert(context != nullptr); + + int err = libusb_init(context); + if (err != 0) { + LOGE("libusb initialization error"); + return err; + } + +#if LIBUSB_API_VERSION >= 0x01000106 + libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); +#else + libusb_set_debug(*context, 3); +#endif + + return err; +} + +PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) { + // init libusb + ssize_t num_devices; + libusb_device **dev_list = NULL; + int err = init_usb_ctx(&ctx); + if (err != 0) { goto fail; } + + // connect by serial + num_devices = libusb_get_device_list(ctx, &dev_list); + if (num_devices < 0) { goto fail; } + for (size_t i = 0; i < num_devices; ++i) { + libusb_device_descriptor desc; + libusb_get_device_descriptor(dev_list[i], &desc); + if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { + int ret = libusb_open(dev_list[i], &dev_handle); + if (dev_handle == NULL || ret < 0) { goto fail; } + + unsigned char desc_serial[26] = { 0 }; + ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); + if (ret < 0) { goto fail; } + + auto hw_serial = std::string((char *)desc_serial, ret); + if (serial.empty() || serial == hw_serial) { + break; + } + libusb_close(dev_handle); + dev_handle = NULL; + } + } + if (dev_handle == NULL) goto fail; + libusb_free_device_list(dev_list, 1); + dev_list = nullptr; + + if (libusb_kernel_driver_active(dev_handle, 0) == 1) { + libusb_detach_kernel_driver(dev_handle, 0); + } + + err = libusb_set_configuration(dev_handle, 1); + if (err != 0) { goto fail; } + + err = libusb_claim_interface(dev_handle, 0); + if (err != 0) { goto fail; } + + return; + +fail: + if (dev_list != NULL) { + libusb_free_device_list(dev_list, 1); + } + cleanup(); + throw std::runtime_error("Error connecting to panda"); +} + +PandaUsbHandle::~PandaUsbHandle() { + std::lock_guard lk(hw_lock); + cleanup(); + connected = false; +} + +void PandaUsbHandle::cleanup() { + if (dev_handle) { + libusb_release_interface(dev_handle, 0); + libusb_close(dev_handle); + } + + if (ctx) { + libusb_exit(ctx); + } +} + +std::vector PandaUsbHandle::list() { + // init libusb + ssize_t num_devices; + libusb_context *context = NULL; + libusb_device **dev_list = NULL; + std::vector serials; + + int err = init_usb_ctx(&context); + if (err != 0) { return serials; } + + num_devices = libusb_get_device_list(context, &dev_list); + if (num_devices < 0) { + LOGE("libusb can't get device list"); + goto finish; + } + for (size_t i = 0; i < num_devices; ++i) { + libusb_device *device = dev_list[i]; + libusb_device_descriptor desc; + libusb_get_device_descriptor(device, &desc); + if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { + libusb_device_handle *handle = NULL; + int ret = libusb_open(device, &handle); + if (ret < 0) { goto finish; } + + unsigned char desc_serial[26] = { 0 }; + ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); + libusb_close(handle); + if (ret < 0) { goto finish; } + + serials.push_back(std::string((char *)desc_serial, ret).c_str()); + } + } + +finish: + if (dev_list != NULL) { + libusb_free_device_list(dev_list, 1); + } + if (context) { + libusb_exit(context); + } + return serials; +} + +void PandaUsbHandle::handle_usb_issue(int err, const char func[]) { + LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func); + if (err == LIBUSB_ERROR_NO_DEVICE) { + LOGE("lost connection"); + connected = false; + } + // TODO: check other errors, is simply retrying okay? +} + +int PandaUsbHandle::control_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) { + int err; + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; + + if (!connected) { + return LIBUSB_ERROR_NO_DEVICE; + } + + std::lock_guard lk(hw_lock); + do { + err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout); + if (err < 0) handle_usb_issue(err, __func__); + } while (err < 0 && connected); + + return err; +} + +int PandaUsbHandle::control_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { + int err; + const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; + + if (!connected) { + return LIBUSB_ERROR_NO_DEVICE; + } + + std::lock_guard lk(hw_lock); + do { + err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); + if (err < 0) handle_usb_issue(err, __func__); + } while (err < 0 && connected); + + return err; +} + +int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + int err; + int transferred = 0; + + if (!connected) { + return 0; + } + + std::lock_guard lk(hw_lock); + do { + // Try sending can messages. If the receive buffer on the panda is full it will NAK + // and libusb will try again. After 5ms, it will time out. We will drop the messages. + err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); + + if (err == LIBUSB_ERROR_TIMEOUT) { + LOGW("Transmit buffer full"); + break; + } else if (err != 0 || length != transferred) { + handle_usb_issue(err, __func__); + } + } while(err != 0 && connected); + + return transferred; +} + +int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + int err; + int transferred = 0; + + if (!connected) { + return 0; + } + + std::lock_guard lk(hw_lock); + + do { + err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); + + if (err == LIBUSB_ERROR_TIMEOUT) { + break; // timeout is okay to exit, recv still happened + } else if (err == LIBUSB_ERROR_OVERFLOW) { + comms_healthy = false; + LOGE_100("overflow got 0x%x", transferred); + } else if (err != 0) { + handle_usb_issue(err, __func__); + } + + } while(err != 0 && connected); + + return transferred; +} diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h new file mode 100644 index 0000000000..c5143b16b3 --- /dev/null +++ b/selfdrive/boardd/panda_comms.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#define TIMEOUT 0 + + +// comms base class +class PandaCommsHandle { +public: + PandaCommsHandle(std::string serial) {}; + virtual ~PandaCommsHandle() {}; + virtual void cleanup() = 0; + + std::atomic connected = true; + std::atomic comms_healthy = true; + static std::vector list(); + + // HW communication + virtual int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT) = 0; + virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0; + virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; + virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; + +protected: + std::mutex hw_lock; +}; + +class PandaUsbHandle : public PandaCommsHandle { +public: + PandaUsbHandle(std::string serial); + ~PandaUsbHandle(); + int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); + int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); + int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + void cleanup(); + + static std::vector list(); + +private: + libusb_context *ctx = NULL; + libusb_device_handle *dev_handle = NULL; + std::vector recv_buf; + void handle_usb_issue(int err, const char func[]); +}; diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index e948698cb4..58afed27eb 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -7,14 +7,15 @@ from enum import Enum from natsort import natsorted from typing import Dict, List +from cereal import car from common.basedir import BASEDIR from selfdrive.car import gen_empty_fingerprint -from selfdrive.car.docs_definitions import CarInfo, Column +from selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote from selfdrive.car.car_helpers import interfaces, get_interface_attr def get_all_footnotes() -> Dict[Enum, int]: - all_footnotes = [] + all_footnotes = list(CommonFootnote) for footnotes in get_interface_attr("Footnote", ignore_none=True).values(): all_footnotes.extend(footnotes) return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)} @@ -28,7 +29,7 @@ def get_all_car_info() -> List[CarInfo]: all_car_info: List[CarInfo] = [] footnotes = get_all_footnotes() for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): - CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), experimental_long=True) + CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")]) if CP.dashcamOnly or car_info is None: continue diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 015877dcb4..8e057a1563 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -68,7 +68,18 @@ class Harness(Enum): none = "None" -CarFootnote = namedtuple("CarFootnote", ["text", "column"], defaults=[None]) +CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only"], defaults=(None, False)) + + +class CommonFootnote(Enum): + EXP_LONG_AVAIL = CarFootnote( + "Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `master-ci`. " + + "Using openpilot longitudinal may disable Automatic Emergency Braking (AEB).", + Column.LONGITUDINAL, docs_only=True) + EXP_LONG_DSU = CarFootnote( + "When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace " + + "stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", + Column.LONGITUDINAL) def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]: @@ -128,11 +139,22 @@ class CarInfo: self.car_name = CP.carName self.car_fingerprint = CP.carFingerprint self.make, self.model, self.years = split_name(self.name) + + op_long = "Stock" + if CP.openpilotLongitudinalControl and not CP.enableDsu: + op_long = "openpilot" + elif CP.experimentalLongitudinalAvailable or CP.enableDsu: + op_long = "openpilot available" + if CP.enableDsu: + self.footnotes.append(CommonFootnote.EXP_LONG_DSU) + else: + self.footnotes.append(CommonFootnote.EXP_LONG_AVAIL) + self.row = { Column.MAKE: self.make, Column.MODEL: self.model, Column.PACKAGE: self.package, - Column.LONGITUDINAL: "openpilot" if CP.openpilotLongitudinalControl or CP.experimentalLongitudinalAvailable else "Stock", + Column.LONGITUDINAL: op_long, Column.FSR_LONGITUDINAL: f"{max(self.min_enable_speed * CV.MS_TO_MPH, 0):.0f} mph", Column.FSR_STEERING: f"{max(self.min_steer_speed * CV.MS_TO_MPH, 0):.0f} mph", Column.STEERING_TORQUE: Star.EMPTY, diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index a75eb89f25..a6cd2f19b9 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -5,10 +5,11 @@ from common.realtime import DT_CTRL from opendbc.can.packer import CANPacker from selfdrive.car import apply_std_steer_torque_limits from selfdrive.car.gm import gmcan -from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, EV_CAR +from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons VisualAlert = car.CarControl.HUDControl.VisualAlert NetworkLocation = car.CarParams.NetworkLocation +LongCtrlState = car.CarControl.Actuators.LongControlState # Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s CAMERA_CANCEL_DELAY_FRAMES = 10 @@ -30,7 +31,7 @@ class CarController: self.sent_lka_steering_cmd = False self.lka_icon_status_last = (False, False) - self.params = CarControllerParams() + self.params = CarControllerParams(self.CP) self.packer_pt = CANPacker(DBC[self.CP.carFingerprint]['pt']) self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) @@ -83,20 +84,23 @@ class CarController: self.apply_gas = self.params.INACTIVE_REGEN self.apply_brake = 0 else: - if self.CP.carFingerprint in EV_CAR: - self.apply_gas = int(round(interp(actuators.accel, self.params.EV_GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) - self.apply_brake = int(round(interp(actuators.accel, self.params.EV_BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) - else: - self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) - self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) + self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) + self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) idx = (self.frame // 4) % 4 at_full_stop = CC.longActive and CS.out.standstill near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE) + friction_brake_bus = CanBus.CHASSIS + # GM Camera exceptions + # TODO: can we always check the longControlState? + if self.CP.networkLocation == NetworkLocation.fwdCamera: + at_full_stop = at_full_stop and actuators.longControlState == LongCtrlState.stopping + friction_brake_bus = CanBus.POWERTRAIN + # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop)) - can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx, near_stop, at_full_stop)) + can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake, idx, CC.enabled, near_stop, at_full_stop, self.CP)) # Send dashboard UI commands (ACC status) send_fcw = hud_alert == VisualAlert.fcw diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 7cb2274674..af69307a2c 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -28,6 +28,7 @@ class CarState(CarStateBase): self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"] self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"]) + self.moving_backward = pt_cp.vl["EBCMWheelSpdRear"]["MovingBackward"] != 0 # Variables used for avoiding LKAS faults self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 @@ -139,6 +140,7 @@ class CarState(CarStateBase): ("FRWheelSpd", "EBCMWheelSpdFront"), ("RLWheelSpd", "EBCMWheelSpdRear"), ("RRWheelSpd", "EBCMWheelSpdRear"), + ("MovingBackward", "EBCMWheelSpdRear"), ("PRNDL2", "ECMPRDNL2"), ("ManualMode", "ECMPRDNL2"), ("LKADriverAppldTrq", "PSCMStatus"), diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index 42df2c6afd..56c981326f 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -1,4 +1,5 @@ from selfdrive.car import make_can_msg +from selfdrive.car.gm.values import CAR def create_buttons(packer, bus, idx, button): @@ -52,15 +53,20 @@ def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop): return packer.make_can_msg("ASCMGasRegenCmd", bus, values) -def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_full_stop): +def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_stop, at_full_stop, CP): mode = 0x1 + + # TODO: Understand this better. Volts and ICE Camera ACC cars are 0x1 when enabled with no brake + if enabled and CP.carFingerprint in (CAR.BOLT_EUV,): + mode = 0x9 + if apply_brake > 0: mode = 0xa if at_full_stop: mode = 0xd # TODO: this is to have GM bringing the car to complete stop, - # but currently it conflicts with OP controls, so turned off. + # but currently it conflicts with OP controls, so turned off. Not set by all cars #elif near_stop: # mode = 0xb diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index e25f203772..2420098b4a 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -20,8 +20,7 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D class CarInterface(CarInterfaceBase): @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed): - params = CarControllerParams() - return params.ACCEL_MIN, params.ACCEL_MAX + return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX # Determined by iteratively plotting and minimizing error for f(angle, speed) = steer. @staticmethod @@ -56,13 +55,34 @@ class CarInterface(CarInterfaceBase): else: ret.transmissionType = TransmissionType.automatic + ret.longitudinalTuning.deadzoneBP = [0.] + ret.longitudinalTuning.deadzoneV = [0.15] + + ret.longitudinalTuning.kpBP = [5., 35.] + ret.longitudinalTuning.kiBP = [0.] + if candidate in CAMERA_ACC_CAR: - ret.openpilotLongitudinalControl = False + ret.experimentalLongitudinalAvailable = True ret.networkLocation = NetworkLocation.fwdCamera ret.radarOffCan = True # no radar ret.pcmCruise = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM ret.minEnableSpeed = 5 * CV.KPH_TO_MS + + if experimental_long: + ret.pcmCruise = False + ret.openpilotLongitudinalControl = True + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM_LONG + + # Tuning + ret.longitudinalTuning.kpV = [2.0, 1.5] + ret.longitudinalTuning.kiV = [0.72] + ret.stopAccel = -2.0 + ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling + ret.vEgoStopping = 0.25 + ret.vEgoStarting = 0.25 + ret.longitudinalActuatorDelayUpperBound = 0.5 + else: # ASCM, OBD-II harness ret.openpilotLongitudinalControl = True ret.networkLocation = NetworkLocation.gateway @@ -71,6 +91,10 @@ class CarInterface(CarInterfaceBase): # supports stop and go, but initial engage must (conservatively) be above 18mph ret.minEnableSpeed = 18 * CV.MPH_TO_MS + # Tuning + ret.longitudinalTuning.kpV = [2.4, 1.5] + ret.longitudinalTuning.kiV = [0.36] + # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. @@ -85,11 +109,6 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 # Default delay, not measured yet tire_stiffness_factor = 0.444 # not optimized yet - ret.longitudinalTuning.kpBP = [5., 35.] - ret.longitudinalTuning.kpV = [2.4, 1.5] - ret.longitudinalTuning.kiBP = [0.] - ret.longitudinalTuning.kiV = [0.36] - ret.steerLimitTimer = 0.4 ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz @@ -206,8 +225,9 @@ class CarInterface(CarInterfaceBase): # Enabling at a standstill with brake is allowed # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs - if ret.vEgo < self.CP.minEnableSpeed and not (ret.standstill and ret.brake >= 20 and - self.CP.networkLocation == NetworkLocation.fwdCamera): + below_min_enable_speed = ret.vEgo < self.CP.minEnableSpeed or self.CS.moving_backward + if below_min_enable_speed and not (ret.standstill and ret.brake >= 20 and + self.CP.networkLocation == NetworkLocation.fwdCamera): events.add(EventName.belowEngageSpeed) if ret.cruiseState.standstill: events.add(EventName.resumeRequired) diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 85e291aaf6..eace6b6aca 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -24,15 +24,6 @@ class CarControllerParams: ADAS_KEEPALIVE_STEP = 100 CAMERA_KEEPALIVE_STEP = 100 - # Volt gas/brake lookups - # TODO: These values should be confirmed on non-Volt vehicles. - # MAX_GAS should achieve 2 m/s^2 and MAX_BRAKE with regen should achieve -4.0 m/s^2 - MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. - ZERO_GAS = 2048 # Coasting - MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen - MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen - INACTIVE_REGEN = 1404 - # Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we # perform the closed loop control, and might need some # to apply some more braking if we're on a downhill slope. @@ -41,15 +32,32 @@ class CarControllerParams: ACCEL_MAX = 2. # m/s^2 ACCEL_MIN = -4. # m/s^2 - # ICE has much less engine braking force compared to regen in EVs, - # lower threshold removes some braking deadzone - GAS_LOOKUP_BP = [-0.1, 0., ACCEL_MAX] - EV_GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX] - GAS_LOOKUP_V = [MAX_ACC_REGEN, ZERO_GAS, MAX_GAS] - - BRAKE_LOOKUP_BP = [ACCEL_MIN, -0.1] - EV_BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.] - BRAKE_LOOKUP_V = [MAX_BRAKE, 0.] + def __init__(self, CP): + # Gas/brake lookups + self.ZERO_GAS = 2048 # Coasting + self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen + + if CP.carFingerprint in CAMERA_ACC_CAR: + self.MAX_GAS = 3400 + self.MAX_ACC_REGEN = 1514 + self.INACTIVE_REGEN = 1554 + # Camera ACC vehicles have no regen while enabled. + # Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly + max_regen_acceleration = 0. + + else: + self.MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. + self.MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen + self.INACTIVE_REGEN = 1404 + # ICE has much less engine braking force compared to regen in EVs, + # lower threshold removes some braking deadzone + max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1 + + self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX] + self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS] + + self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration] + self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.] class CAR: diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 3cce581868..7cf1515fda 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -21,8 +21,9 @@ class CarState(CarStateBase): self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) + self.gear_msg_canfd = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER" if CP.carFingerprint in CANFD_CAR: - self.shifter_values = can_define.dv["GEAR_SHIFTER"]["GEAR"] + self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"] elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]: self.shifter_values = can_define.dv["CLU15"]["CF_Clu_Gear"] elif self.CP.carFingerprint in FEATURES["use_tcu_gears"]: @@ -154,17 +155,21 @@ class CarState(CarStateBase): def update_canfd(self, cp, cp_cam): ret = car.CarState.new_message() - if self.CP.carFingerprint in EV_CAR: - ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. - elif self.CP.carFingerprint in HYBRID_CAR: - ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023. - ret.gasPressed = ret.gas > 1e-5 + if self.CP.carFingerprint in (EV_CAR | HYBRID_CAR): + if self.CP.carFingerprint in EV_CAR: + ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. + else: + ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023. + ret.gasPressed = ret.gas > 1e-5 + else: + ret.gasPressed = bool(cp.vl["ACCELERATOR_BRAKE_ALT"]["ACCELERATOR_PEDAL_PRESSED"]) + ret.brakePressed = cp.vl["TCS"]["DriverBraking"] == 1 ret.doorOpen = cp.vl["DOORS_SEATBELTS"]["DRIVER_DOOR_OPEN"] == 1 ret.seatbeltUnlatched = cp.vl["DOORS_SEATBELTS"]["DRIVER_SEATBELT_LATCHED"] == 0 - gear = cp.vl["GEAR_SHIFTER"]["GEAR"] + gear = cp.vl[self.gear_msg_canfd]["GEAR"] ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) # TODO: figure out positions @@ -411,13 +416,14 @@ class CarState(CarStateBase): def get_can_parser_canfd(CP): cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" + gear_msg = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER" signals = [ ("WHEEL_SPEED_1", "WHEEL_SPEEDS"), ("WHEEL_SPEED_2", "WHEEL_SPEEDS"), ("WHEEL_SPEED_3", "WHEEL_SPEEDS"), ("WHEEL_SPEED_4", "WHEEL_SPEEDS"), - ("GEAR", "GEAR_SHIFTER"), + ("GEAR", gear_msg), ("STEERING_RATE", "STEERING_SENSORS"), ("STEERING_ANGLE", "STEERING_SENSORS"), @@ -442,8 +448,7 @@ class CarState(CarStateBase): checks = [ ("WHEEL_SPEEDS", 100), - ("GEAR_SHIFTER", 100), - ("BRAKE", 100), + (gear_msg, 100), ("STEERING_SENSORS", 100), ("MDPS", 100), ("TCS", 50), @@ -486,6 +491,13 @@ class CarState(CarStateBase): checks += [ ("ACCELERATOR_ALT", 100), ] + else: + signals += [ + ("ACCELERATOR_PEDAL_PRESSED", "ACCELERATOR_BRAKE_ALT"), + ] + checks += [ + ("ACCELERATOR_BRAKE_ALT", 100), + ] bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 4b4d51f3f1..6c8d0076f1 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -41,6 +41,9 @@ class CarInterface(CarInterfaceBase): # non-HDA2 if 0x1cf not in fingerprint[4]: ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value + # ICE cars do not have 0x130; GEARS message on 0x40 instead + if 0x130 not in fingerprint[4]: + ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value ret.steerActuatorDelay = 0.1 # Default delay ret.steerLimitTimer = 0.4 @@ -120,6 +123,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.756 ret.steerRatio = 16. tire_stiffness_factor = 0.385 + elif candidate == CAR.SANTA_CRUZ_1ST_GEN: + ret.mass = 1870. + STD_CARGO_KG # weight from Limited trim - the only supported trim + ret.wheelbase = 3.000 + ret.steerRatio = 14.2 # steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf # Kia elif candidate == CAR.KIA_SORENTO: @@ -138,6 +145,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.63 ret.steerRatio = 14.56 tire_stiffness_factor = 1 + elif candidate == CAR.KIA_SPORTAGE_5TH_GEN: + ret.mass = 1700. + STD_CARGO_KG # weight from SX and above trims, average of FWD and AWD versions + ret.wheelbase = 2.756 + ret.steerRatio = 13.6 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H): ret.mass = 3558. * CV.LB_TO_KG ret.wheelbase = 2.80 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index c760c724a9..536af7cf01 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -47,6 +47,7 @@ class CarControllerParams: class HyundaiFlags(IntFlag): CANFD_HDA2 = 1 CANFD_ALT_BUTTONS = 2 + CANFD_ALT_GEARS = 4 class CAR: @@ -77,6 +78,7 @@ class CAR: SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" IONIQ_5 = "HYUNDAI IONIQ 5 2022" TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN" + SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN" # Kia KIA_FORTE = "KIA FORTE E 2018 & GT 2021" @@ -88,6 +90,7 @@ class CAR: KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT" KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" KIA_SELTOS = "KIA SELTOS 2021" + KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN" KIA_SORENTO = "KIA SORENTO GT LINE 2018" KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" @@ -143,9 +146,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), CAR.IONIQ_5: [ HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022" , "Highway Driving Assist", harness=Harness.hyundai_k), - HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_q), + HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q), ], CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), + CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2021-22", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n), # Kia CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g), @@ -168,6 +172,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Optima Hybrid 2019"), ], CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a), + CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n), CAR.KIA_SORENTO: [ HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), @@ -1052,6 +1057,7 @@ FW_VERSIONS = { b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', + b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', # TODO: these return from the MULTI request, above return from LONG b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xee\xff\xe0\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xffP\xf5\xff\xfd\x00\x00\x00\x00\xfc\x00\x01', b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xdb\x00\x00\x00\x00\xff\xb1\xff\xd9\xff\xd2\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xff\xd6\xf5\x00\x06\x00\x00\x00\x14\xfd\x00\x04', @@ -1061,10 +1067,12 @@ FW_VERSIONS = { b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', ], (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', @@ -1376,6 +1384,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', ], }, CAR.TUCSON_HYBRID_4TH_GEN: { @@ -1395,6 +1404,24 @@ FW_VERSIONS = { b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', ], }, + CAR.SANTA_CRUZ_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ', + ], + }, + CAR.KIA_SPORTAGE_5TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662', + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ', + b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', + ], + }, } CHECKSUM = { @@ -1412,7 +1439,7 @@ FEATURES = { "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } @@ -1469,5 +1496,7 @@ DBC = { CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), + CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 5c6d214bcc..bf949d3492 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -82,6 +82,7 @@ routes = [ CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80), + CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN), CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE), CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022), CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), @@ -117,6 +118,7 @@ routes = [ CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021), 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("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), @@ -172,7 +174,8 @@ routes = [ CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1), CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1), - CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), + CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), # Stock ACC + CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.GOLF_MK7), # openpilot longitudinal CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7), CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8), CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS), diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py index b7056df5b3..e56f98f7a8 100755 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -10,8 +10,9 @@ from selfdrive.car.honda.values import CAR as HONDA class TestCarDocs(unittest.TestCase): - def setUp(self): - self.all_cars = get_all_car_info() + @classmethod + def setUpClass(cls): + cls.all_cars = get_all_car_info() def test_generator(self): generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 460b9a9097..c5a316aaaf 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -31,6 +31,8 @@ CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0] +HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0] +KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.0] KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0] # Dashcam or fallback configured as ideal car diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 5c828fa669..c2ebadfc7a 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -25,6 +25,7 @@ HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] HONDA PASSPORT 2021: [1.5305538930036766, 0.7956063674638759, 0.19599407381531284] HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] +HYUNDAI ELANTRA 2021: [3.169, 2.1259108157250735, 0.0819] HYUNDAI GENESIS 2015-2016: [1.8466226943929824, 1.5552063647830634, 0.0984484465421171] HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index acd7526003..c043c9d455 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -35,7 +35,6 @@ 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 ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 -HYUNDAI ELANTRA 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 GENESIS G90 2017: GENESIS G70 2018 diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index faea08ed3f..4ec9500171 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -5,7 +5,8 @@ from selfdrive.car.toyota.toyotacan import create_steer_command, create_ui_comma create_accel_command, create_acc_cancel_command, \ create_fcw_command, create_lta_steer_command from selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ - MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams + MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams, \ + UNSUPPORTED_DSU_CAR from opendbc.can.packer import CANPacker VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -112,7 +113,7 @@ class CarController: lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged # Lexus IS uses a different cancellation message - if pcm_cancel_cmd and self.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): + if pcm_cancel_cmd and self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: can_sends.append(create_acc_cancel_command(self.packer)) elif self.CP.openpilotLongitudinalControl: can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type)) diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 0cfba2b09f..4758149916 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -6,7 +6,7 @@ from common.realtime import DT_CTRL from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE +from selfdrive.car.toyota.values import ToyotaFlags, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR class CarState(CarStateBase): @@ -87,7 +87,7 @@ class CarState(CarStateBase): # 17 is a fault from a prolonged high torque delta between cmd and user ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] == 17 - if self.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): + if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_ALT"]["UI_SET_SPEED"] @@ -112,7 +112,7 @@ class CarState(CarStateBase): # these cars are identified by an ACC_TYPE value of 2. # TODO: it is possible to avoid the lockout and gain stop and go if you # send your own ACC_CONTROL msg on startup with ACC_TYPE set to 1 - if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in (CAR.LEXUS_IS, CAR.LEXUS_RC)) or \ + if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR) or \ (self.CP.carFingerprint in TSS2_CAR and self.acc_type == 1): self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 @@ -196,7 +196,7 @@ class CarState(CarStateBase): signals.append(("GAS_PEDAL", "GAS_PEDAL")) checks.append(("GAS_PEDAL", 33)) - if CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): + if CP.carFingerprint in UNSUPPORTED_DSU_CAR: signals.append(("MAIN_ON", "DSU_CRUISE")) signals.append(("SET_SPEED", "DSU_CRUISE")) signals.append(("UI_SET_SPEED", "PCM_CRUISE_ALT")) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 0d5acbfff4..02e4caa9d6 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -2,7 +2,7 @@ from cereal import car from common.conversions import Conversions as CV from panda import Panda -from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, CarControllerParams +from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, CarControllerParams, NO_STOP_TIMER_CAR from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase @@ -189,13 +189,13 @@ class CarInterface(CarInterfaceBase): smartDsu = 0x2FF in fingerprint[0] # In TSS2 cars the camera does long control found_ecus = [fw.ecu for fw in car_fw] - ret.enableDsu = (len(found_ecus) > 0) and (Ecu.dsu not in found_ecus) and (candidate not in NO_DSU_CAR) and (not smartDsu) + ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) and not smartDsu ret.enableGasInterceptor = 0x201 in fingerprint[0] # if the smartDSU is detected, openpilot can send ACC_CMD (and the smartDSU will block it from the DSU) or not (the DSU is "connected") ret.openpilotLongitudinalControl = smartDsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) + ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR if not ret.openpilotLongitudinalControl: - ret.autoResumeSng = False ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL # we can't use the fingerprint to detect this reliably, since @@ -208,18 +208,16 @@ class CarInterface(CarInterfaceBase): ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptor) else MIN_ACC_SPEED tune = ret.longitudinalTuning + tune.deadzoneBP = [0., 9.] + tune.deadzoneV = [.0, .15] if candidate in TSS2_CAR or ret.enableGasInterceptor: - tune.deadzoneBP = [0., 8.05] - tune.deadzoneV = [.0, .14] tune.kpBP = [0., 5., 20.] tune.kpV = [1.3, 1.0, 0.7] tune.kiBP = [0., 5., 12., 20., 27.] tune.kiV = [.35, .23, .20, .17, .1] if candidate in TSS2_CAR: - ret.stoppingDecelRate = 0.3 # reach stopping target smoothly + ret.stoppingDecelRate = 0.3 # reach stopping target smoothly else: - tune.deadzoneBP = [0., 9.] - tune.deadzoneV = [.0, .15] tune.kpBP = [0., 5., 35.] tune.kiBP = [0., 35.] tune.kpV = [3.6, 2.4, 1.5] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 07c33d0173..9e96118085 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -87,10 +87,6 @@ class CAR: class Footnote(Enum): - DSU = CarFootnote( - "When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace " + - "stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", - Column.LONGITUDINAL) CAMRY = CarFootnote( "openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.", Column.FSR_LONGITUDINAL) @@ -107,11 +103,11 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.ALPHARD_TSS2: ToyotaCarInfo("Toyota Alphard 2019-20"), CAR.ALPHARDH_TSS2: ToyotaCarInfo("Toyota Alphard Hybrid 2021"), CAR.AVALON: [ - ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Toyota Avalon 2017-18", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P"), + ToyotaCarInfo("Toyota Avalon 2017-18"), ], - CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21", footnotes=[Footnote.DSU]), - CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21", footnotes=[Footnote.DSU]), + 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]), @@ -120,7 +116,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-22"), CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-21"), CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-19"), - CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19", footnotes=[Footnote.DSU]), + CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), CAR.COROLLA_TSS2: [ ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-21", min_enable_speed=7.5), @@ -131,53 +127,53 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), ToyotaCarInfo("Lexus UX Hybrid 2019-22"), ], - CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo", footnotes=[Footnote.DSU]), + CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"), - CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19", footnotes=[Footnote.DSU]), + CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-22"), CAR.PRIUS: [ - ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0", [Footnote.DSU]), - ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0"), + ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), + ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), ], - CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED, footnotes=[Footnote.DSU]), + CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED), CAR.PRIUS_TSS2: [ ToyotaCarInfo("Toyota Prius 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), ToyotaCarInfo("Toyota Prius Prime 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), ], CAR.RAV4: [ - ToyotaCarInfo("Toyota RAV4 2016", "Toyota Safety Sense P", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Toyota RAV4 2017-18", footnotes=[Footnote.DSU]) + ToyotaCarInfo("Toyota RAV4 2016", "Toyota Safety Sense P"), + ToyotaCarInfo("Toyota RAV4 2017-18") ], CAR.RAV4H: [ - ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", "https://youtu.be/LhT5VzJVfNI?t=26", [Footnote.DSU]), - ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26", footnotes=[Footnote.DSU]) + ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", "https://youtu.be/LhT5VzJVfNI?t=26"), + ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26") ], CAR.RAV4_TSS2: ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), CAR.RAV4_TSS2_2022: ToyotaCarInfo("Toyota RAV4 2022"), CAR.RAV4H_TSS2: ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"), CAR.RAV4H_TSS2_2022: ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"), - CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", footnotes=[Footnote.DSU], min_enable_speed=MIN_ACC_SPEED), + CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED), # Lexus - CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+", footnotes=[Footnote.DSU]), - CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "Lexus Safety System+", footnotes=[Footnote.DSU]), + CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+"), + CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "Lexus Safety System+"), CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-22"), CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22", video_link="https://youtu.be/BZ29osRVJeg?t=12"), CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), - CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19", footnotes=[Footnote.DSU]), - CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19", footnotes=[Footnote.DSU]), + CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19"), + CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19"), CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"), CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"), CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-20"), CAR.LEXUS_RX: [ - ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Lexus RX 2017-19", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+"), + ToyotaCarInfo("Lexus RX 2017-19"), ], CAR.LEXUS_RXH: [ - ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Lexus RX Hybrid 2017-19", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+"), + ToyotaCarInfo("Lexus RX Hybrid 2017-19"), ], CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"), CAR.LEXUS_RXH_TSS2: ToyotaCarInfo("Lexus RX Hybrid 2020-21"), @@ -1363,7 +1359,7 @@ FW_VERSIONS = { ], (Ecu.engine, 0x700, None): [ b'\x01896634AA0000\x00\x00\x00\x00', - b'\x01896634AA0100\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', @@ -2032,6 +2028,9 @@ TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.COROLLA_TSS2, CAR.COROLLAH_TS NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} +# 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} diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 5dc4543c0d..d09420cf7a 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -105,6 +105,7 @@ class CarState(CarStateBase): ret.stockAeb = bool(ext_cp.vl["ACC_10"]["ANB_Teilbremsung_Freigabe"]) or bool(ext_cp.vl["ACC_10"]["ANB_Zielbremsung_Freigabe"]) # Update ACC radar status. + self.acc_type = ext_cp.vl["ACC_06"]["ACC_Typ"] if pt_cp.vl["TSK_06"]["TSK_Status"] == 2: # ACC okay and enabled, but not currently engaged ret.cruiseState.available = True @@ -480,11 +481,13 @@ class MqbExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ ("ACC_Wunschgeschw_02", "ACC_02"), # ACC set speed + ("ACC_Typ", "ACC_06"), # Basic vs F2S vs SNG ("AWV2_Freigabe", "ACC_10"), # FCW brake jerk release ("ANB_Teilbremsung_Freigabe", "ACC_10"), # AEB partial braking release ("ANB_Zielbremsung_Freigabe", "ACC_10"), # AEB target braking release ] fwd_radar_checks = [ + ("ACC_06", 50), # From J428 ACC radar control module ("ACC_10", 50), # From J428 ACC radar control module ("ACC_02", 17), # From J428 ACC radar control module ] diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 870f3ab163..816e7fcf34 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -27,19 +27,20 @@ class CarInterface(CarInterfaceBase): ret.carName = "volkswagen" ret.radarOffCan = True + use_off_car_defaults = len(fingerprint[0]) == 0 # Pick sensible carParams during offline doc generation/CI jobs + if candidate in PQ_CARS: # Set global PQ35/PQ46/NMS parameters ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.volkswagenPq)] ret.enableBsm = 0x3BA in fingerprint[0] # SWA_1 - if 0x440 in fingerprint[0]: # Getriebe_1 + if 0x440 in fingerprint[0] or use_off_car_defaults: # Getriebe_1 ret.transmissionType = TransmissionType.automatic else: ret.transmissionType = TransmissionType.manual if any(msg in fingerprint[1] for msg in (0x1A0, 0xC2)): # Bremse_1, Lenkwinkel_1 ret.networkLocation = NetworkLocation.gateway - ret.experimentalLongitudinalAvailable = True else: ret.networkLocation = NetworkLocation.fwdCamera @@ -56,7 +57,7 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.volkswagen)] ret.enableBsm = 0x30F in fingerprint[0] # SWA_01 - if 0xAD in fingerprint[0]: # Getriebe_11 + if 0xAD in fingerprint[0] or use_off_car_defaults: # Getriebe_11 ret.transmissionType = TransmissionType.automatic elif 0x187 in fingerprint[0]: # EV_Gearshift ret.transmissionType = TransmissionType.direct @@ -82,7 +83,8 @@ class CarInterface(CarInterfaceBase): # Global longitudinal tuning defaults, can be overridden per-vehicle - if experimental_long and candidate in PQ_CARS: + ret.experimentalLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or use_off_car_defaults + if experimental_long: # Proof-of-concept, prep for E2E only. No radar points available. Panda ALLOW_DEBUG firmware required. ret.openpilotLongitudinalControl = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_VOLKSWAGEN_LONG_CONTROL @@ -90,7 +92,11 @@ class CarInterface(CarInterfaceBase): ret.minEnableSpeed = 4.5 ret.pcmCruise = not ret.openpilotLongitudinalControl - ret.longitudinalActuatorDelayUpperBound = 0.5 # s + ret.stoppingControl = True + ret.startingState = True + ret.startAccel = 1.0 + ret.vEgoStarting = 1.0 + ret.vEgoStopping = 1.0 ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py index 3819f4f76f..25a710dbb8 100644 --- a/selfdrive/car/volkswagen/mqbcan.py +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -36,3 +36,73 @@ def create_acc_buttons_control(packer, bus, gra_stock_values, counter, cancel=Fa }) return packer.make_can_msg("GRA_ACC_01", bus, values) + + +def acc_control_value(main_switch_on, acc_faulted, long_active): + if acc_faulted: + acc_control = 6 + elif long_active: + acc_control = 3 + elif main_switch_on: + acc_control = 2 + else: + acc_control = 0 + + return acc_control + + +def acc_hud_status_value(main_switch_on, acc_faulted, long_active): + # TODO: happens to resemble the ACC control value for now, but extend this for init/gas override later + return acc_control_value(main_switch_on, acc_faulted, long_active) + + +def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, stopping, starting, esp_hold): + commands = [] + + acc_06_values = { + "ACC_Typ": acc_type, + "ACC_Status_ACC": acc_control, + "ACC_StartStopp_Info": enabled, + "ACC_Sollbeschleunigung_02": accel if enabled else 3.01, + "ACC_zul_Regelabw_unten": 0.2, # TODO: dynamic adjustment of comfort-band + "ACC_zul_Regelabw_oben": 0.2, # TODO: dynamic adjustment of comfort-band + "ACC_neg_Sollbeschl_Grad_02": 4.0 if enabled else 0, # TODO: dynamic adjustment of jerk limits + "ACC_pos_Sollbeschl_Grad_02": 4.0 if enabled else 0, # TODO: dynamic adjustment of jerk limits + "ACC_Anfahren": starting, + "ACC_Anhalten": stopping, + } + commands.append(packer.make_can_msg("ACC_06", bus, acc_06_values)) + + if starting: + acc_hold_type = 4 # hold release / startup + elif esp_hold: + acc_hold_type = 3 # hold standby + elif stopping: + acc_hold_type = 1 # hold request + else: + acc_hold_type = 0 + + acc_07_values = { + "ACC_Anhalteweg": 0.75 if stopping else 20.46, # Distance to stop (stopping coordinator handles terminal roll-out) + "ACC_Freilauf_Info": 2 if enabled else 0, + "ACC_Folgebeschl": 3.02, # Not using secondary controller accel unless and until we understand its impact + "ACC_Sollbeschleunigung_02": accel if enabled else 3.01, + "ACC_Anforderung_HMS": acc_hold_type, + "ACC_Anfahren": starting, + "ACC_Anhalten": stopping, + } + commands.append(packer.make_can_msg("ACC_07", bus, acc_07_values)) + + return commands + + +def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_visible): + values = { + "ACC_Status_Anzeige": acc_hud_status, + "ACC_Wunschgeschw_02": set_speed if set_speed < 250 else 327.36, + "ACC_Gesetzte_Zeitluecke": 3, + "ACC_Display_Prio": 3, + # TODO: ACC_Abstandsindex for lead car distance, must determine analog vs digital cluster for scaling + } + + return packer.make_can_msg("ACC_02", bus, values) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 695868452d..babaffbcbe 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,5 +1,5 @@ from collections import defaultdict, namedtuple -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Union @@ -151,75 +151,74 @@ class Footnote(Enum): PASSAT = CarFootnote( "Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.", Column.MODEL) - VW_HARNESS = CarFootnote( - "Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma " + - "store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black " + - "(older design) or light brown (newer design). In the interim, if your car has a J533 connector CAN gateway " + - "inside the dashboard, choose \"VW J533 Development\" from the vehicle drop-down for a suitable harness. " + - "(Some newer models are also observed to not have a J533 connector.)", - Column.MODEL) - VW_VARIANT = CarFootnote( - "Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)", - Column.MODEL) + VW_EXP_LONG = CarFootnote ( + "Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness " + + "are limited to using stock ACC.", + Column.LONGITUDINAL) + VW_MQB_A0 = CarFootnote( + "Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot " + + "in software, but doesn't yet have a harness available from the comma store.", + Column.HARNESS) @dataclass class VWCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC) & Lane Assist" - harness: Enum = Harness.vw + harness: Enum = Harness.j533 + footnotes: List[Enum] = field(default_factory=lambda: [Footnote.VW_EXP_LONG]) CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.ARTEON_MK1: [ - VWCarInfo("Volkswagen Arteon 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon R 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon eHybrid 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen CC 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon R 2020-22", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon eHybrid 2020-22", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), ], CAR.ATLAS_MK1: [ - VWCarInfo("Volkswagen Atlas 2018-23", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Atlas Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Teramont 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Teramont Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Teramont X 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Atlas 2018-23"), + VWCarInfo("Volkswagen Atlas Cross Sport 2021-22"), + VWCarInfo("Volkswagen Teramont 2018-22"), + VWCarInfo("Volkswagen Teramont Cross Sport 2021-22"), + VWCarInfo("Volkswagen Teramont X 2021-22"), ], CAR.GOLF_MK7: [ VWCarInfo("Volkswagen e-Golf 2014-20"), - VWCarInfo("Volkswagen Golf 2015-20", footnotes=[Footnote.VW_VARIANT]), + VWCarInfo("Volkswagen Golf 2015-20"), VWCarInfo("Volkswagen Golf Alltrack 2015-19"), VWCarInfo("Volkswagen Golf GTD 2015-20"), VWCarInfo("Volkswagen Golf GTE 2015-20"), VWCarInfo("Volkswagen Golf GTI 2015-21"), - VWCarInfo("Volkswagen Golf R 2015-19", footnotes=[Footnote.VW_VARIANT]), + VWCarInfo("Volkswagen Golf R 2015-19"), VWCarInfo("Volkswagen Golf SportsVan 2015-20"), ], CAR.JETTA_MK7: [ - VWCarInfo("Volkswagen Jetta 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Jetta GLI 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Jetta 2018-22"), + VWCarInfo("Volkswagen Jetta GLI 2021-22"), ], CAR.PASSAT_MK8: [ - VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.PASSAT, Footnote.VW_VARIANT], harness=Harness.j533), - VWCarInfo("Volkswagen Passat Alltrack 2015-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Passat GTE 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533), + VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.PASSAT]), + VWCarInfo("Volkswagen Passat Alltrack 2015-22"), + VWCarInfo("Volkswagen Passat GTE 2015-22"), ], - CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22", harness=Harness.j533), + CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22"), CAR.POLO_MK6: [ - VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), ], CAR.SHARAN_MK2: [ - VWCarInfo("Volkswagen Sharan 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("SEAT Alhambra 2018-20", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Sharan 2018-22"), + VWCarInfo("SEAT Alhambra 2018-20"), ], - CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022"), + CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22"), CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2017"), CAR.TRANSPORTER_T61: [ - VWCarInfo("Volkswagen Caravelle 2020", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen California 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Caravelle 2020"), + VWCarInfo("Volkswagen California 2021"), ], - CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), CAR.AUDI_A3_MK3: [ VWCarInfo("Audi A3 2014-19"), VWCarInfo("Audi A3 Sportback e-tron 2017-18"), @@ -227,13 +226,13 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Audi S3 2015-17"), ], CAR.AUDI_Q2_MK1: VWCarInfo("Audi Q2 2018"), - CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2020-21"), + CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2019-23"), CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"), CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"), - CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.KAMIQ]), - CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21", footnotes=[Footnote.VW_HARNESS]), + CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0, Footnote.KAMIQ]), + CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"), - CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020"), + CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-18"), CAR.SKODA_OCTAVIA_MK3: [ VWCarInfo("Škoda Octavia 2015, 2018-19"), @@ -872,20 +871,24 @@ FW_VERSIONS = { CAR.AUDI_Q3_MK2: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705E906018N \xf1\x899970', + b'\xf1\x8705L906022M \xf1\x890901', b'\xf1\x8783A906259 \xf1\x890001', b'\xf1\x8783A906259 \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158CN\xf1\x893608', + b'\xf1\x870GC300045D \xf1\x892802', b'\xf1\x870GC300046F \xf1\x892701', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655BF\xf1\x890403\xf1\x82\x1321211111211200311121232152219321422111', + b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111', b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000800', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571G60533A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572R \xf1\x890372', diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index db5bf4d3e6..92a4f1f99b 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -103,6 +103,7 @@ class LongControl: elif self.long_control_state == LongCtrlState.stopping: if output_accel > self.CP.stopAccel: + output_accel = min(output_accel, 0.0) output_accel -= self.CP.stoppingDecelRate * DT_CTRL self.reset(CS.vEgo) diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index e156af5d64..8d9e247655 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -270,15 +270,22 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)); bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK); - if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) { + // quectel gps verticalAccuracy is clipped to 500 + bool gps_accuracy_insane_quectel = false; + if (!ublox_available) { + gps_accuracy_insane_quectel = log.getVerticalAccuracy() == 500; + } + + if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane || gps_accuracy_insane_quectel) { + this->gps_valid = false; this->determine_gps_mode(current_time); return; } - + double sensor_time = current_time - sensor_time_offset; // Process message - this->last_gps_fix = sensor_time; + this->gps_valid = true; this->gps_mode = true; Geodetic geodetic = { log.getLatitude(), log.getLongitude(), log.getAltitude() }; this->converter = std::make_unique(geodetic); @@ -476,9 +483,8 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build return msg_builder.toBytes(); } - bool Localizer::isGpsOK() { - return this->kf->get_filter_time() - this->last_gps_fix < 1.0; + return this->gps_valid; } void Localizer::determine_gps_mode(double current_time) { @@ -498,8 +504,10 @@ void Localizer::determine_gps_mode(double current_time) { } int Localizer::locationd_thread() { + ublox_available = Params().getBool("UbloxAvailable", true); + const char* gps_location_socket; - if (Params().getBool("UbloxAvailable", true)) { + if (ublox_available) { gps_location_socket = "gpsLocationExternal"; } else { gps_location_socket = "gpsLocation"; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index 280296b06c..d6bb5347c5 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -68,8 +68,9 @@ private: std::unique_ptr converter; int64_t unix_timestamp_millis = 0; - double last_gps_fix = 0; double reset_tracker = 0.0; bool device_fell = false; bool gps_mode = false; + bool gps_valid = false; + bool ublox_available = true; }; diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 86672b0460..9bd4ed0837 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -92,7 +92,7 @@ class ParamsLearner: self.speed = msg.vEgo in_linear_region = abs(self.steering_angle) < 45 or not self.steering_pressed - self.active = self.speed > 5 and in_linear_region + self.active = self.speed > 1 and in_linear_region if self.active: self.kf.predict_and_observe(t, ObservationKind.STEER_ANGLE, np.array([[math.radians(msg.steeringAngleDeg)]])) diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 3f63fbb959..dbccb8d4a9 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -31,6 +31,7 @@ procs = [ NativeProcess("encoderd", "selfdrive/loggerd", ["./encoderd"]), NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), + # NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"]), NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC), NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)), NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)), diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index a2dda5da9d..48163dcf2a 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -44,6 +44,7 @@ class DRIVER_MONITOR_SETTINGS(): self._POSE_YAW_THRESHOLD_SLACK = 0.5042 self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD self._PITCH_NATURAL_OFFSET = 0.029 # initial value before offset is learned + self._PITCH_NATURAL_THRESHOLD = 0.449 self._YAW_NATURAL_OFFSET = 0.097 # initial value before offset is learned self._PITCH_MAX_OFFSET = 0.124 self._PITCH_MIN_OFFSET = -0.0881 @@ -197,7 +198,7 @@ class DriverStatus(): self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit yaw_error = abs(yaw_error) - if pitch_error > self.settings._POSE_PITCH_THRESHOLD*self.pose.cfactor_pitch or \ + if pitch_error > (self.settings._POSE_PITCH_THRESHOLD*self.pose.cfactor_pitch if self.pose_calibrated else self.settings._PITCH_NATURAL_THRESHOLD) or \ yaw_error > self.settings._POSE_YAW_THRESHOLD*self.pose.cfactor_yaw: distracted_types.append(DistractedType.DISTRACTED_POSE) diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index f85916a4ca..daf89b2636 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -1,5 +1,6 @@ #include "selfdrive/navd/map_renderer.h" +#include #include #include #include @@ -10,11 +11,34 @@ #include "selfdrive/ui/qt/maps/map_helpers.h" const float DEFAULT_ZOOM = 13.5; // Don't go below 13 or features will start to disappear -const int WIDTH = 512; -const int HEIGHT = WIDTH; - +const int RENDER_HEIGHT = 512, RENDER_WIDTH = 512; +const int HEIGHT = 256, WIDTH = 256; const int NUM_VIPC_BUFFERS = 4; +const int EARTH_CIRCUMFERENCE_METERS = 40075000; +const int PIXELS_PER_TILE = 256; + +float get_meters_per_pixel(float lat, float zoom) { + float num_tiles = pow(2, zoom+1); + float meters_per_tile = cos(DEG2RAD(lat)) * EARTH_CIRCUMFERENCE_METERS / num_tiles; + return meters_per_tile / PIXELS_PER_TILE; +} + +float get_zoom_level_for_scale(float lat, float meters_per_pixel) { + float meters_per_tile = meters_per_pixel * PIXELS_PER_TILE; + float num_tiles = cos(DEG2RAD(lat)) * EARTH_CIRCUMFERENCE_METERS / meters_per_tile; + return log2(num_tiles) - 1; +} + +void downsample(uint8_t *src, uint8_t *dst) { + for (int r = 0; r < HEIGHT; r++) { + for (int c = 0; c < WIDTH; c++) { + dst[r*WIDTH + c] = src[(r*2*RENDER_WIDTH + c*2) * 3]; + } + } +} + + MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_settings(settings) { QSurfaceFormat fmt; fmt.setRenderableType(QSurfaceFormat::OpenGLES); @@ -35,7 +59,7 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set gl_functions->initializeOpenGLFunctions(); QOpenGLFramebufferObjectFormat fbo_format; - fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); + fbo.reset(new QOpenGLFramebufferObject(RENDER_WIDTH, RENDER_HEIGHT, fbo_format)); std::string style = util::read_file(STYLE_PATH); m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); @@ -45,7 +69,7 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set m_map->resize(fbo->size()); m_map->setFramebufferObject(fbo->handle(), fbo->size()); - gl_functions->glViewport(0, 0, WIDTH, HEIGHT); + gl_functions->glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT); if (online) { vipc_server.reset(new VisionIpcServer("navd")); @@ -85,22 +109,18 @@ void MapRenderer::msgUpdate() { } } -void MapRenderer::updateZoom(float zoom) { - if (m_map.isNull()) { - return; - } - - m_map->setZoom(zoom); - update(); -} - void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { if (m_map.isNull()) { return; } + // Choose a zoom level that matches the scale of zoom level 13 at latitude 80deg + float scale_lat80 = get_meters_per_pixel(80, 13); + float zoom = get_zoom_level_for_scale(position.first, scale_lat80); + m_map->setCoordinate(position); m_map->setBearing(bearing); + m_map->setZoom(zoom); update(); } @@ -130,15 +150,13 @@ void MapRenderer::sendVipc() { .timestamp_eof = ts, }; - assert(cap.sizeInBytes() >= buf->len); + assert(cap.sizeInBytes() >= buf->len*4); uint8_t* dst = (uint8_t*)buf->addr; uint8_t* src = cap.bits(); - // RGB to greyscale + // 2x downsample + rgb to grayscale memset(dst, 128, buf->len); - for (int i = 0; i < WIDTH * HEIGHT; i++) { - dst[i] = src[i * 3]; - } + downsample(src, dst); vipc_server->send(buf, &extra); @@ -169,9 +187,8 @@ uint8_t* MapRenderer::getImage() { uint8_t* src = cap.bits(); uint8_t* dst = new uint8_t[WIDTH * HEIGHT]; - for (int i = 0; i < WIDTH * HEIGHT; i++) { - dst[i] = src[i * 3]; - } + // 2x downsample + rgb to grayscale + downsample(src, dst); return dst; } @@ -222,11 +239,6 @@ extern "C" { return new MapRenderer(settings, false); } - void map_renderer_update_zoom(MapRenderer *inst, float zoom) { - inst->updateZoom(zoom); - QApplication::processEvents(); - } - void map_renderer_update_position(MapRenderer *inst, float lat, float lon, float bearing) { inst->updatePosition({lat, lon}, bearing); QApplication::processEvents(); diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index 921d871632..855dc91894 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -47,7 +47,6 @@ private: QTimer* timer; public slots: - void updateZoom(float zoom); void updatePosition(QMapbox::Coordinate position, float bearing); void updateRoute(QList coordinates); void msgUpdate(); diff --git a/selfdrive/navd/map_renderer.py b/selfdrive/navd/map_renderer.py index 079bb028ce..9000622928 100755 --- a/selfdrive/navd/map_renderer.py +++ b/selfdrive/navd/map_renderer.py @@ -9,7 +9,7 @@ from cffi import FFI from common.ffi_wrapper import suffix from common.basedir import BASEDIR -HEIGHT = WIDTH = 512 +HEIGHT = WIDTH = 256 def get_ffi(): @@ -18,7 +18,6 @@ def get_ffi(): ffi = FFI() ffi.cdef(""" void* map_renderer_init(char *maps_host, char *token); -void map_renderer_update_zoom(void *inst, float zoom); void map_renderer_update_position(void *inst, float lat, float lon, float bearing); void map_renderer_update_route(void *inst, char *polyline); void map_renderer_update(void *inst); diff --git a/selfdrive/sensord/sensors/lsm6ds3_accel.cc b/selfdrive/sensord/sensors/lsm6ds3_accel.cc index 27cd4d0c70..c19e3de7ed 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_accel.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_accel.cc @@ -1,17 +1,132 @@ #include "lsm6ds3_accel.h" #include +#include +#include #include "common/swaglog.h" #include "common/timing.h" +#include "common/util.h" LSM6DS3_Accel::LSM6DS3_Accel(I2CBus *bus, int gpio_nr, bool shared_gpio) : I2CSensor(bus, gpio_nr, shared_gpio) {} +void LSM6DS3_Accel::wait_for_data_ready() { + uint8_t drdy = 0; + uint8_t buffer[6]; + + do { + read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_ACCEL_DRDY_XLDA; + } while (drdy == 0); + + read_register(LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL, buffer, sizeof(buffer)); +} + +void LSM6DS3_Accel::read_and_avg_data(float* out_buf) { + uint8_t drdy = 0; + uint8_t buffer[6]; + + float scaling = 0.061f; + if (source == cereal::SensorEventData::SensorSource::LSM6DS3TRC) { + scaling = 0.122f; + } + + for (int i = 0; i < 5; i++) { + do { + read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_ACCEL_DRDY_XLDA; + } while (drdy == 0); + + int len = read_register(LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL, buffer, sizeof(buffer)); + assert(len == sizeof(buffer)); + + for (int j = 0; j < 3; j++) { + out_buf[j] += (float)read_16_bit(buffer[j*2], buffer[j*2+1]) * scaling; + } + } + + for (int i = 0; i < 3; i++) { + out_buf[i] /= 5.0f; + } +} + +int LSM6DS3_Accel::self_test(int test_type) { + float val_st_off[3] = {0}; + float val_st_on[3] = {0}; + float test_val[3] = {0}; + uint8_t ODR_FS_MO = LSM6DS3_ACCEL_ODR_52HZ; // full scale: +-2g, ODR: 52Hz + + // prepare sensor for self-test + + // enable block data update and automatic increment + int ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL3_C, LSM6DS3_ACCEL_IF_INC_BDU); + if (ret < 0) { + return ret; + } + + if (source == cereal::SensorEventData::SensorSource::LSM6DS3TRC) { + ODR_FS_MO = LSM6DS3_ACCEL_FS_4G | LSM6DS3_ACCEL_ODR_52HZ; + } + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, ODR_FS_MO); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(100); + wait_for_data_ready(); + read_and_avg_data(val_st_off); + + // enable Self Test positive (or negative) + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL5_C, test_type); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(100); + wait_for_data_ready(); + read_and_avg_data(val_st_on); + + // disable sensor + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, 0); + if (ret < 0) { + return ret; + } + + // disable self test + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL5_C, 0); + if (ret < 0) { + return ret; + } + + // calculate the mg values for self test + for (int i = 0; i < 3; i++) { + test_val[i] = fabs(val_st_on[i] - val_st_off[i]); + } + + // verify test result + for (int i = 0; i < 3; i++) { + if ((LSM6DS3_ACCEL_MIN_ST_LIMIT_mg > test_val[i]) || + (test_val[i] > LSM6DS3_ACCEL_MAX_ST_LIMIT_mg)) { + return -1; + } + } + + return ret; +} + int LSM6DS3_Accel::init() { int ret = 0; uint8_t buffer[1]; uint8_t value = 0; + bool do_self_test = false; + + const char* env_lsm_selftest =env_lsm_selftest = std::getenv("LSM_SELF_TEST"); + if (env_lsm_selftest != nullptr && strncmp(env_lsm_selftest, "1", 1) == 0) { + do_self_test = true; + } ret = read_register(LSM6DS3_ACCEL_I2C_REG_ID, buffer, 1); if(ret < 0) { @@ -29,11 +144,29 @@ int LSM6DS3_Accel::init() { source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; } + ret = self_test(LSM6DS3_ACCEL_POSITIVE_TEST); + if (ret < 0) { + LOGE("LSM6DS3 accel positive self-test failed!"); + if (do_self_test) goto fail; + } + + ret = self_test(LSM6DS3_ACCEL_NEGATIVE_TEST); + if (ret < 0) { + LOGE("LSM6DS3 accel negative self-test failed!"); + if (do_self_test) goto fail; + } + ret = init_gpio(); if (ret < 0) { goto fail; } + // enable continuous update, and automatic increase + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL3_C, LSM6DS3_ACCEL_IF_INC); + if (ret < 0) { + goto fail; + } + // TODO: set scale and bandwidth. Default is +- 2G, 50 Hz ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, LSM6DS3_ACCEL_ODR_104HZ); if (ret < 0) { diff --git a/selfdrive/sensord/sensors/lsm6ds3_accel.h b/selfdrive/sensord/sensors/lsm6ds3_accel.h index 84084fc916..c3f66f5803 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_accel.h +++ b/selfdrive/sensord/sensors/lsm6ds3_accel.h @@ -10,21 +10,37 @@ #define LSM6DS3_ACCEL_I2C_REG_ID 0x0F #define LSM6DS3_ACCEL_I2C_REG_INT1_CTRL 0x0D #define LSM6DS3_ACCEL_I2C_REG_CTRL1_XL 0x10 +#define LSM6DS3_ACCEL_I2C_REG_CTRL3_C 0x12 +#define LSM6DS3_ACCEL_I2C_REG_CTRL5_C 0x14 +#define LSM6DS3_ACCEL_I2C_REG_CTR9_XL 0x18 #define LSM6DS3_ACCEL_I2C_REG_STAT_REG 0x1E #define LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL 0x28 // Constants #define LSM6DS3_ACCEL_CHIP_ID 0x69 #define LSM6DS3TRC_ACCEL_CHIP_ID 0x6A +#define LSM6DS3_ACCEL_FS_4G (0b10 << 2) +#define LSM6DS3_ACCEL_ODR_52HZ (0b0011 << 4) #define LSM6DS3_ACCEL_ODR_104HZ (0b0100 << 4) #define LSM6DS3_ACCEL_INT1_DRDY_XL 0b1 #define LSM6DS3_ACCEL_DRDY_XLDA 0b1 #define LSM6DS3_ACCEL_DRDY_PULSE_MODE (1 << 7) - +#define LSM6DS3_ACCEL_IF_INC 0b00000100 +#define LSM6DS3_ACCEL_IF_INC_BDU 0b01000100 +#define LSM6DS3_ACCEL_XYZ_DEN 0b11100000 +#define LSM6DS3_ACCEL_POSITIVE_TEST 0b01 +#define LSM6DS3_ACCEL_NEGATIVE_TEST 0b10 +#define LSM6DS3_ACCEL_MIN_ST_LIMIT_mg 90.0f +#define LSM6DS3_ACCEL_MAX_ST_LIMIT_mg 1700.0f class LSM6DS3_Accel : public I2CSensor { uint8_t get_device_address() {return LSM6DS3_ACCEL_I2C_ADDR;} cereal::SensorEventData::SensorSource source = cereal::SensorEventData::SensorSource::LSM6DS3; + + // self test functions + int self_test(int test_type); + void wait_for_data_ready(); + void read_and_avg_data(float* val_st_off); public: LSM6DS3_Accel(I2CBus *bus, int gpio_nr = 0, bool shared_gpio = false); int init(); diff --git a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc b/selfdrive/sensord/sensors/lsm6ds3_gyro.cc index 014a72bb73..f306be0fe8 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_gyro.cc @@ -2,19 +2,120 @@ #include #include +#include #include "common/swaglog.h" #include "common/timing.h" +#include "common/util.h" #define DEG2RAD(x) ((x) * M_PI / 180.0) LSM6DS3_Gyro::LSM6DS3_Gyro(I2CBus *bus, int gpio_nr, bool shared_gpio) : I2CSensor(bus, gpio_nr, shared_gpio) {} +void LSM6DS3_Gyro::wait_for_data_ready() { + uint8_t drdy = 0; + uint8_t buffer[6]; + + do { + read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_GYRO_DRDY_GDA; + } while (drdy == 0); + + read_register(LSM6DS3_GYRO_I2C_REG_OUTX_L_G, buffer, sizeof(buffer)); +} + +void LSM6DS3_Gyro::read_and_avg_data(float* out_buf) { + uint8_t drdy = 0; + uint8_t buffer[6]; + + for (int i = 0; i < 5; i++) { + do { + read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_GYRO_DRDY_GDA; + } while (drdy == 0); + + int len = read_register(LSM6DS3_GYRO_I2C_REG_OUTX_L_G, buffer, sizeof(buffer)); + assert(len == sizeof(buffer)); + + for (int j = 0; j < 3; j++) { + out_buf[j] += (float)read_16_bit(buffer[j*2], buffer[j*2+1]) * 70.0f; + } + } + + // calculate the mg average values + for (int i = 0; i < 3; i++) { + out_buf[i] /= 5.0f; + } +} + +int LSM6DS3_Gyro::self_test(int test_type) { + float val_st_off[3] = {0}; + float val_st_on[3] = {0}; + float test_val[3] = {0}; + + // prepare sensor for self-test + + // full scale: 2000dps, ODR: 208Hz + int ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, LSM6DS3_GYRO_ODR_208HZ | LSM6DS3_GYRO_FS_2000dps); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(150); + wait_for_data_ready(); + read_and_avg_data(val_st_off); + + // enable Self Test positive (or negative) + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL5_C, test_type); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(50); + wait_for_data_ready(); + read_and_avg_data(val_st_on); + + // disable sensor + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, 0); + if (ret < 0) { + return ret; + } + + // disable self test + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL5_C, 0); + if (ret < 0) { + return ret; + } + + // calculate the mg values for self test + for (int i = 0; i < 3; i++) { + test_val[i] = fabs(val_st_on[i] - val_st_off[i]); + } + + // verify test result + for (int i = 0; i < 3; i++) { + if ((LSM6DS3_GYRO_MIN_ST_LIMIT_mdps > test_val[i]) || + (test_val[i] > LSM6DS3_GYRO_MAX_ST_LIMIT_mdps)) { + return -1; + } + } + + return ret; +} + int LSM6DS3_Gyro::init() { int ret = 0; uint8_t buffer[1]; uint8_t value = 0; + bool do_self_test = false; + + const char* env_lsm_selftest =env_lsm_selftest = std::getenv("LSM_SELF_TEST"); + if (env_lsm_selftest != nullptr && strncmp(env_lsm_selftest, "1", 1) == 0) { + do_self_test = true; + } ret = read_register(LSM6DS3_GYRO_I2C_REG_ID, buffer, 1); if(ret < 0) { @@ -37,6 +138,18 @@ int LSM6DS3_Gyro::init() { goto fail; } + ret = self_test(LSM6DS3_GYRO_POSITIVE_TEST); + if (ret < 0 ) { + LOGE("LSM6DS3 gyro positive self-test failed!"); + if (do_self_test) goto fail; + } + + ret = self_test(LSM6DS3_GYRO_NEGATIVE_TEST); + if (ret < 0) { + LOGE("LSM6DS3 gyro negative self-test failed!"); + if (do_self_test) goto fail; + } + // TODO: set scale. Default is +- 250 deg/s ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, LSM6DS3_GYRO_ODR_104HZ); if (ret < 0) { @@ -65,7 +178,7 @@ fail: int LSM6DS3_Gyro::shutdown() { int ret = 0; - // disable data ready interrupt for accel on INT1 + // disable data ready interrupt for gyro on INT1 uint8_t value = 0; ret = read_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, &value, 1); if (ret < 0) { diff --git a/selfdrive/sensord/sensors/lsm6ds3_gyro.h b/selfdrive/sensord/sensors/lsm6ds3_gyro.h index 6c61ffcef2..220e6b0cec 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_gyro.h +++ b/selfdrive/sensord/sensors/lsm6ds3_gyro.h @@ -10,21 +10,33 @@ #define LSM6DS3_GYRO_I2C_REG_ID 0x0F #define LSM6DS3_GYRO_I2C_REG_INT1_CTRL 0x0D #define LSM6DS3_GYRO_I2C_REG_CTRL2_G 0x11 +#define LSM6DS3_GYRO_I2C_REG_CTRL5_C 0x14 #define LSM6DS3_GYRO_I2C_REG_STAT_REG 0x1E #define LSM6DS3_GYRO_I2C_REG_OUTX_L_G 0x22 +#define LSM6DS3_GYRO_POSITIVE_TEST (0b01 << 2) +#define LSM6DS3_GYRO_NEGATIVE_TEST (0b11 << 2) // Constants #define LSM6DS3_GYRO_CHIP_ID 0x69 #define LSM6DS3TRC_GYRO_CHIP_ID 0x6A +#define LSM6DS3_GYRO_FS_2000dps (0b11 << 2) #define LSM6DS3_GYRO_ODR_104HZ (0b0100 << 4) +#define LSM6DS3_GYRO_ODR_208HZ (0b0101 << 4) #define LSM6DS3_GYRO_INT1_DRDY_G 0b10 #define LSM6DS3_GYRO_DRDY_GDA 0b10 #define LSM6DS3_GYRO_DRDY_PULSE_MODE (1 << 7) +#define LSM6DS3_GYRO_MIN_ST_LIMIT_mdps 150000.0f +#define LSM6DS3_GYRO_MAX_ST_LIMIT_mdps 700000.0f class LSM6DS3_Gyro : public I2CSensor { uint8_t get_device_address() {return LSM6DS3_GYRO_I2C_ADDR;} cereal::SensorEventData::SensorSource source = cereal::SensorEventData::SensorSource::LSM6DS3; + + // self test functions + int self_test(int test_type); + void wait_for_data_ready(); + void read_and_avg_data(float* val_st_off); public: LSM6DS3_Gyro(I2CBus *bus, int gpio_nr = 0, bool shared_gpio = false); int init(); diff --git a/selfdrive/sensord/tests/test_sensord.py b/selfdrive/sensord/tests/test_sensord.py index 82bd28446b..c6fe33129a 100755 --- a/selfdrive/sensord/tests/test_sensord.py +++ b/selfdrive/sensord/tests/test_sensord.py @@ -104,6 +104,9 @@ class TestSensord(unittest.TestCase): # make sure gpiochip0 is readable HARDWARE.initialize_hardware() + # enable LSM self test + os.environ["LSM_SELF_TEST"] = "1" + # read initial sensor values every test case can use os.system("pkill -f ./_sensord") try: @@ -118,6 +121,8 @@ class TestSensord(unittest.TestCase): @classmethod def tearDownClass(cls): managed_processes["sensord"].stop() + if "LSM_SELF_TEST" in os.environ: + del os.environ['LSM_SELF_TEST'] def tearDown(self): managed_processes["sensord"].stop() diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 0e4ac94784..8bc13f37d4 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -fc3a044c567a8702ed1500d745170c365dd6b3d4 \ No newline at end of file +24a8d02b148b7f6d20f641d56a7bed71c244b6e3 \ No newline at end of file diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 0d3d7b367a..4021e27de3 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -21,7 +21,7 @@ from tools.lib.logreader import LogReader # Baseline CPU usage by process PROCS = { - "selfdrive.controls.controlsd": 35.0, + "selfdrive.controls.controlsd": 39.0, "./loggerd": 10.0, "./encoderd": 17.0, "./camerad": 14.5, @@ -202,9 +202,9 @@ class TestOnroad(unittest.TestCase): print(result) self.assertGreater(len(ts), 20*50, "insufficient samples") - self.assertLess(max(ts), 30.) + #self.assertLess(max(ts), 30.) self.assertLess(np.mean(ts), 10.) - self.assertLess(np.std(ts), 5.) + #self.assertLess(np.std(ts), 5.) def test_cpu_usage(self): proclogs = [m for m in self.lr if m.which() == 'procLog'] diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index 13697adfb5..d69d67edeb 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -314,7 +314,7 @@ void WifiUI::refresh() { QPushButton *forgetBtn = new QPushButton(tr("FORGET")); forgetBtn->setObjectName("forgetBtn"); QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(network.ssid)), this)) { + if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(network.ssid)), tr("Forget"), this)) { wifi->forgetConnection(network.ssid); } }); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index ae878c5419..51b5ce6bd7 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -27,49 +27,56 @@ #include "selfdrive/ui/qt/widgets/input.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { - // param, title, desc, icon - std::vector> toggle_defs{ + // param, title, desc, icon, confirm + std::vector> toggle_defs{ { "OpenpilotEnabledToggle", tr("Enable openpilot"), tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), "../assets/offroad/icon_openpilot.png", + false, }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), "../assets/offroad/icon_warning.png", + false, }, { "IsMetric", tr("Use Metric System"), tr("Display speed in km/h instead of mph."), "../assets/offroad/icon_metric.png", + false, }, { "RecordFront", tr("Record and Upload Driver Camera"), tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), "../assets/offroad/icon_monitoring.png", + false, }, { "DisengageOnAccelerator", tr("Disengage On Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), "../assets/offroad/icon_disengage_on_accelerator.svg", + false, }, { "EndToEndLong", tr("🌮 End-to-end longitudinal (extremely alpha) 🌮"), "", "../assets/offroad/icon_road.png", + false, }, { "ExperimentalLongitudinalEnabled", tr("Experimental openpilot longitudinal control"), tr("WARNING: openpilot longitudinal control is experimental for this car and will disable AEB."), "../assets/offroad/icon_speed_limit.png", + true, }, #ifdef ENABLE_MAPS { @@ -77,19 +84,20 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Show ETA in 24h Format"), tr("Use 24h format instead of am/pm"), "../assets/offroad/icon_metric.png", + false, }, { "NavSettingLeftSide", tr("Show Map on Left Side of UI"), tr("Show map on left side when in split screen view."), "../assets/offroad/icon_road.png", + false, }, #endif - }; - for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + for (auto &[param, title, desc, icon, confirm] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, confirm, this); bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); @@ -161,7 +169,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), ""); connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription); connect(resetCalibBtn, &ButtonControl::clicked, [&]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), tr("Reset"), this)) { params.remove("CalibrationParams"); } }); @@ -170,7 +178,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { if (!params.getBool("Passive")) { auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); connect(retrainingBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { emit reviewTrainingGuide(); } }); @@ -181,7 +189,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), ""); connect(regulatoryBtn, &ButtonControl::clicked, [=]() { const std::string txt = util::read_file("../assets/offroad/fcc.html"); - RichTextDialog::alert(QString::fromStdString(txt), this); + ConfirmationDialog::rich(QString::fromStdString(txt), this); }); addItem(regulatoryBtn); } @@ -258,7 +266,7 @@ void DevicePanel::updateCalibDescription() { void DevicePanel::reboot() { if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), tr("Reboot"), this)) { // Check engaged again in case it changed while the dialog was open if (!uiState()->engaged()) { Params().putBool("DoReboot", true); @@ -271,7 +279,7 @@ void DevicePanel::reboot() { void DevicePanel::poweroff() { if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) { // Check engaged again in case it changed while the dialog was open if (!uiState()->engaged()) { Params().putBool("DoShutdown", true); diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index e08a02a4d6..12d62e63fb 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -77,7 +77,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { // uninstall button auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL")); connect(uninstallBtn, &ButtonControl::clicked, [&]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), tr("Uninstall"), this)) { params.putBool("DoUninstall", true); } }); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index d3bc931b67..c4e4beb76a 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -170,7 +170,7 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { } -AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { +AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), accel_filter(UI_FREQ, .5, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { pm = std::make_unique>({"uiDebug"}); engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); @@ -463,19 +463,18 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { if (scene.end_to_end_long) { const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration(); float acceleration_future = 0; - if (acceleration.getZ().size() > 16) { - acceleration_future = acceleration.getX()[16]; // 2.5 seconds + if (acceleration.getZ().size() > 10 && (*s->sm)["carControl"].getCarControl().getLongActive()) { + acceleration_future = acceleration.getX()[10]; // 1.0 second } - start_hue = 60; - // speed up: 120, slow down: 0 - end_hue = fmax(fmin(start_hue + acceleration_future * 30, 120), 0); + // speed up: 148, slow down: 0 + start_hue = fmax(fmin(60 + accel_filter.update(acceleration_future) * 80, 148), 0); // FIXME: painter.drawPolygon can be slow if hue is not rounded - end_hue = int(end_hue * 100 + 0.5) / 100; + start_hue = int(start_hue * 100 + 0.5) / 100; bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.97, 0.56, 0.4)); - bg.setColorAt(0.5, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.35)); - bg.setColorAt(1.0, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.0)); + bg.setColorAt(0.75, QColor::fromHslF(63 / 360., 1.0, 0.68, 0.35)); + bg.setColorAt(1.0, QColor::fromHslF(63 / 360., 1.0, 0.68, 0.0)); } else { const auto &orientation = (*s->sm)["modelV2"].getModelV2().getOrientation(); float orientation_future = 0; diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 7edca6b3d5..1f6a49bf8c 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -87,6 +87,7 @@ protected: double prev_draw_t = 0; FirstOrderFilter fps_filter; + FirstOrderFilter accel_filter; }; // container for all onroad widgets diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index f11f9baf59..243c078f85 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -7,6 +7,7 @@ #include #include "common/params.h" +#include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/toggle.h" QFrame *horizontal_line(QWidget *parent = nullptr); @@ -49,6 +50,10 @@ public: value->setText(val); } + const QString getDescription() { + return description->text(); + } + public slots: void showDescription() { description->setVisible(true); @@ -134,10 +139,17 @@ class ParamControl : public ToggleControl { Q_OBJECT public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { + ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool confirm, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { - params.putBool(key, state); + QString content("

" + title + "



" + "

" + getDescription() + "

"); + ConfirmationDialog dialog(content, tr("Ok"), tr("Cancel"), true, this); + if (!confirm || !state || dialog.exec()) { + params.putBool(key, state); + } else { + toggle.togglePosition(); + } }); } diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index d703825885..75453e1b90 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -183,17 +183,21 @@ void InputDialog::setMinLength(int length) { // ConfirmationDialog ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, - QWidget *parent) : QDialogBase(parent) { + const bool rich, QWidget *parent) : QDialogBase(parent) { QFrame *container = new QFrame(this); - container->setStyleSheet("QFrame { border-radius: 0; background-color: #ECECEC; }"); + container->setStyleSheet(R"( + QFrame { background-color: #1B1B1B; color: #C9C9C9; } + #confirm_btn { background-color: #465BEA; } + #confirm_btn:pressed { background-color: #3049F4; } + )"); QVBoxLayout *main_layout = new QVBoxLayout(container); - main_layout->setContentsMargins(32, 120, 32, 32); + main_layout->setContentsMargins(32, rich ? 32 : 120, 32, 32); QLabel *prompt = new QLabel(prompt_text, this); prompt->setWordWrap(true); - prompt->setAlignment(Qt::AlignHCenter); - prompt->setStyleSheet("font-size: 70px; font-weight: bold; color: black;"); - main_layout->addWidget(prompt, 1, Qt::AlignTop | Qt::AlignHCenter); + prompt->setAlignment(rich ? Qt::AlignLeft : Qt::AlignHCenter); + prompt->setStyleSheet((rich ? "font-size: 42px; font-weight: light;" : "font-size: 70px; font-weight: bold;") + QString(" margin: 45px;")); + main_layout->addWidget(rich ? (QWidget*)new ScrollView(prompt, this) : (QWidget*)prompt, 1, Qt::AlignTop); // cancel + confirm buttons QHBoxLayout *btn_layout = new QHBoxLayout(); @@ -208,54 +212,29 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString if (confirm_text.length()) { QPushButton* confirm_btn = new QPushButton(confirm_text); + confirm_btn->setObjectName("confirm_btn"); btn_layout->addWidget(confirm_btn); QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept); } QVBoxLayout *outer_layout = new QVBoxLayout(this); - outer_layout->setContentsMargins(210, 170, 210, 170); + int margin = rich ? 100 : 200; + outer_layout->setContentsMargins(margin, margin, margin, margin); outer_layout->addWidget(container); } bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", parent); + ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", false, parent); return d.exec(); } -bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), tr("Cancel"), parent); +bool ConfirmationDialog::confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent) { + ConfirmationDialog d = ConfirmationDialog(prompt_text, confirm_text, tr("Cancel"), false, parent); return d.exec(); } - -// RichTextDialog - -RichTextDialog::RichTextDialog(const QString &prompt_text, const QString &btn_text, - QWidget *parent) : QDialogBase(parent) { - QFrame *container = new QFrame(this); - container->setStyleSheet("QFrame { background-color: #1B1B1B; }"); - QVBoxLayout *main_layout = new QVBoxLayout(container); - main_layout->setContentsMargins(32, 32, 32, 32); - - QLabel *prompt = new QLabel(prompt_text, this); - prompt->setWordWrap(true); - prompt->setAlignment(Qt::AlignLeft); - prompt->setTextFormat(Qt::RichText); - prompt->setStyleSheet("font-size: 42px; font-weight: light; color: #C9C9C9; margin: 45px;"); - main_layout->addWidget(new ScrollView(prompt, this), 1, Qt::AlignTop); - - // confirm button - QPushButton* confirm_btn = new QPushButton(btn_text); - main_layout->addWidget(confirm_btn); - QObject::connect(confirm_btn, &QPushButton::clicked, this, &QDialog::accept); - - QVBoxLayout *outer_layout = new QVBoxLayout(this); - outer_layout->setContentsMargins(100, 100, 100, 100); - outer_layout->addWidget(container); -} - -bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) { - auto d = RichTextDialog(prompt_text, tr("Ok"), parent); +bool ConfirmationDialog::rich(const QString &prompt_text, QWidget *parent) { + ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", true, parent); return d.exec(); } diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index 6c47a31d87..e6c0fba86d 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -55,18 +55,10 @@ class ConfirmationDialog : public QDialogBase { public: explicit ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, - const QString &cancel_text, QWidget* parent); - static bool alert(const QString &prompt_text, QWidget *parent); - static bool confirm(const QString &prompt_text, QWidget *parent); -}; - -// larger ConfirmationDialog for rich text -class RichTextDialog : public QDialogBase { - Q_OBJECT - -public: - explicit RichTextDialog(const QString &prompt_text, const QString &btn_text, QWidget* parent); + const QString &cancel_text, const bool rich, QWidget* parent); static bool alert(const QString &prompt_text, QWidget *parent); + static bool confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent); + static bool rich(const QString &prompt_text, QWidget *parent); }; class MultiOptionDialog : public QDialogBase { diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index a11333a54d..5ee2d86756 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -238,6 +238,14 @@ Disengage to Power Off openpilot をキャンセルしてシャットダウンができます + + Reset + + + + Review + + DriveStats @@ -463,6 +471,17 @@ location set 「connect.comma.ai」をホーム画面に追加して、アプリのように使うことができます。 + + ParamControl + + Ok + OK + + + Cancel + キャンセル + + PrimeAdWidget @@ -585,13 +604,6 @@ location set 「data」パーティションをマウントできません。「確認」ボタンを押すとデバイスが初期化されます。 - - RichTextDialog - - Ok - OK - - SettingsWindow @@ -850,6 +862,10 @@ location set CHECK 確認 + + Uninstall + + SshControl @@ -1052,5 +1068,9 @@ location set Forget Wi-Fi Network "%1"? Wi-Fiネットワーク%1を削除してもよろしいですか? + + Forget + + diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 021243595e..397b43f545 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -238,6 +238,14 @@ Disengage to Power Off 전원을 종료하려면 해제하세요 + + Reset + + + + Review + + DriveStats @@ -463,6 +471,17 @@ location set connect.comma.ai을 앱처럼 사용하려면 홈 화면에 바로가기를 만드십시오 + + ParamControl + + Ok + 확인 + + + Cancel + 취소 + + PrimeAdWidget @@ -585,13 +604,6 @@ location set 데이터 파티션을 마운트할 수 없습니다. 확인 버튼을 눌러 장치를 리셋합니다. - - RichTextDialog - - Ok - 확인 - - SettingsWindow @@ -850,6 +862,10 @@ location set CHECK 확인 + + Uninstall + + SshControl @@ -1052,5 +1068,9 @@ location set Forget Wi-Fi Network "%1"? wifi 네트워크 저장안함 "%1"? + + Forget + + diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 2afdaf3388..6774555b73 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -238,6 +238,14 @@ Disengage to Power Off Desacione para Desligar + + Reset + + + + Review + + DriveStats @@ -464,6 +472,17 @@ trabalho definido Salve connect.comma.ai como sua página inicial para utilizar como um app + + ParamControl + + Ok + OK + + + Cancel + Cancelar + + PrimeAdWidget @@ -589,13 +608,6 @@ trabalho definido Não foi possível montar a partição de dados. Pressione confirmar para resetar seu dispositivo. - - RichTextDialog - - Ok - Ok - - SettingsWindow @@ -854,6 +866,10 @@ trabalho definido CHECK VERIFICAR + + Uninstall + + SshControl @@ -1056,5 +1072,9 @@ trabalho definido Forget Wi-Fi Network "%1"? Esquecer Rede Wi-Fi "%1"? + + Forget + + diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 9e7c354444..6189a235e8 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -238,6 +238,14 @@ Disengage to Power Off 取消openpilot以关机 + + Reset + + + + Review + + DriveStats @@ -461,6 +469,17 @@ location set 将 connect.comma.ai 收藏到您的主屏幕,以便像应用程序一样使用它 + + ParamControl + + Ok + 好的 + + + Cancel + 取消 + + PrimeAdWidget @@ -583,13 +602,6 @@ location set 无法挂载数据分区。 确认以重置您的设备。 - - RichTextDialog - - Ok - 好的 - - SettingsWindow @@ -848,6 +860,10 @@ location set CHECK 查看 + + Uninstall + + SshControl @@ -1050,5 +1066,9 @@ location set Forget Wi-Fi Network "%1"? 忘记WiFi网络 "%1"? + + Forget + + diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 513135c7f4..0b0dbe4ae5 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -238,6 +238,14 @@ Disengage to Power Off 請先取消控車才能關機 + + Reset + + + + Review + + DriveStats @@ -463,6 +471,17 @@ location set 將 connect.comma.ai 加入您的主屏幕,以便像手機 App 一樣使用它 + + ParamControl + + Ok + 確定 + + + Cancel + 取消 + + PrimeAdWidget @@ -585,13 +604,6 @@ location set 無法掛載數據分區。請按確認重置您的設備。 - - RichTextDialog - - Ok - 確定 - - SettingsWindow @@ -850,6 +862,10 @@ location set CHECK 檢查 + + Uninstall + + SshControl @@ -1052,5 +1068,9 @@ location set Forget Wi-Fi Network "%1"? 清除 Wi-Fi 網路 "%1"? + + Forget + + diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 821064f81f..0198405769 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -63,6 +63,8 @@ void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Rea right_points.reserve(max_idx + 1); for (int i = 0; i <= max_idx; i++) { + // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera + if (line_x[i] < 0) continue; QPointF left, right; bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); @@ -212,7 +214,7 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ - "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", + "modelV2", "carControl", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", }); diff --git a/selfdrive/updated.py b/selfdrive/updated.py index c261a92a84..9da2a05a11 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -175,7 +175,7 @@ def finalize_update() -> None: shutil.copytree(OVERLAY_MERGED, FINALIZED, symlinks=True) run(["git", "reset", "--hard"], FINALIZED) - run(["git", "submodule", "foreach", "--recursive", "git", "reset"], FINALIZED) + run(["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"], FINALIZED) cloudlog.info("Starting git cleanup in finalized update") t = time.monotonic() @@ -374,8 +374,8 @@ class Updater: ["git", "reset", "--hard"], ["git", "clean", "-xdff"], ["git", "submodule", "sync"], - ["git", "submodule", "init"], - ["git", "submodule", "update"], + ["git", "submodule", "update", "--init", "--recursive"], + ["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"], ] r = [run(cmd, OVERLAY_MERGED) for cmd in cmds] cloudlog.info("git reset success: %s", '\n'.join(r)) diff --git a/system/camerad/cameras/real_debayer.cl b/system/camerad/cameras/real_debayer.cl index 59aa488653..cff5ae455b 100644 --- a/system/camerad/cameras/real_debayer.cl +++ b/system/camerad/cameras/real_debayer.cl @@ -72,7 +72,7 @@ float4 val4_from_12(uchar8 pvs, float gain) { float4 pv = {ox03c10_lut[parsed.s0], ox03c10_lut[parsed.s1], ox03c10_lut[parsed.s2], ox03c10_lut[parsed.s3]}; // it's a 24 bit signal, center in the middle 8 bits - return pv*256.0; + return clamp(pv*gain*256.0, 0.0, 1.0); #else // AR // normalize and scale float4 pv = (convert_float4(parsed) - 168.0) / (4096.0 - 168.0); diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 378d4b7058..3c6d466a69 100755 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -4,12 +4,14 @@ import unittest from collections import defaultdict import cereal.messaging as messaging +from cereal import log from cereal.services import service_list from selfdrive.manager.process_config import managed_processes from system.hardware import TICI TEST_TIMESPAN = 30 -LAG_FRAME_TOLERANCE = 0.5 # ms +LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0321: 0.5, # ARs use synced pulses for frame starts + log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') @@ -62,6 +64,7 @@ class TestCamerad(unittest.TestCase): assert len(skips) == 0, f"Found frame skips, missing cameras for the following frames: {skips}" def test_frame_sync(self): + sensor_type = [getattr(msgs[0], msgs[0].which()).sensor for frame_id, msgs in self.log_by_frame_id.items()][0].raw frame_times = {frame_id: [getattr(m, m.which()).timestampSof for m in msgs] for frame_id, msgs in self.log_by_frame_id.items()} diffs = {frame_id: (max(ts) - min(ts))/1e6 for frame_id, ts in frame_times.items()} @@ -69,7 +72,7 @@ class TestCamerad(unittest.TestCase): def get_desc(fid, diff): cam_times = [(m.which(), getattr(m, m.which()).timestampSof/1e6) for m in self.log_by_frame_id[fid]] return f"{diff=} {cam_times=}" - laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE} + laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[sensor_type]} assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" if __name__ == "__main__": diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 91353e9726..ba50b101fc 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -11,7 +11,7 @@ // BinaryView -const int CELL_HEIGHT = 30; +const int CELL_HEIGHT = 26; BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { model = new BinaryViewModel(this); @@ -20,15 +20,12 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { setItemDelegate(delegate); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); horizontalHeader()->hide(); - verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setMouseTracking(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + setFrameShape(QFrame::NoFrame); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); -} - -QSize BinaryView::sizeHint() const { - QSize sz = QTableView::sizeHint(); - return {sz.width(), model->rowCount() <= 8 ? ((CELL_HEIGHT + 1) * model->rowCount() + 2) : sz.height()}; + setMouseTracking(true); } void BinaryView::highlight(const Signal *sig) { @@ -108,15 +105,9 @@ void BinaryView::leaveEvent(QEvent *event) { } void BinaryView::setMessage(const QString &message_id) { - msg_id = message_id; model->setMessage(message_id); clearSelection(); updateState(); - updateGeometry(); -} - -void BinaryView::updateState() { - model->updateState(); } const Signal *BinaryView::getResizingSignal() const { @@ -179,6 +170,9 @@ void BinaryViewModel::setMessage(const QString &message_id) { items[idx].sigs.push_back(&dbc_msg->sigs[i]); } } + } else { + row_count = can->lastMessage(msg_id).dat.size(); + items.resize(row_count * column_count); } endResetModel(); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 060a2eef7f..a907a673bf 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -57,11 +57,10 @@ class BinaryView : public QTableView { public: BinaryView(QWidget *parent = nullptr); void setMessage(const QString &message_id); - void updateState(); void highlight(const Signal *sig); - const Signal *hoveredSignal() const { return hovered_sig; } QSet getOverlappingSignals() const; - QSize sizeHint() const override; + inline const Signal *hoveredSignal() const { return hovered_sig; } + inline void updateState() { model->updateState(); } signals: void signalHovered(const Signal *sig); @@ -76,7 +75,6 @@ private: void leaveEvent(QEvent *event) override; const Signal *getResizingSignal() const; - QString msg_id; QModelIndex anchor_index; BinaryViewModel *model; BinaryItemDelegate *delegate; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 56346c6d6e..7540abbcb2 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -1,11 +1,13 @@ #include "tools/cabana/chartswidget.h" +#include #include #include #include #include #include #include +#include // ChartsWidget @@ -116,8 +118,9 @@ void ChartsWidget::updateState() { display_range.first = std::max(display_range.first, event_range.first); display_range.second = std::min(display_range.first + settings.max_chart_x_range, event_range.second); if (prev_range != display_range) { + QFutureSynchronizer future_synchronizer; for (auto c : charts) - c->chart_view->updateSeries(display_range); + future_synchronizer.addFuture(QtConcurrent::run(c->chart_view, &ChartView::updateSeries, display_range)); } } @@ -155,6 +158,7 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) { charts_layout->insertWidget(0, chart); charts.push_back(chart); emit chartOpened(chart->id, chart->signal); + updateState(); } updateTitleBar(); } @@ -324,11 +328,10 @@ void ChartView::updateLineMarker(double current_sec) { chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min()); if (int(line_marker->line().x1()) != x) { line_marker->setLine(x, 0, x, height()); - chart()->update(); } } -void ChartView::updateSeries(const std::pair &range) { +void ChartView::updateSeries(const std::pair range) { auto events = can->events(); if (!events) return; @@ -337,7 +340,7 @@ void ChartView::updateSeries(const std::pair &range) { uint32_t address = l[1].toUInt(nullptr, 16); vals.clear(); - vals.reserve((range.second - range.first) * 100); // [n]minutes * 100hz + vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz double route_start_time = can->routeStartTime(); Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + range.first) * 1e9); auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); @@ -368,8 +371,13 @@ void ChartView::updateAxisY() { auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); - (min->y() == max->y()) ? axis_y->setRange(min->y() - 1, max->y() + 1) - : axis_y->setRange(min->y(), max->y()); + if (max->y() == min->y()) { + axis_y->setRange(min->y() - 1, max->y() + 1); + } else { + double range = max->y() - min->y(); + axis_y->setRange(min->y() - range * 0.05, max->y() + range * 0.05); + axis_y->applyNiceNumbers(); + } } void ChartView::enterEvent(QEvent *event) { @@ -402,6 +410,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { // zoom in if selected range is greater than 0.5s emit zoomIn(min, max); } + viewport()->update(); event->accept(); } else if (event->button() == Qt::RightButton) { emit zoomReset(); @@ -409,6 +418,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { } else { QGraphicsView::mouseReleaseEvent(event); } + setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); } void ChartView::mouseMoveEvent(QMouseEvent *ev) { @@ -435,6 +445,8 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { track_line->setVisible(value != vals.end()); value_text->setVisible(value != vals.end()); track_ellipse->setVisible(value != vals.end()); + } else { + setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } QChartView::mouseMoveEvent(ev); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 602d50ca0a..ff56008e7d 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -21,7 +21,7 @@ class ChartView : public QChartView { public: ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr); - void updateSeries(const std::pair &range); + void updateSeries(const std::pair range); void setRange(double min, double max, bool force_update = false); void updateLineMarker(double current_sec); void updateFromSettings(); @@ -42,7 +42,7 @@ private: QGraphicsEllipseItem *track_ellipse; QGraphicsTextItem *value_text; QGraphicsLineItem *line_marker; - QList vals; + QVector vals; QString id; const Signal *signal; }; diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 57a3910303..f3e3438229 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include "selfdrive/ui/qt/util.h" #include "tools/cabana/canmessages.h" @@ -13,38 +15,25 @@ // DetailWidget DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { - main_layout = new QHBoxLayout(this); + QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); - right_column = new QVBoxLayout(); - main_layout->addLayout(right_column); - - binary_view_container = new QWidget(this); - binary_view_container->setMinimumWidth(500); - binary_view_container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - QVBoxLayout *bin_layout = new QVBoxLayout(binary_view_container); - bin_layout->setContentsMargins(0, 0, 0, 0); - bin_layout->setSpacing(0); - // tabbar + // tabbar tabbar = new QTabBar(this); tabbar->setTabsClosable(true); tabbar->setDrawBase(false); tabbar->setUsesScrollButtons(true); tabbar->setAutoHide(true); tabbar->setContextMenuPolicy(Qt::CustomContextMenu); - bin_layout->addWidget(tabbar); + main_layout->addWidget(tabbar); - TitleFrame *title_frame = new TitleFrame(this); - title_frame->setFrameShape(QFrame::StyledPanel); - title_frame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + QFrame *title_frame = new QFrame(this); QVBoxLayout *frame_layout = new QVBoxLayout(title_frame); + title_frame->setFrameShape(QFrame::StyledPanel); // message title QHBoxLayout *title_layout = new QHBoxLayout(); - split_btn = new QPushButton("⬅", this); - split_btn->setFixedSize(20, 20); - split_btn->setToolTip(tr("Split to two columns")); - title_layout->addWidget(split_btn); title_layout->addWidget(new QLabel("time:")); time_label = new QLabel(this); time_label->setStyleSheet("font-weight:bold"); @@ -62,6 +51,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart // warning warning_widget = new QWidget(this); QHBoxLayout *warning_hlayout = new QHBoxLayout(warning_widget); + warning_hlayout->setContentsMargins(0, 0, 0, 0); QLabel *warning_icon = new QLabel(this); warning_icon->setPixmap(style()->standardPixmap(QStyle::SP_MessageBoxWarning)); warning_hlayout->addWidget(warning_icon, 0, Qt::AlignTop); @@ -69,30 +59,32 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart warning_hlayout->addWidget(warning_label, 1, Qt::AlignLeft); warning_widget->hide(); frame_layout->addWidget(warning_widget); - bin_layout->addWidget(title_frame); + main_layout->addWidget(title_frame); + + QWidget *container = new QWidget(this); + QVBoxLayout *container_layout = new QVBoxLayout(container); + container_layout->setSpacing(0); + container_layout->setContentsMargins(0, 0, 0, 0); + + scroll = new QScrollArea(this); + scroll->setWidget(container); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + main_layout->addWidget(scroll); // binary view binary_view = new BinaryView(this); - bin_layout->addWidget(binary_view); - right_column->addWidget(binary_view_container); + container_layout->addWidget(binary_view); // signals signals_container = new QWidget(this); signals_container->setLayout(new QVBoxLayout); - signals_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - - scroll = new ScrollArea(this); - scroll->setWidget(signals_container); - scroll->setWidgetResizable(true); - scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - right_column->addWidget(scroll); + container_layout->addWidget(signals_container); // history log history_log = new HistoryLog(this); - right_column->addWidget(history_log); + container_layout->addWidget(history_log); - QObject::connect(split_btn, &QPushButton::clicked, this, &DetailWidget::moveBinaryView); - QObject::connect(title_frame, &TitleFrame::doubleClicked, this, &DetailWidget::moveBinaryView); QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); @@ -146,37 +138,42 @@ void DetailWidget::setMessage(const QString &message_id) { tabbar->setCurrentIndex(index); msg_id = message_id; dbcMsgChanged(); + + scroll->verticalScrollBar()->setValue(0); } void DetailWidget::dbcMsgChanged(int show_form_idx) { if (msg_id.isEmpty()) return; - warning_widget->hide(); + setUpdatesEnabled(false); QStringList warnings; + for (auto f : signal_list) f->hide(); - clearLayout(signals_container->layout()); - QString msg_name = tr("untitled"); - if (auto msg = dbc()->msg(msg_id)) { + const Msg *msg = dbc()->msg(msg_id); + if (msg) { for (int i = 0; i < msg->sigs.size(); ++i) { - auto form = new SignalEdit(i, msg_id, &(msg->sigs[i])); - form->setChartOpened(charts->isChartOpened(msg_id, &(msg->sigs[i]))); - signals_container->layout()->addWidget(form); - QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); - QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); - QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); - QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); - QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); - QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]](bool show) { charts->showChart(msg_id, sig, show); }); - if (i == show_form_idx) { - QTimer::singleShot(0, [=]() { emit form->showFormClicked(); }); + SignalEdit *form = i < signal_list.size() ? signal_list[i] : nullptr; + if (!form) { + form = new SignalEdit(i); + QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); + QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); + QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); + QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); + QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); + QObject::connect(form, &SignalEdit::showChart, charts, &ChartsWidget::showChart); + signals_container->layout()->addWidget(form); + signal_list.push_back(form); } + form->setSignal(msg_id, &(msg->sigs[i]), i == show_form_idx); + form->setChartOpened(charts->isChartOpened(msg_id, &(msg->sigs[i]))); + form->show(); } - msg_name = msg->name.c_str(); if (msg->size != can->lastMessage(msg_id).dat.size()) warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } + edit_btn->setVisible(true); - name_label->setText(msg_name); + name_label->setText(msg ? msg->name.c_str() : "untitled"); binary_view->setMessage(msg_id); history_log->setMessage(msg_id); @@ -187,10 +184,9 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { warnings.push_back(tr("%1 has overlapping bits.").arg(s->name.c_str())); } - if (!warnings.isEmpty()) { - warning_label->setText(warnings.join('\n')); - warning_widget->show(); - } + warning_label->setText(warnings.join('\n')); + warning_widget->setVisible(!warnings.isEmpty()); + setUpdatesEnabled(true); } void DetailWidget::updateState() { @@ -201,35 +197,17 @@ void DetailWidget::updateState() { history_log->updateState(); } -void DetailWidget::moveBinaryView() { - if (binview_in_left_col) { - right_column->insertWidget(0, binary_view_container); - emit binaryViewMoved(true); - } else { - main_layout->insertWidget(0, binary_view_container); - emit binaryViewMoved(false); - } - split_btn->setText(binview_in_left_col ? "⬅" : "➡"); - split_btn->setToolTip(binview_in_left_col ? tr("Split to two columns") : tr("Move back")); - binary_view->updateGeometry(); - binview_in_left_col = !binview_in_left_col; -} - void DetailWidget::showForm() { SignalEdit *sender = qobject_cast(QObject::sender()); - for (auto f : signals_container->findChildren()) { + setUpdatesEnabled(false); + for (auto f : signal_list) f->setFormVisible(f == sender && !f->isFormVisible()); - if (f == sender) { - QTimer::singleShot(0, [=]() { scroll->ensureWidgetVisible(f); }); - } - } + QTimer::singleShot(1, [this]() { setUpdatesEnabled(true); }); } void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool opened) { - if (id == msg_id) { - for (auto f : signals_container->findChildren()) - if (f->sig == sig) f->setChartOpened(opened); - } + for (auto f : signal_list) + if (f->msg_id == id && f->sig == sig) f->setChartOpened(opened); } void DetailWidget::editMsg() { @@ -284,7 +262,7 @@ void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) { dbc()->updateSignal(msg_id, sig->name.c_str(), new_sig); // update binary view and history log - dbcMsgChanged(); + updateState(); } void DetailWidget::removeSignal(const Signal *sig) { @@ -322,19 +300,3 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } - -// ScrollArea - -bool ScrollArea::eventFilter(QObject *obj, QEvent *ev) { - if (obj == widget() && ev->type() == QEvent::Resize) { - int height = widget()->height() + 4; - setMinimumHeight(height > 480 ? 480 : height); - setMaximumHeight(height); - } - return QScrollArea::eventFilter(obj, ev); -} - -void ScrollArea::setWidget(QWidget *w) { - QScrollArea::setWidget(w); - w->installEventFilter(this); -} diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 656aacb106..ce3468e472 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -2,22 +2,12 @@ #include #include -#include #include "tools/cabana/binaryview.h" #include "tools/cabana/chartswidget.h" #include "tools/cabana/historylog.h" #include "tools/cabana/signaledit.h" -class TitleFrame : public QFrame { - Q_OBJECT -public: - TitleFrame(QWidget *parent) : QFrame(parent) {} - void mouseDoubleClickEvent(QMouseEvent *e) { emit doubleClicked(); } -signals: - void doubleClicked(); -}; - class EditMessageDialog : public QDialog { Q_OBJECT @@ -28,15 +18,6 @@ public: QSpinBox *size_spin; }; -class ScrollArea : public QScrollArea { - Q_OBJECT - -public: - ScrollArea(QWidget *parent) : QScrollArea(parent) {} - bool eventFilter(QObject *obj, QEvent *ev) override; - void setWidget(QWidget *w); -}; - class DetailWidget : public QWidget { Q_OBJECT @@ -58,7 +39,6 @@ private: void editMsg(); void showForm(); void updateState(); - void moveBinaryView(); QString msg_id; QLabel *name_label, *time_label, *warning_label; @@ -66,13 +46,9 @@ private: QPushButton *edit_btn; QWidget *signals_container; QTabBar *tabbar; - QHBoxLayout *main_layout; - QVBoxLayout *right_column; - bool binview_in_left_col = false; - QWidget *binary_view_container; - QPushButton *split_btn; HistoryLog *history_log; BinaryView *binary_view; - ScrollArea *scroll; + QScrollArea *scroll; ChartsWidget *charts; + QList signal_list; }; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 8136d0577c..7bb2f37699 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -1,9 +1,10 @@ #include "tools/cabana/historylog.h" #include -#include #include +// HistoryLogModel + QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { bool has_signal = dbc_msg && !dbc_msg->sigs.empty(); if (role == Qt::DisplayRole) { @@ -37,7 +38,7 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i if (section == 0) { return "Time"; } - return has_signal ? dbc_msg->sigs[section - 1].name.c_str() : "Data"; + return has_signal ? QString::fromStdString(dbc_msg->sigs[section - 1].name).replace('_', ' ') : "Data"; } else if (role == Qt::BackgroundRole && section > 0 && has_signal) { return QBrush(QColor(getColor(section - 1))); } @@ -64,16 +65,27 @@ void HistoryLogModel::updateState() { } } -HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(0, 0, 0, 0); +// HeaderView + +QSize HeaderView::sectionSizeFromContents(int logicalIndex) const { + const QString text = model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString(); + const QRect rect = fontMetrics().boundingRect(QRect(0, 0, sectionSize(logicalIndex), 1000), defaultAlignment(), text); + return rect.size() + QSize{10, 5}; +} + +// HistoryLog + +HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { model = new HistoryLogModel(this); - table = new QTableView(this); - table->setModel(model); - table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - table->setColumnWidth(0, 60); - table->verticalHeader()->setVisible(false); - table->setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); - main_layout->addWidget(table); + setModel(model); + setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); + horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap); + horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + verticalHeader()->setVisible(false); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + setFrameShape(QFrame::NoFrame); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index d39bcf9f1d..e1c1319166 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -1,10 +1,17 @@ #pragma once +#include #include #include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" +class HeaderView : public QHeaderView { +public: + HeaderView(Qt::Orientation orientation, QWidget *parent = nullptr) : QHeaderView(orientation, parent) {} + QSize sectionSizeFromContents(int logicalIndex) const; +}; + class HistoryLogModel : public QAbstractTableModel { Q_OBJECT @@ -25,15 +32,13 @@ private: std::deque messages; }; -class HistoryLog : public QWidget { +class HistoryLog : public QTableView { Q_OBJECT public: HistoryLog(QWidget *parent); void setMessage(const QString &message_id) { model->setMessage(message_id); } void updateState() { model->updateState(); } - private: - QTableView *table; HistoryLogModel *model; }; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 205e5347ce..e80a66bce9 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -147,6 +147,11 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { return {}; } +void MessageListModel::setFilterString(const QString &string) { + filter_str = string; + updateState(true); +} + bool MessageListModel::updateMessages(bool sort) { if (msgs.size() == can->can_msgs.size() && filter_str.isEmpty() && !sort) return false; diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index a3d4d860b2..255dce7dc8 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -38,7 +38,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); } void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void updateState(bool sort = false); - void setFilterString(const QString &string) { filter_str = string; } + void setFilterString(const QString &string); private: bool updateMessages(bool sort); diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 17299ebca4..bba59c0d74 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -25,7 +25,7 @@ void Settings::save() { void Settings::load() { QSettings s("settings", QSettings::IniFormat); fps = s.value("fps", 10).toInt(); - can_msg_log_size = s.value("log_size", 100).toInt(); + can_msg_log_size = s.value("log_size", 50).toInt(); cached_segment_limit = s.value("cached_segment", 3).toInt(); chart_height = s.value("chart_height", 200).toInt(); chart_theme = s.value("chart_theme", 0).toInt(); diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index 88eeebc722..cb858de873 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -13,7 +13,7 @@ public: void load(); int fps = 10; - int can_msg_log_size = 100; + int can_msg_log_size = 50; int cached_segment_limit = 3; int chart_height = 200; int chart_theme = 0; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index ef0a85eba3..ee91887d0a 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -13,40 +13,37 @@ // SignalForm -SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { +SignalForm::SignalForm(QWidget *parent) : QWidget(parent) { QFormLayout *form_layout = new QFormLayout(this); + form_layout->setContentsMargins(0, 0, 0, 0); - name = new QLineEdit(sig.name.c_str()); + name = new QLineEdit(); form_layout->addRow(tr("Name"), name); size = new QSpinBox(); size->setMinimum(1); - size->setValue(sig.size); form_layout->addRow(tr("Size"), size); endianness = new QComboBox(); endianness->addItems({"Little", "Big"}); - endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1); form_layout->addRow(tr("Endianness"), endianness); - form_layout->addRow(tr("lsb"), new QLabel(QString::number(sig.lsb))); - form_layout->addRow(tr("msb"), new QLabel(QString::number(sig.msb))); + ; + form_layout->addRow(tr("lsb"), lsb = new QLabel()); + form_layout->addRow(tr("msb"), msb = new QLabel()); sign = new QComboBox(); sign->addItems({"Signed", "Unsigned"}); - sign->setCurrentIndex(sig.is_signed ? 0 : 1); form_layout->addRow(tr("sign"), sign); auto double_validator = new QDoubleValidator(this); factor = new QLineEdit(); factor->setValidator(double_validator); - factor->setText(QString::number(sig.factor)); form_layout->addRow(tr("Factor"), factor); offset = new QLineEdit(); offset->setValidator(double_validator); - offset->setText(QString::number(sig.offset)); form_layout->addRow(tr("Offset"), offset); // TODO: parse the following parameters in opendbc @@ -66,18 +63,15 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { // SignalEdit -SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWidget *parent) : msg_id(msg_id), sig(sig), form_idx(index), QWidget(parent) { +SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); // title bar QHBoxLayout *title_layout = new QHBoxLayout(); - icon = new QLabel(">"); - icon->setStyleSheet("font-weight:bold"); + icon = new QLabel(); title_layout->addWidget(icon); title = new ElidedLabel(this); - title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - title->setText(QString("%1. %2").arg(index + 1).arg(sig->name.c_str())); title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index))); title_layout->addWidget(title, 1); @@ -96,9 +90,6 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWid // signal form form_container = new QWidget(this); QVBoxLayout *v_layout = new QVBoxLayout(form_container); - form = new SignalForm(*sig, this); - v_layout->addWidget(form); - QHBoxLayout *h = new QHBoxLayout(); QPushButton *remove_btn = new QPushButton(tr("Remove Signal")); h->addWidget(remove_btn); @@ -106,8 +97,6 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWid QPushButton *save_btn = new QPushButton(tr("Save")); h->addWidget(save_btn); v_layout->addLayout(h); - - form_container->setVisible(false); main_layout->addWidget(form_container); // bottom line @@ -119,11 +108,19 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWid QObject::connect(remove_btn, &QPushButton::clicked, [this]() { emit remove(this->sig); }); QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::saveSignal); - QObject::connect(plot_btn, &QPushButton::clicked, [this]() { emit showChart(!chart_opened); }); - QObject::connect(seek_btn, &QPushButton::clicked, [this, msg_id]() { - SignalFindDlg dlg(msg_id, this->sig, this); + QObject::connect(plot_btn, &QPushButton::clicked, [this]() { emit showChart(msg_id, sig, !chart_opened); }); + QObject::connect(seek_btn, &QPushButton::clicked, [this]() { + SignalFindDlg dlg(msg_id, sig, this); dlg.exec(); }); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +void SignalEdit::setSignal(const QString &message_id, const Signal *signal, bool show_form) { + msg_id = message_id; + sig = signal; + title->setText(QString("%1. %2").arg(form_idx + 1).arg(sig->name.c_str())); + setFormVisible(show_form); } void SignalEdit::saveSignal() { @@ -152,6 +149,20 @@ void SignalEdit::setChartOpened(bool opened) { } void SignalEdit::setFormVisible(bool visible) { + if (visible) { + if (!form) { + form = new SignalForm(this); + ((QVBoxLayout *)form_container->layout())->insertWidget(0, form); + } + form->name->setText(sig->name.c_str()); + form->size->setValue(sig->size); + form->endianness->setCurrentIndex(sig->is_little_endian ? 0 : 1); + form->sign->setCurrentIndex(sig->is_signed ? 0 : 1); + form->factor->setText(QString::number(sig->factor)); + form->offset->setText(QString::number(sig->offset)); + form->msb->setText(QString::number(sig->msb)); + form->lsb->setText(QString::number(sig->lsb)); + } form_container->setVisible(visible); icon->setText(visible ? "▼" : ">"); } diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index e3d38d5b25..dce9d27479 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -14,9 +14,10 @@ class SignalForm : public QWidget { public: - SignalForm(const Signal &sig, QWidget *parent); + SignalForm(QWidget *parent); QLineEdit *name, *unit, *comment, *val_desc, *offset, *factor, *min_val, *max_val; + QLabel *lsb, *msb; QSpinBox *size; QComboBox *sign, *endianness; }; @@ -25,16 +26,18 @@ class SignalEdit : public QWidget { Q_OBJECT public: - SignalEdit(int index, const QString &msg_id, const Signal *sig, QWidget *parent = nullptr); + SignalEdit(int index, QWidget *parent = nullptr); + void setSignal(const QString &msg_id, const Signal *sig, bool show_form); void setChartOpened(bool opened); void setFormVisible(bool show); void signalHovered(const Signal *sig); inline bool isFormVisible() const { return form_container->isVisible(); } const Signal *sig = nullptr; + QString msg_id; signals: void highlight(const Signal *sig); - void showChart(bool show); + void showChart(const QString &name, const Signal *sig, bool show); void showFormClicked(); void remove(const Signal *sig); void save(const Signal *sig, const Signal &new_sig); @@ -44,12 +47,11 @@ protected: void leaveEvent(QEvent *event) override; void saveSignal(); - SignalForm *form; + SignalForm *form = nullptr; ElidedLabel *title; QWidget *form_container; QLabel *icon; int form_idx = 0; - QString msg_id; bool chart_opened = false; QPushButton *plot_btn; }; diff --git a/tools/plotjuggler/layouts/longitudinal.xml b/tools/plotjuggler/layouts/longitudinal.xml new file mode 100644 index 0000000000..ff8f3ad193 --- /dev/null +++ b/tools/plotjuggler/layouts/longitudinal.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/sim/start_openpilot_docker.sh b/tools/sim/start_openpilot_docker.sh index e48e63574d..dba89b8b70 100755 --- a/tools/sim/start_openpilot_docker.sh +++ b/tools/sim/start_openpilot_docker.sh @@ -20,6 +20,7 @@ else EXTRA_ARGS="${EXTRA_ARGS} -it" fi +docker kill openpilot_client || true docker run --net=host\ --name openpilot_client \ --rm \