diff --git a/common/params_keys.h b/common/params_keys.h index 2cda89e18..5a4042641 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -208,7 +208,7 @@ inline static std::unordered_map keys = { {"CECurves", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}}, {"CECurvesLead", {PERSISTENT, BOOL, "0", "0", 1}}, {"CELead", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}}, - {"CEModelStopTime", {PERSISTENT, FLOAT, "9.0", "0.0", 2, SETTINGS_SIMPLE}}, + {"CEModelStopTime", {PERSISTENT, FLOAT, "7.7", "0.0", 2, SETTINGS_SIMPLE}}, {"CESignalLaneDetection", {PERSISTENT, BOOL, "1", "0", 2}}, {"CESignalSpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}}, {"CESlowerLead", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}}, diff --git a/opendbc_repo/docs/CARS.md b/opendbc_repo/docs/CARS.md index 778a36333..4cf7ab5e9 100644 --- a/opendbc_repo/docs/CARS.md +++ b/opendbc_repo/docs/CARS.md @@ -1,24 +1,36 @@ -# Support Information for 384 Known Cars +# Support Information for 489 Known Cars |Make|Model|Package|Support Level| |---|---|---|:---:| +|Acura|ADX 2025-26|All|[Upstream](#upstream)| |Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|[Upstream](#upstream)| |Acura|ILX 2019|All|[Upstream](#upstream)| |Acura|Integra 2023-25|All|[Community](#community)| +|Acura|Integra 2023-26|All|[Upstream](#upstream)| +|Acura|MDX 2014-16|Advance Package|[Upstream](#upstream)| |Acura|MDX 2015-16|Advance Package|[Community](#community)| +|Acura|MDX 2017-19|All|[Upstream](#upstream)| |Acura|MDX 2017-20|All|[Community](#community)| +|Acura|MDX 2020|All|[Upstream](#upstream)| +|Acura|MDX 2022-24|All|[Upstream](#upstream)| |Acura|MDX 2022-24|All|[Community](#community)| |Acura|MDX 2025|All except Type S|[Upstream](#upstream)| +|Acura|MDX Hybrid 2017-19|All|[Upstream](#upstream)| +|Acura|MDX Hybrid 2020|All|[Upstream](#upstream)| |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|[Upstream](#upstream)| |Acura|RDX 2019-21|All|[Upstream](#upstream)| |Acura|RDX 2022-25|All|[Community](#community)| +|Acura|RDX 2022-26|All|[Upstream](#upstream)| |Acura|RLX 2017|Advance Package or Technology Package|[Community](#community)| +|Acura|TLX 2015-17|Advance Package|[Upstream](#upstream)| |Acura|TLX 2015-17|Advance Package|[Community](#community)| +|Acura|TLX 2018-20|All|[Upstream](#upstream)| |Acura|TLX 2018-20|All|[Community](#community)| |Acura|TLX 2021|All|[Upstream](#upstream)| |Acura|TLX 2022-23|All|[Community](#community)| +|Acura|TLX 2024-25|All|[Upstream](#upstream)| |Acura|ZDX 2024|All|[Not compatible](#can-bus-security)| |Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| @@ -29,11 +41,37 @@ |Audi|Q5 2017-24|All|[Not compatible](#flexray)| |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|[Upstream](#upstream)| -|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|[Upstream](#upstream)| +|Buick|Baby Enclave 2020-23|Driver Assist Package|[Upstream](#upstream)| +|Buick|LaCrosse ASCM Harness 2017-19|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Buick|LaCrosse US ASCM Harness 2019|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Cadillac|CT6 No-ACC 2016-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Cadillac|Escalade ASCM Harness 2018|Driver Assist Package|[Upstream](#upstream)| +|Cadillac|Escalade ESV Platinum ASCM Harness 2019|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)| +|Cadillac|XT4 No-ACC 2023|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Cadillac|XT5 No-ACC 2022|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Cadillac|XT6 2020|Driver Assist Package|[Upstream](#upstream)| +|Chevrolet|Blazer 2019-25|Driver Assist Package|[Upstream](#upstream)| +|Chevrolet|Bolt EV & EUV ACC 2022-23|Premier or Premier Redline Trim without Super Cruise Package|[Upstream](#upstream)| +|Chevrolet|Bolt EV & EUV ACC w Pedal 2022-23|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Bolt EV & EUV No-ACC 2022-23|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Bolt EV No-ACC 2017|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Bolt EV No-ACC 2018-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Equinox No-ACC 2019-22|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Malibu 2019|SDGM Harness (Optional SASCM)|[Upstream](#upstream)| +|Chevrolet|Malibu ASCM Harness 2017-19|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Malibu Hybrid No-ACC 2017|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Malibu No-ACC 2023|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chevrolet|Silverado 1500 2020-21|Safety Package II|[Upstream](#upstream)| +|Chevrolet|Silverado 1500 No-ACC 2020-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Suburban Premier 2016-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Suburban Premier No-ACC 2016-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Trailblazer No-ACC 2021-22|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|TRAX 2024|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|[Upstream](#upstream)| +|Chevrolet|Volt No-ACC 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chrysler|Pacifica 2021-23|All|[Upstream](#upstream)| @@ -67,9 +105,11 @@ |Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)| |Ford|Mustang Mach-E 2021-24|All|[Upstream](#upstream)| |Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)| +|Ford|Transit 2025|Co-Pilot360 Assist+|[Upstream](#upstream)| |Genesis|G70 2018|All|[Upstream](#upstream)| |Genesis|G70 2019-21|All|[Upstream](#upstream)| |Genesis|G70 2022-23|All|[Upstream](#upstream)| +|Genesis|G70 Non-SCC 2021|No Smart Cruise Control (Non-SCC)|[Community](community)| |Genesis|G80 2017|All|[Upstream](#upstream)| |Genesis|G80 2018-19|All|[Upstream](#upstream)| |Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024|Highway Driving Assist II|[Upstream](#upstream)| @@ -78,14 +118,22 @@ |Genesis|GV60 (Performance Trim) 2022-23|All|[Upstream](#upstream)| |Genesis|GV70 (2.5T Trim, without HDA II) 2022-24|All|[Upstream](#upstream)| |Genesis|GV70 (3.5T Trim, without HDA II) 2022-23|All|[Upstream](#upstream)| +|Genesis|GV70 Electrified 2026|All|[Upstream](#upstream)| |Genesis|GV70 Electrified (Australia Only) 2022|All|[Upstream](#upstream)| |Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|[Upstream](#upstream)| |Genesis|GV80 2023|All|[Upstream](#upstream)| +|Genesis|GV80 (3.5T Prestige Trim, with HDA II & LFA2) 2025|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)| +|Genesis|GV80 Coupe (with HDA II & LFA2) 2025|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)| +|GMC|Acadia ASCM Harness 2018|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |GMC|Sierra 1500 2020-21|Driver Alert Package II|[Upstream](#upstream)| -|GMC|Yukon 2019-20|Adaptive Cruise Control (ACC) & LKAS|[Dashcam mode](#dashcam)| +|GMC|Sierra 1500 No-ACC 2020-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|GMC|Yukon 2019-20|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)| +|GMC|Yukon No-ACC 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| +|Honda|Accord 2016-17|Honda Sensing|[Upstream](#upstream)| |Honda|Accord 2016-17|Honda Sensing|[Community](#community)| |Honda|Accord 2018-22|All|[Upstream](#upstream)| |Honda|Accord 2023-25|All|[Upstream](#upstream)| +|Honda|Accord Hybrid 2017|All|[Upstream](#upstream)| |Honda|Accord Hybrid 2018-22|All|[Upstream](#upstream)| |Honda|Accord Hybrid 2023-25|All|[Upstream](#upstream)| |Honda|City (Brazil only) 2023|All|[Upstream](#upstream)| @@ -98,6 +146,7 @@ |Honda|Civic Hatchback Hybrid 2025-26|All|[Upstream](#upstream)| |Honda|Civic Hatchback Hybrid (Europe only) 2023|All|[Upstream](#upstream)| |Honda|Civic Hybrid 2025-26|All|[Upstream](#upstream)| +|Honda|Clarity 2018-21|All|[Upstream](#upstream)| |Honda|Clarity 2018-21|All|[Community](#community)| |Honda|CR-V 2015-16|Touring Trim|[Upstream](#upstream)| |Honda|CR-V 2017-22|Honda Sensing|[Upstream](#upstream)| @@ -106,6 +155,8 @@ |Honda|CR-V Hybrid 2023-25|All|[Upstream](#upstream)| |Honda|e 2020|All|[Upstream](#upstream)| |Honda|Fit 2018-20|Honda Sensing|[Upstream](#upstream)| +|Honda|Fit (Taiwan) 2021|All|[Upstream](#upstream)| +|Honda|Fit (Taiwan) 2024-25|All|[Upstream](#upstream)| |Honda|Freed 2020|Honda Sensing|[Upstream](#upstream)| |Honda|HR-V 2019-22|Honda Sensing|[Upstream](#upstream)| |Honda|HR-V 2023-25|All|[Upstream](#upstream)| @@ -114,27 +165,40 @@ |Honda|N-Box 2018|All|[Upstream](#upstream)| |Honda|Odyssey 2018-20|Honda Sensing|[Upstream](#upstream)| |Honda|Odyssey 2021-26|All|[Upstream](#upstream)| +|Honda|Odyssey (Singapore) 2021|Honda Sensing|[Upstream](#upstream)| +|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|[Upstream](#upstream)| |Honda|Passport 2019-25|All|[Upstream](#upstream)| |Honda|Passport 2026|All|[Upstream](#upstream)| |Honda|Pilot 2016-22|Honda Sensing|[Upstream](#upstream)| |Honda|Pilot 2023-25|All|[Upstream](#upstream)| +|Honda|Prelude 2026|All|[Upstream](#upstream)| |Honda|Prologue 2024-25|All|[Not compatible](#can-bus-security)| |Honda|Ridgeline 2017-25|Honda Sensing|[Upstream](#upstream)| |Hyundai|Azera 2022|All|[Upstream](#upstream)| |Hyundai|Azera Hybrid 2019|All|[Upstream](#upstream)| |Hyundai|Azera Hybrid 2020|All|[Upstream](#upstream)| +|Hyundai|Azera Hybrid (with HDA II & LFA2) 2025|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)| +|Hyundai|Bayon Non-SCC 2021|No Smart Cruise Control (Non-SCC)|[Community](community)| |Hyundai|Custin 2023|All|[Upstream](#upstream)| |Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Elantra 2019|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Elantra 2024-25|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Elantra Hybrid 2024-26|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Elantra Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|[Community](community)| +|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (Non-SCC)|[Community](community)| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|i30 Hybrid 2024|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|[Upstream](#upstream)| |Hyundai|Ioniq 5 (with HDA II) 2022-24|Highway Driving Assist II|[Upstream](#upstream)| |Hyundai|Ioniq 5 (without HDA II) 2022-24|Highway Driving Assist|[Upstream](#upstream)| +|Hyundai|Ioniq 5 N (with HDA II) 2024|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Ioniq 5 PE (with HDA II & LFA2) 2025-26|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)| |Hyundai|Ioniq 6 (with HDA II) 2023-24|Highway Driving Assist II|[Upstream](#upstream)| +|Hyundai|Ioniq 9 (with HDA II & LFA2) 2025-26|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Ioniq Electric 2020|All|[Upstream](#upstream)| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|[Upstream](#upstream)| @@ -143,40 +207,68 @@ |Hyundai|Ioniq Plug-in Hybrid 2020-22|All|[Upstream](#upstream)| |Hyundai|Kona 2020|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|[Dashcam mode](#dashcam)| +|Hyundai|Kona (without HDA II) 2024-25|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Kona Electric (without HDA II) 2024|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Kona Electric Non-SCC 2019|No Smart Cruise Control (Non-SCC)|[Community](community)| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Kona Hybrid (without HDA II) 2024|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|[Community](community)| |Hyundai|Nexo 2021|All|[Upstream](#upstream)| |Hyundai|Palisade 2020-22|All|[Upstream](#upstream)| |Hyundai|Palisade 2023-24|HDA2|[Community](#community)| +|Hyundai|Palisade (with HDA II) 2023-24|Highway Driving Assist II|[Upstream](#upstream)| +|Hyundai|Palisade (without HDA II) 2023-25|Highway Driving Assist|[Upstream](#upstream)| |Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Hyundai|Santa Cruz (without HDA II) 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Santa Fe 2019-20|All|[Upstream](#upstream)| |Hyundai|Santa Fe 2021-23|All|[Upstream](#upstream)| |Hyundai|Santa Fe Hybrid 2022-23|All|[Upstream](#upstream)| +|Hyundai|Santa Fe Hybrid (with HDA II & LFA2) 2024-25|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)| +|Hyundai|Santa Fe Hybrid (without HDA II, LFA2) 2025-26|Lane Follow Assist 2|[Upstream](#upstream)| |Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|[Upstream](#upstream)| |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Sonata 2020-23|All|[Upstream](#upstream)| +|Hyundai|Sonata (without HDA II) 2024-25|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Sonata Hybrid 2020-23|All|[Upstream](#upstream)| +|Hyundai|Sonata Hybrid (without HDA II) 2024-25|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Staria 2023|All|[Upstream](#upstream)| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Tucson 2022|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Tucson 2023-24|All|[Upstream](#upstream)| +|Hyundai|Tucson (without HDA II) 2025-26|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Tucson Hybrid 2022-24|All|[Upstream](#upstream)| +|Hyundai|Tucson Hybrid (without HDA II) 2025-26|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Tucson Plug-in Hybrid 2024|All|[Upstream](#upstream)| +|Hyundai|Tucson Plug-in Hybrid (without HDA II) 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Kia|Carnival 2022-24|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Carnival 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Carnival (with HDA II) 2025|Highway Driving Assist II|[Upstream](#upstream)| +|Kia|Carnival Hybrid 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Carnival Hybrid 2026|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Carnival Hybrid (with HDA II) 2025-26|Highway Driving Assist II|[Upstream](#upstream)| |Kia|Ceed 2019-21|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|[Community](community)| |Kia|EV6 (Southeast Asia only) 2022-24|All|[Upstream](#upstream)| +|Kia|EV6 (with HDA I) 2025|Highway Driving Assist I|[Upstream](#upstream)| |Kia|EV6 (with HDA II) 2022-24|Highway Driving Assist II|[Upstream](#upstream)| |Kia|EV6 (without HDA II) 2022-24|Highway Driving Assist|[Upstream](#upstream)| +|Kia|EV9 2025-26|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Forte 2022-23|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|[Community](community)| +|Kia|Forte Non-SCC 2021|No Smart Cruise Control (Non-SCC)|[Community](community)| +|Kia|K4 (with HDA II) 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|K4 (without HDA II) 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|K5 2021-24|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|K5 (without HDA II) 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|[Upstream](#upstream)| |Kia|Niro EV 2019|All|[Upstream](#upstream)| @@ -198,17 +290,25 @@ |Kia|Optima Hybrid 2017|Advanced Smart Cruise Control|[Dashcam mode](#dashcam)| |Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Seltos 2021|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Seltos Non-SCC 2023-24|No Smart Cruise Control (Non-SCC)|[Community](community)| |Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|[Upstream](#upstream)| |Kia|Sorento 2019|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Sorento 2021-23|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Sorento (without HDA II) 2024-25|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Sorento Hybrid 2021-23|All|[Upstream](#upstream)| +|Kia|Sorento Hybrid 2026|All|[Upstream](#upstream)| |Kia|Sorento Plug-in Hybrid 2022-23|All|[Upstream](#upstream)| |Kia|Sportage 2023-24|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Sportage (without HDA II) 2026|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|[Upstream](#upstream)| +|Kia|Sportage Hybrid 2026|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Kia|Stinger 2022-23|All|[Upstream](#upstream)| |Kia|Telluride 2020-22|All|[Upstream](#upstream)| |Kia|Telluride 2023-24|HDA2|[Community](#community)| +|Kia|Telluride (with HDA II) 2023-24|Highway Driving Assist II|[Upstream](#upstream)| +|Kia|Telluride (without HDA II) 2023-25|Highway Driving Assist|[Upstream](#upstream)| +|Kia|XCeed Plug-in Hybrid 2021|Smart Cruise Control (SCC)|[Upstream](#upstream)| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|[Upstream](#upstream)| |Lexus|ES 2017-18|All|[Upstream](#upstream)| |Lexus|ES 2019-25|All|[Upstream](#upstream)| @@ -244,6 +344,7 @@ |Mazda|CX-9 2021-23|All|[Upstream](#upstream)| |Nissan|Altima 2019-20, 2024|ProPILOT Assist|[Upstream](#upstream)| |Nissan|Leaf 2018-25|ProPILOT Assist|[Upstream](#upstream)| +|Nissan|Leaf Instrument Cluster 2018-25|ProPILOT Assist|[Upstream](#upstream)| |Nissan|Rogue 2018-20|ProPILOT Assist|[Upstream](#upstream)| |Nissan|X-Trail 2017|ProPILOT Assist|[Upstream](#upstream)| |Peugeot|208 2019-25|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)| @@ -257,22 +358,24 @@ |SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Subaru|Ascent 2019-21|All|[Upstream](#upstream)| -|Subaru|Ascent 2023|All|[Dashcam mode](#dashcam)| +|Subaru|Ascent 2023-25|All|[Upstream](#upstream)| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|[Upstream](#upstream)| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|[Upstream](#upstream)| +|Subaru|Crosstrek 2025|All|[Upstream](#upstream)| |Subaru|Crosstrek Hybrid 2020|EyeSight Driver Assistance|[Dashcam mode](#dashcam)| |Subaru|Forester 2017-18|EyeSight Driver Assistance|[Dashcam mode](#dashcam)| |Subaru|Forester 2019-21|All|[Upstream](#upstream)| -|Subaru|Forester 2022-24|All|[Dashcam mode](#dashcam)| +|Subaru|Forester 2022-24|All|[Upstream](#upstream)| |Subaru|Forester Hybrid 2020|EyeSight Driver Assistance|[Dashcam mode](#dashcam)| |Subaru|Impreza 2017-19|EyeSight Driver Assistance|[Upstream](#upstream)| |Subaru|Impreza 2020-22|EyeSight Driver Assistance|[Upstream](#upstream)| |Subaru|Legacy 2015-18|EyeSight Driver Assistance|[Dashcam mode](#dashcam)| |Subaru|Legacy 2020-22|All|[Upstream](#upstream)| +|Subaru|Legacy 2025|All|[Upstream](#upstream)| |Subaru|Outback 2015-17|EyeSight Driver Assistance|[Dashcam mode](#dashcam)| |Subaru|Outback 2018-19|EyeSight Driver Assistance|[Dashcam mode](#dashcam)| |Subaru|Outback 2020-22|All|[Upstream](#upstream)| -|Subaru|Outback 2023|All|[Dashcam mode](#dashcam)| +|Subaru|Outback 2023-24|All|[Upstream](#upstream)| |Subaru|Solterra 2023-25|Any|[Not compatible](#can-bus-security)| |Subaru|XV 2018-19|EyeSight Driver Assistance|[Upstream](#upstream)| |Subaru|XV 2020-21|EyeSight Driver Assistance|[Upstream](#upstream)| @@ -286,7 +389,8 @@ |Škoda|Scala 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Tesla|Model 3 (with HW3) 2019-23|All|[Upstream](#upstream)| -|Tesla|Model 3 (with HW4) 2024-25|All|[Upstream](#upstream)| +|Tesla|Model 3 (with HW4) 2024-26|All|[Upstream](#upstream)| +|Tesla|Model S (Pre-AP) 2012-14|All|[Community](#community)| |Tesla|Model X (with HW4) 2024|All|[Dashcam mode](#dashcam)| |Tesla|Model Y (with HW3) 2020-23|All|[Upstream](#upstream)| |Tesla|Model Y (with HW4) 2024-25|All|[Upstream](#upstream)| @@ -321,6 +425,7 @@ |Toyota|Highlander 2025|Any|[Not compatible](#can-bus-security)| |Toyota|Highlander Hybrid 2017-19|All|[Upstream](#upstream)| |Toyota|Highlander Hybrid 2020-23|All|[Upstream](#upstream)| +|Toyota|Matrix Retrofit 2005|Custom retrofit|[Community](#community)| |Toyota|Mirai 2021|All|[Upstream](#upstream)| |Toyota|Prius 2016|Toyota Safety Sense P|[Upstream](#upstream)| |Toyota|Prius 2017-20|All|[Upstream](#upstream)| @@ -342,7 +447,7 @@ |Toyota|RAV4 Prime 2024-25|Any|[Not compatible](#can-bus-security)| |Toyota|Sequoia 2023-25|Any|[Not compatible](#can-bus-security)| |Toyota|Sienna 2018-20|All|[Upstream](#upstream)| -|Toyota|Sienna 2021-23|All|[Community](#community)| +|Toyota|Sienna 2021-25|All|[Community](#community)| |Toyota|Sienna 2024-25|Any|[Not compatible](#can-bus-security)| |Toyota|Tundra 2022-25|Any|[Not compatible](#can-bus-security)| |Toyota|Venza 2021-25|Any|[Not compatible](#can-bus-security)| @@ -444,4 +549,4 @@ Toyota, and the GM Global B platform. All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a CAN bus isn't the only way that the computers in your car can communicate. Most, if not all, vehicles from the following manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars -may one day be supported, but we have no immediate plans to support FlexRay. +may one day be supported, but we have no immediate plans to support FlexRay. \ No newline at end of file diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 49d7433db..1c09ae167 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -671,7 +671,7 @@ class CarController(CarControllerBase): stopping, hud_control, CS, CC, starpilot_toggles, lka_icon, lfa_icon)) else: can_sends.extend(self.create_can_msgs(apply_steer_req, apply_torque, torque_fault, set_speed_in_units, accel, - stopping, hud_control, actuators, CS, CC, lfa_icon)) + stopping, hud_control, actuators, CS, CC, lka_icon, lfa_icon)) new_actuators = actuators.as_builder() if self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING: @@ -686,7 +686,7 @@ class CarController(CarControllerBase): self.frame += 1 return new_actuators, can_sends - def create_can_msgs(self, apply_steer_req, apply_torque, torque_fault, set_speed_in_units, accel, stopping, hud_control, actuators, CS, CC, lfa_icon): + def create_can_msgs(self, apply_steer_req, apply_torque, torque_fault, set_speed_in_units, accel, stopping, hud_control, actuators, CS, CC, lka_icon, lfa_icon): can_sends = [] can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED) @@ -711,7 +711,7 @@ class CarController(CarControllerBase): can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req, torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled, hud_control.leftLaneVisible, hud_control.rightLaneVisible, - left_lane_warning, right_lane_warning)) + left_lane_warning, right_lane_warning, lka_icon)) # Button messages if not self.long_active_ecu: diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index eed419217..383f23f26 100644 --- a/opendbc_repo/opendbc/car/hyundai/carstate.py +++ b/opendbc_repo/opendbc/car/hyundai/carstate.py @@ -49,6 +49,15 @@ def calculate_canfd_speed_limit(CP, FPCP, cp, cp_cam, speed_factor): return 0.0 +def get_canfd_cruise_available(CP, cp, scc_available: bool) -> bool: + # The EV9 fallback keeps stock ACC active when ECU disable is skipped. Its + # SCC status remains inactive in that mode, while TCS still reports whether + # ACC is fault-free and available. + if CP.carFingerprint == CAR.KIA_EV9 and not CP.openpilotLongitudinalControl: + return cp.vl["TCS"]["ACCEnable"] == 0 + return scc_available + + def decode_ioniq_6_blindspot_radar_state(state: int) -> tuple[bool, bool]: state_int = int(state) return bool(state_int & IONIQ_6_BLINDSPOT_LEFT_MASK), bool(state_int & IONIQ_6_BLINDSPOT_RIGHT_MASK) @@ -522,7 +531,8 @@ class CarState(CarStateBase): ret.cruiseState.standstill = False else: cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp - ret.cruiseState.available = cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1 + ret.cruiseState.available = get_canfd_cruise_available( + self.CP, cp, cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1) ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaican.py b/opendbc_repo/opendbc/car/hyundai/hyundaican.py index 76e0fae25..8d3b37a38 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaican.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaican.py @@ -8,7 +8,7 @@ hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf) def create_lkas11(packer, frame, CP, apply_torque, steer_req, torque_fault, lkas11, sys_warning, sys_state, enabled, left_lane, right_lane, - left_lane_depart, right_lane_depart): + left_lane_depart, right_lane_depart, lka_icon): values = {s: lkas11[s] for s in [ "CF_Lkas_LdwsActivemode", "CF_Lkas_LdwsSysState", @@ -51,7 +51,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req, # FcwOpt_USM 2 = Green car + lanes # FcwOpt_USM 1 = White car + lanes # FcwOpt_USM 0 = No car + lanes - values["CF_Lkas_FcwOpt_USM"] = 2 if enabled else 1 + values["CF_Lkas_FcwOpt_USM"] = lka_icon if CP.carFingerprint == CAR.GENESIS_G70_2020 else 2 if enabled else 1 # SysWarning 4 = keep hands on wheel # SysWarning 5 = keep hands on wheel (red) diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index 750b38ee2..d8ac4d92d 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -17,7 +17,8 @@ from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalT should_reset_ev6_gt_line_longitudinal_tuning, reset_ev6_gt_line_longitudinal_tuning, \ direct_angle_request_allowed, get_angle_smoothing_alpha, \ should_use_ev6_gt_line_stop_direct_tracking -from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state +from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \ + get_canfd_cruise_available from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX from opendbc.car.hyundai import hyundaican, hyundaicanfd from opendbc.car.hyundai.hyundaicanfd import CanBus @@ -480,13 +481,35 @@ class TestHyundaiFingerprint: CC = SimpleNamespace(enabled=True, cruiseControl=SimpleNamespace(cancel=False, resume=False)) actuators = SimpleNamespace(longControlState=LongCtrlState.off) - msgs = controller.create_can_msgs(True, 100, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2) + msgs = controller.create_can_msgs(True, 100, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2) msg_addrs_buses = {(addr, bus) for addr, _, bus in msgs} assert (0x50, 0) in msg_addrs_buses assert (0x2A4, 0) in msg_addrs_buses assert not ({0x340, 0x364} & {addr for addr, _, _ in msgs}) + def test_g70_aol_uses_active_lkas_icon(self): + CP = CarInterface.get_params(CAR.GENESIS_G70_2020, gen_empty_fingerprint(), [], False, False, False, None) + controller = CarController(DBC[CP.carFingerprint], CP) + parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0) + + hud_control = SimpleNamespace( + visualAlert=CarControl.HUDControl.VisualAlert.none, + leftLaneVisible=True, + rightLaneVisible=True, + leftLaneDepart=False, + rightLaneDepart=False, + ) + CS = SimpleNamespace(lkas11=parser.vl["LKAS11"]) + CC = SimpleNamespace(enabled=False, cruiseControl=SimpleNamespace(cancel=False, resume=False)) + actuators = SimpleNamespace(longControlState=LongCtrlState.off) + + msgs = controller.create_can_msgs(True, 100, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0) + lkas11 = next(msg for msg in msgs if msg[0] == 0x340) + parser.update([(1, [lkas11])]) + + assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2 + @pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024)) def test_hyundai_can_refresh_platforms_use_refresh_dbc_and_safety_param(self, candidate): CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None) @@ -728,6 +751,18 @@ class TestHyundaiFingerprint: ret.buttonEvents = [structs.CarState.ButtonEvent(pressed=True, type=ButtonType.mainCruise)] assert not CarState.update_main_cruise(car_state, ret) + def test_ev9_stock_fallback_uses_tcs_cruise_availability(self): + CP = SimpleNamespace(carFingerprint=CAR.KIA_EV9, openpilotLongitudinalControl=False) + cp = SimpleNamespace(vl={"TCS": {"ACCEnable": 0}}) + + assert get_canfd_cruise_available(CP, cp, False) + + cp.vl["TCS"]["ACCEnable"] = 1 + assert not get_canfd_cruise_available(CP, cp, True) + + other_cp = SimpleNamespace(carFingerprint=CAR.HYUNDAI_IONIQ_6, openpilotLongitudinalControl=False) + assert not get_canfd_cruise_available(other_cp, cp, False) + def test_palisade_2023_cancel_release_enables_from_standby(self): toggles = get_test_toggles() CP = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, toggles) diff --git a/opendbc_repo/opendbc/car/subaru/carcontroller.py b/opendbc_repo/opendbc/car/subaru/carcontroller.py index c5f5db09a..0c3a5356b 100644 --- a/opendbc_repo/opendbc/car/subaru/carcontroller.py +++ b/opendbc_repo/opendbc/car/subaru/carcontroller.py @@ -35,7 +35,7 @@ class CarController(CarControllerBase): self.packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) self.main_bus = CanBus.main_for_cp(CP) self.angle_bus = CanBus.angle_for_cp(CP) - self.status_bus = CanBus.camera if CP.flags & SubaruFlags.D_PLATFORM else CanBus.main + self.status_bus = CanBus.main if CP.flags & SubaruFlags.LKAS_ANGLE: self.VM = VehicleModel(get_safety_CP()) diff --git a/opendbc_repo/opendbc/car/subaru/carstate.py b/opendbc_repo/opendbc/car/subaru/carstate.py index 899cc7dfc..928eb7dcb 100644 --- a/opendbc_repo/opendbc/car/subaru/carstate.py +++ b/opendbc_repo/opendbc/car/subaru/carstate.py @@ -21,7 +21,7 @@ class CarState(CarStateBase): cp_cam = can_parsers[Bus.cam] cp_alt = can_parsers[Bus.alt] cp_main = can_parsers[Bus.main] if self.CP.flags & SubaruFlags.D_PLATFORM else cp - cp_angle = cp_cam if self.CP.flags & SubaruFlags.D_PLATFORM else cp + cp_angle = cp_main if self.CP.flags & SubaruFlags.D_PLATFORM else cp ret = structs.CarState() throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"] diff --git a/opendbc_repo/opendbc/car/subaru/fingerprints.py b/opendbc_repo/opendbc/car/subaru/fingerprints.py index 799b48140..300d3623f 100644 --- a/opendbc_repo/opendbc/car/subaru/fingerprints.py +++ b/opendbc_repo/opendbc/car/subaru/fingerprints.py @@ -37,15 +37,19 @@ FW_VERSIONS = { CAR.SUBARU_ASCENT_2023: { (Ecu.abs, 0x7b0, None): [ b'\xa5 #\x03\x00', + b'\xa5 %\x03\x01', ], (Ecu.eps, 0x746, None): [ b'%\xc0\xd0\x11', + b'\x55\xc0\xd0\x10', ], (Ecu.fwdCamera, 0x787, None): [ b'\x05!\x08\x1dK\x05!\x08\x01/', + b'\x17!\x08\x01A\x12!\x08\x00;', ], (Ecu.engine, 0x7a2, None): [ b'\xe5,\xa0P\x07', + b'\x11,\xa00\x07', ], (Ecu.transmission, 0x7a3, None): [ b'\x04\xfe\xf3\x00\x00', diff --git a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py index 05a0b7b18..a2881deb7 100644 --- a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py +++ b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py @@ -99,6 +99,17 @@ class TestSubaruFingerprint: assert exact assert matches == {CAR.SUBARU_LEGACY_2025} + def test_ascent_2025_firmware(self): + car_fw = [ + CarParams.CarFw(ecu=CarParams.Ecu.abs, fwVersion=b'\xa5 %\x03\x01', address=0x7b0, brand="subaru"), + CarParams.CarFw(ecu=CarParams.Ecu.eps, fwVersion=b'\x55\xc0\xd0\x10', address=0x746, brand="subaru"), + CarParams.CarFw(ecu=CarParams.Ecu.fwdCamera, fwVersion=b'\x17!\x08\x01A\x12!\x08\x00;', address=0x787, brand="subaru"), + CarParams.CarFw(ecu=CarParams.Ecu.engine, fwVersion=b'\x11,\xa00\x07', address=0x7a2, brand="subaru"), + ] + exact, matches = match_fw_to_car(car_fw, "4S4WMAAD9S3414980", allow_fuzzy=False, log=False) + assert exact + assert matches == {CAR.SUBARU_ASCENT_2023} + ANGLE_PLATFORMS = ( CAR.SUBARU_FORESTER_2022, @@ -136,13 +147,13 @@ def test_outback_2023_uses_d_platform_bus_layout(): assert CP.flags & SubaruFlags.D_PLATFORM assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM assert CanBus.main_for_cp(CP) == CanBus.alt - assert CanBus.angle_for_cp(CP) == CanBus.camera + assert CanBus.angle_for_cp(CP) == CanBus.main assert parsers[Bus.pt].bus == CanBus.alt assert parsers[Bus.cam].bus == CanBus.camera assert parsers[Bus.alt].bus == CanBus.alt assert parsers[Bus.main].bus == CanBus.main - assert controller.angle_bus == CanBus.camera - assert controller.status_bus == CanBus.camera + assert controller.angle_bus == CanBus.main + assert controller.status_bus == CanBus.main def test_legacy_2025_uses_d_platform_bus_layout(): @@ -153,13 +164,30 @@ def test_legacy_2025_uses_d_platform_bus_layout(): assert CP.flags & SubaruFlags.D_PLATFORM assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM assert CanBus.main_for_cp(CP) == CanBus.alt - assert CanBus.angle_for_cp(CP) == CanBus.camera + assert CanBus.angle_for_cp(CP) == CanBus.main assert parsers[Bus.pt].bus == CanBus.alt assert parsers[Bus.cam].bus == CanBus.camera assert parsers[Bus.alt].bus == CanBus.alt assert parsers[Bus.main].bus == CanBus.main - assert controller.angle_bus == CanBus.camera - assert controller.status_bus == CanBus.camera + assert controller.angle_bus == CanBus.main + assert controller.status_bus == CanBus.main + + +def test_ascent_2023_uses_d_platform_bus_layout(): + CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023) + parsers = CarState.get_can_parsers(CP) + controller = CarController({}, CP) + + assert CP.flags & SubaruFlags.D_PLATFORM + assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM + assert CanBus.main_for_cp(CP) == CanBus.alt + assert CanBus.angle_for_cp(CP) == CanBus.main + assert parsers[Bus.pt].bus == CanBus.alt + assert parsers[Bus.cam].bus == CanBus.camera + assert parsers[Bus.alt].bus == CanBus.alt + assert parsers[Bus.main].bus == CanBus.main + assert controller.angle_bus == CanBus.main + assert controller.status_bus == CanBus.main def test_other_angle_platforms_keep_existing_bus_layout(): diff --git a/opendbc_repo/opendbc/car/subaru/values.py b/opendbc_repo/opendbc/car/subaru/values.py index 48786ab7e..5e0b4b166 100644 --- a/opendbc_repo/opendbc/car/subaru/values.py +++ b/opendbc_repo/opendbc/car/subaru/values.py @@ -113,8 +113,9 @@ class CanBus: @staticmethod def angle_for_cp(CP): - # D-platform angle LKAS is exchanged with the camera ECU on the camera bus. - return CanBus.camera if CP.flags & SubaruFlags.D_PLATFORM else CanBus.main + # D-platform angle commands reach the EPS through the main bus. The camera + # bus still carries the stock angle message and remains receive-only. + return CanBus.main class Footnote(Enum): @@ -244,9 +245,9 @@ class CAR(Platforms): flags=SubaruFlags.LKAS_ANGLE | SubaruFlags.D_PLATFORM, ) SUBARU_ASCENT_2023 = SubaruGen2PlatformConfig( - [SubaruCarDocs("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))], + [SubaruCarDocs("Subaru Ascent 2023-25", "All", car_parts=CarParts.common([CarHarness.subaru_d]))], SUBARU_ASCENT.specs, - flags=SubaruFlags.LKAS_ANGLE, + flags=SubaruFlags.LKAS_ANGLE | SubaruFlags.D_PLATFORM, ) SUBARU_CROSSTREK_2025 = SubaruGen2PlatformConfig( [SubaruCarDocs("Subaru Crosstrek 2025", "All", car_parts=CarParts.common([CarHarness.subaru_d]))], diff --git a/opendbc_repo/opendbc/safety/modes/subaru.h b/opendbc_repo/opendbc/safety/modes/subaru.h index 94f69cedf..f655133cc 100644 --- a/opendbc_repo/opendbc/safety/modes/subaru.h +++ b/opendbc_repo/opendbc/safety/modes/subaru.h @@ -56,10 +56,10 @@ {MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = false}, \ #define SUBARU_D_PLATFORM_ANGLE_TX_MSGS() \ - {MSG_SUBARU_ES_LKAS_ANGLE, SUBARU_CAM_BUS, 8, .check_relay = true}, \ - {MSG_SUBARU_ES_DashStatus, SUBARU_CAM_BUS, 8, .check_relay = true}, \ - {MSG_SUBARU_ES_LKAS_State, SUBARU_CAM_BUS, 8, .check_relay = true}, \ - {MSG_SUBARU_ES_Infotainment, SUBARU_CAM_BUS, 8, .check_relay = true}, \ + {MSG_SUBARU_ES_LKAS_ANGLE, SUBARU_MAIN_BUS, 8, .check_relay = true}, \ + {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8, .check_relay = true}, \ + {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8, .check_relay = true}, \ + {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8, .check_relay = true}, \ #define SUBARU_COMMON_LONG_TX_MSGS(alt_bus) \ {MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = true}, \ @@ -93,8 +93,8 @@ #define SUBARU_D_PLATFORM_ANGLE_RX_CHECKS() \ {.msg = {{MSG_SUBARU_Throttle, SUBARU_ALT_BUS, 8, 100U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ - {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_CAM_BUS, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ - {.msg = {{MSG_SUBARU_Steering_2, SUBARU_CAM_BUS, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_MAIN_BUS, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Steering_2, SUBARU_MAIN_BUS, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{MSG_SUBARU_Wheel_Speeds, SUBARU_ALT_BUS, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{MSG_SUBARU_Brake_Status, SUBARU_ALT_BUS, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{MSG_SUBARU_ES_Brake, SUBARU_ALT_BUS, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ @@ -127,7 +127,7 @@ static uint32_t subaru_compute_checksum(const CANPacket_t *msg) { static void subaru_rx_hook(const CANPacket_t *msg) { const unsigned int alt_main_bus = subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS; const unsigned int status_bus = subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_CAM_BUS; - const unsigned int steering_bus = subaru_d_platform ? SUBARU_CAM_BUS : SUBARU_MAIN_BUS; + const unsigned int steering_bus = SUBARU_MAIN_BUS; const unsigned int main_bus = subaru_d_platform ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS; if ((msg->addr == MSG_SUBARU_Steering_Torque) && (msg->bus == steering_bus)) { diff --git a/opendbc_repo/opendbc/safety/tests/common.py b/opendbc_repo/opendbc/safety/tests/common.py index a1c215098..413bc6e91 100644 --- a/opendbc_repo/opendbc/safety/tests/common.py +++ b/opendbc_repo/opendbc/safety/tests/common.py @@ -970,6 +970,10 @@ class SafetyTest(SafetyTestBase): if 'TestSubaruDPlatformAngleSafety' in {attr, current_test} and \ 'Angle' in attr and 'Angle' in current_test: continue + if 'TestSubaruDPlatformAngleSafety' in {attr, current_test}: + # D-platform uses the same main-bus HUD messages as the other + # Subaru modes, so those modes cannot be distinguished by ID. + tx = list(filter(lambda m: not (m[1] == 0 and m[0] in [0x321, 0x322, 0x323]), tx)) if attr.startswith('TestSubaruPreglobal') and current_test.startswith('TestSubaruPreglobal'): continue if {attr, current_test}.issubset({'TestVolkswagenPqSafety', 'TestVolkswagenPqStockSafety', 'TestVolkswagenPqLongSafety'}): diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru.py b/opendbc_repo/opendbc/safety/tests/test_subaru.py index 65291ac7d..d4a153abc 100755 --- a/opendbc_repo/opendbc/safety/tests/test_subaru.py +++ b/opendbc_repo/opendbc/safety/tests/test_subaru.py @@ -343,15 +343,16 @@ class TestSubaruGen2AngleStockLongitudinalSafety(TestSubaruStockLongitudinalSafe class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruAngleSafetyBase): FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM ALT_MAIN_BUS = SUBARU_ALT_BUS - TX_MSGS = [[SubaruMsg.ES_LKAS_ANGLE, SUBARU_CAM_BUS], - [SubaruMsg.ES_DashStatus, SUBARU_CAM_BUS], - [SubaruMsg.ES_LKAS_State, SUBARU_CAM_BUS], - [SubaruMsg.ES_Infotainment, SUBARU_CAM_BUS], + TX_MSGS = [[SubaruMsg.ES_LKAS_ANGLE, SUBARU_MAIN_BUS], + [SubaruMsg.ES_DashStatus, SUBARU_MAIN_BUS], + [SubaruMsg.ES_LKAS_State, SUBARU_MAIN_BUS], + [SubaruMsg.ES_Infotainment, SUBARU_MAIN_BUS], [SubaruMsg.ES_Distance, SUBARU_ALT_BUS]] - RELAY_MALFUNCTION_ADDRS = {SUBARU_CAM_BUS: (SubaruMsg.ES_LKAS_ANGLE, SubaruMsg.ES_DashStatus, + RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS_ANGLE, + SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State, SubaruMsg.ES_Infotainment)} FWD_BLACKLISTED_ADDRS = { - SUBARU_MAIN_BUS: [SubaruMsg.ES_LKAS_ANGLE, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State, SubaruMsg.ES_Infotainment], + SUBARU_CAM_BUS: [SubaruMsg.ES_LKAS_ANGLE, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State, SubaruMsg.ES_Infotainment], } def _torque_driver_msg(self, torque): @@ -365,10 +366,10 @@ class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, Test self.safety.set_timer(self.angle_cmd_cnt * int(1e6 / self.LATERAL_FREQUENCY)) self.angle_cmd_cnt += 1 values = {"LKAS_Output": angle, "LKAS_Request": enabled, "SET_3": 3} - return self.packer.make_can_msg_safety("ES_LKAS_ANGLE", SUBARU_CAM_BUS, values) + return self.packer.make_can_msg_safety("ES_LKAS_ANGLE", SUBARU_MAIN_BUS, values) def _angle_meas_msg(self, angle): - return self.packer.make_can_msg_safety("Steering_2", SUBARU_CAM_BUS, {"Steering_Angle": angle}) + return self.packer.make_can_msg_safety("Steering_2", SUBARU_MAIN_BUS, {"Steering_Angle": angle}) class TestSubaruGen2LongitudinalSafety(TestSubaruLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase): diff --git a/scripts/model_compiler.py b/scripts/model_compiler.py index 83045bf4e..7e36fbd1e 100644 --- a/scripts/model_compiler.py +++ b/scripts/model_compiler.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import argparse import codecs +import ctypes +import glob import hashlib import json import os @@ -42,8 +44,17 @@ MODEL_RUN_FREQ = 20 MODEL_CONTEXT_FREQ = 5 REPOSITORY_FILE_LIMIT = 100 * 1024 * 1024 DEFAULT_MULTIPART_SIZE = 95 * 1024 * 1024 -USBGPU_PROBE_ATTEMPTS = 3 -USBGPU_PROBE_TIMEOUT = 10 +USBGPU_PROBE_ATTEMPTS = 10 +USBGPU_PROBE_TIMEOUT = 2 +USBDEVFS_CONTROL = 0xC0185500 +USBGPU_VID_PIDS = (("add1", "0001"), ("3801", "0001")) + + +class _UsbdevfsControl(ctypes.Structure): + _fields_ = [("request_type", ctypes.c_uint8), ("request", ctypes.c_uint8), + ("value", ctypes.c_uint16), ("index", ctypes.c_uint16), + ("length", ctypes.c_uint16), ("timeout", ctypes.c_uint32), + ("data", ctypes.c_void_p)] def build_compile_env() -> dict[str, str]: @@ -65,49 +76,65 @@ def build_compile_env() -> dict[str, str]: return env +def _probe_external_gpu_link_once() -> tuple[bool, str]: + """Probe the bridge without initializing tinygrad or resetting the USB device.""" + import fcntl + + diagnostics: list[str] = [] + for path in glob.glob("/sys/bus/usb/devices/*"): + try: + vendor = Path(path, "idVendor").read_text().strip().lower() + product = Path(path, "idProduct").read_text().strip().lower() + if (vendor, product) not in USBGPU_VID_PIDS: + continue + bus = int(Path(path, "busnum").read_text()) + device = int(Path(path, "devnum").read_text()) + location = f"usb:{bus}-{device}" + fd = os.open(f"/dev/bus/usb/{bus:03d}/{device:03d}", os.O_RDWR) + except (OSError, ValueError) as exc: + diagnostics.append(f"{path}: open failed ({exc})") + continue + + try: + fcntl.ioctl(fd, USBDEVFS_CONTROL, _UsbdevfsControl(0x40, 0xF3, 1, 0, 0, USBGPU_PROBE_TIMEOUT * 1000, None)) + state = (ctypes.c_ubyte * 1)() + fcntl.ioctl(fd, USBDEVFS_CONTROL, _UsbdevfsControl(0xC0, 0xE4, 0xB450, 0, 1, 1000, ctypes.cast(state, ctypes.c_void_p))) + if state[0] == 0x78: + return True, f"{location}: LTSSM=0x78" + diagnostics.append(f"{location}: LTSSM=0x{state[0]:02X}") + except OSError as exc: + diagnostics.append(f"{location}: control probe failed ({exc})") + finally: + os.close(fd) + return False, diagnostics[-1] if diagnostics else "no ASM2464PD device found" + + def wait_for_external_gpu(compile_env: dict[str, str]) -> bool: """Wait for the USB GPU's PCIe link before starting the large model build. The dock can enumerate on USB before its PCIe link has finished training. - OpenPilot probes the tinygrad device in a short-lived process and retries; - doing the same here avoids making the model compiler lose its one chance at - initialization while keeping all non-GPU builds unchanged. + Probe the bridge's control endpoint directly, like upstream openpilot. Do + not instantiate tinygrad here: opening the GPU resets/claims the USB + interface, and doing that in a probe process can leave the bridge in a state + where the authoritative compiler cannot train the link. """ - probe = [sys.executable, "-c", "from tinygrad.device import Device; Device[Device.DEFAULT]; import os; os._exit(0)"] - probe_env = {**compile_env, "DEV": "USB+AMD"} + del compile_env # retained in the public helper signature for callers/tests diagnostics: list[str] = [] for attempt in range(USBGPU_PROBE_ATTEMPTS): if attempt: time.sleep(1) try: - result = subprocess.run( - probe, - cwd=REPO_ROOT, - env=probe_env, - capture_output=True, - text=True, - timeout=USBGPU_PROBE_TIMEOUT, - check=False, - ) - except subprocess.TimeoutExpired as exc: - partial = exc.stderr or exc.stdout or "" - if isinstance(partial, bytes): - partial = partial.decode(errors="replace") - partial = partial.strip() - diagnostics.append( - f"probe timed out after {USBGPU_PROBE_TIMEOUT}s" + (f": {partial[-2000:]}" if partial else "") - ) - continue - - if result.returncode == 0: + ready, detail = _probe_external_gpu_link_once() + except Exception as exc: # probe is advisory; compile_modeld remains authoritative + ready, detail = False, str(exc) + if ready: return True - detail = (result.stderr or result.stdout).strip() - diagnostics.append((detail[-2000:] if detail else f"probe exited with status {result.returncode}")) + diagnostics.append(detail) detail = diagnostics[-1] if diagnostics else "unknown error" print( - f"Warning: external GPU probe did not become ready after {USBGPU_PROBE_ATTEMPTS} probes: {detail}\n" + f"Warning: external GPU link did not become ready after {USBGPU_PROBE_ATTEMPTS} probes: {detail}\n" " Continuing; compile_modeld will perform the authoritative link wait and initialization." ) return False diff --git a/selfdrive/car/cruise.py b/selfdrive/car/cruise.py index 9945c34f0..e2f770bb6 100644 --- a/selfdrive/car/cruise.py +++ b/selfdrive/car/cruise.py @@ -49,6 +49,9 @@ class VCruiseHelper: } self.button_hard_states = dict.fromkeys(self.button_timers, False) self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers} + # Keep the confirmation button from leaking into cruise-speed handling when the + # planner clears the confirmation state between the press and release events. + self.confirmation_button_suppressed = set() self.gm_cc_only = self.CP.carFingerprint in CC_ONLY_CAR and self.CP.flags & GMFlags.CC_LONG.value self.redneck_non_pcm = bool(FPCP is not None and @@ -120,6 +123,15 @@ class VCruiseHelper: v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT + for b in CS.buttonEvents: + event_button_type = b.type.raw + if event_button_type in self.button_timers: + if speed_limit_changed and b.pressed: + self.confirmation_button_suppressed.add(event_button_type) + elif not b.pressed and event_button_type in self.confirmation_button_suppressed: + self.confirmation_button_suppressed.remove(event_button_type) + return + for b in CS.buttonEvents: if b.type.raw in self.button_timers and not b.pressed: if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS: @@ -138,6 +150,9 @@ class VCruiseHelper: if button_type is None: return + if button_type in self.confirmation_button_suppressed: + return + # Don't adjust speed when pressing to confirm or deny speed limit changes if speed_limit_changed: return diff --git a/selfdrive/car/redneck_cruise.py b/selfdrive/car/redneck_cruise.py index 15fe5a4cf..e4a32130f 100644 --- a/selfdrive/car/redneck_cruise.py +++ b/selfdrive/car/redneck_cruise.py @@ -2,6 +2,7 @@ from cereal import car from opendbc.car import apply_hysteresis from openpilot.common.constants import CV from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET ButtonType = car.CarState.ButtonEvent.Type @@ -50,6 +51,10 @@ def select_redneck_target_speed(v_cruise_kph: float, speed_cluster_ms: float, target_speed_ms = float(speed_cluster_ms) if slc_target_speed_ms > 0: target_speed_ms = float(slc_target_speed_ms) + # SLC is an upper bound for the button-spammed stock setpoint. A driver-set + # speed below the posted target must still be able to slow the car down. + if 0 < v_cruise_kph < V_CRUISE_UNSET: + target_speed_ms = min(target_speed_ms, float(v_cruise_kph) * CV.KPH_TO_MS) elif v_cruise_kph > 0: target_speed_ms = float(v_cruise_kph) * CV.KPH_TO_MS elif starpilot_target_speed_ms > 0: diff --git a/selfdrive/car/tests/test_cruise_speed.py b/selfdrive/car/tests/test_cruise_speed.py index 8ef58a083..f9db1b678 100644 --- a/selfdrive/car/tests/test_cruise_speed.py +++ b/selfdrive/car/tests/test_cruise_speed.py @@ -330,6 +330,35 @@ class TestVCruiseHelper: assert self.v_cruise_helper.v_cruise_kph == initial_v_cruise_kph + def test_speed_limit_confirmation_press_release_does_not_leak_after_acceptance(self): + for button_type in (ButtonType.accelCruise, ButtonType.decelCruise): + self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False) + initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph + + pressed_cs = car.CarState(cruiseState={"available": True}) + pressed_cs.buttonEvents = [ButtonEvent(type=button_type, pressed=True)] + self.v_cruise_helper.update_v_cruise( + pressed_cs, + enabled=True, + is_metric=False, + speed_limit_changed=True, + starpilot_toggles=self.starpilot_toggles, + ) + + # The planner may have consumed the confirmation by the time the physical + # button release arrives. The release must remain confirmation-only. + released_cs = car.CarState(cruiseState={"available": True}) + released_cs.buttonEvents = [ButtonEvent(type=button_type, pressed=False)] + self.v_cruise_helper.update_v_cruise( + released_cs, + enabled=True, + is_metric=False, + speed_limit_changed=False, + starpilot_toggles=self.starpilot_toggles, + ) + + assert self.v_cruise_helper.v_cruise_kph == initial_v_cruise_kph + def test_stale_speed_limit_change_does_adjust_cruise(self): self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False) initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph diff --git a/selfdrive/car/tests/test_redneck_cruise.py b/selfdrive/car/tests/test_redneck_cruise.py index 8513195fe..c192dbb3e 100644 --- a/selfdrive/car/tests/test_redneck_cruise.py +++ b/selfdrive/car/tests/test_redneck_cruise.py @@ -170,8 +170,8 @@ class TestRedneckCruise(unittest.TestCase): ) self.assertAlmostEqual(104.4 * CV.KPH_TO_MS, target_speed) - def test_target_speed_follows_resolved_slc_target(self): - for internal_mph, slc_mph in ((55.0, 65.0), (65.0, 55.0)): + def test_target_speed_respects_manual_lower_set_speed_with_slc(self): + for internal_mph, slc_mph, expected_mph in ((55.0, 65.0, 55.0), (65.0, 55.0, 55.0)): with self.subTest(internal_mph=internal_mph, slc_mph=slc_mph): target_speed = select_redneck_target_speed( internal_mph * CV.MPH_TO_KPH, @@ -182,7 +182,20 @@ class TestRedneckCruise(unittest.TestCase): allow_plan_decrease=False, slc_target_speed_ms=slc_mph * CV.MPH_TO_MS, ) - self.assertAlmostEqual(slc_mph * CV.MPH_TO_MS, target_speed) + self.assertAlmostEqual(expected_mph * CV.MPH_TO_MS, target_speed) + + def test_target_speed_keeps_slc_limit_when_manual_set_speed_is_higher(self): + target_speed = select_redneck_target_speed( + 75.0 * CV.MPH_TO_KPH, + 65.0 * CV.MPH_TO_MS, + 0.0, + [], + 10, + allow_plan_decrease=False, + slc_target_speed_ms=65.0 * CV.MPH_TO_MS, + ) + + self.assertAlmostEqual(65.0 * CV.MPH_TO_MS, target_speed) def test_card_target_speed_uses_longitudinal_acceleration(self): sm = MagicMock() diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 9371d730c..4e0d3fbca 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -113,6 +113,7 @@ class LatControlTorque(LatControl): self.is_ioniq_5 = CP.carFingerprint in IONIQ_5_CARS self.is_ioniq_ev_old = CP.carFingerprint in IONIQ_EV_OLD_CARS self.is_ioniq_6 = CP.carFingerprint in IONIQ_6_CARS + self.is_ioniq_6_2025 = is_ioniq_6_2025_model(CP) self.is_sonata = CP.carFingerprint in SONATA_CARS self.is_sonata_hybrid = CP.carFingerprint in SONATA_HYBRID_CARS self.is_elantra_non_scc = CP.carFingerprint in ELANTRA_NON_SCC_CARS @@ -251,9 +252,10 @@ class LatControlTorque(LatControl): delay_frames = int(np.clip(lat_delay / self.dt, 1, self.request_buffer_len)) expected_lateral_accel = self.curvature_request_buffer[-delay_frames] * CS.vEgo ** 2 self.curvature_request_buffer.append(desired_curvature) + lateral_jerk_limit = RAM_1500_MAX_LAT_JERK_UP if self.is_ram_1500 else MAX_LAT_JERK_UP raw_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / max(lat_delay, self.dt) - raw_lateral_jerk = np.clip(raw_lateral_jerk, -MAX_LAT_JERK_UP, MAX_LAT_JERK_UP) - desired_lateral_jerk = np.clip(self.jerk_filter.update(raw_lateral_jerk), -MAX_LAT_JERK_UP, MAX_LAT_JERK_UP) + raw_lateral_jerk = np.clip(raw_lateral_jerk, -lateral_jerk_limit, lateral_jerk_limit) + desired_lateral_jerk = np.clip(self.jerk_filter.update(raw_lateral_jerk), -lateral_jerk_limit, lateral_jerk_limit) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation setpoint = expected_lateral_accel + desired_lateral_jerk * lat_delay desired_lateral_accel_rate = (setpoint - self.prev_desired_lateral_accel) / self.dt @@ -393,6 +395,8 @@ class LatControlTorque(LatControl): friction_scale = get_ioniq_6_friction_scale(CS.vEgo, setpoint, desired_lateral_jerk) friction_scale = 1.0 + ((friction_scale - 1.0) * ioniq_6_center_taper) friction_scale *= get_ioniq_6_friction_center_fade_scale(setpoint, CS.vEgo) + if self.is_ioniq_6_2025: + friction_scale *= IONIQ_6_2025_FRICTION_SCALE_MULT elif sonata_active: ff *= get_sonata_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) * sonata_center_taper elif sonata_hybrid_active: @@ -416,6 +420,8 @@ class LatControlTorque(LatControl): elif kia_carnival_active: friction_threshold = get_kia_carnival_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk) friction_scale *= get_kia_carnival_friction_center_fade_scale(setpoint, CS.vEgo) + elif self.is_kona_non_scc: + friction_threshold = get_kona_non_scc_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk) elif tucson_4th_gen_active: friction_threshold = get_tucson_4th_gen_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk) elif self.is_silverado: @@ -442,7 +448,14 @@ class LatControlTorque(LatControl): if trailer_load_kg > 0.0: ff *= get_trailer_lateral_ff_scale(trailer_load_kg, CS.vEgo, setpoint) friction_scale *= get_trailer_lateral_friction_scale(trailer_load_kg, CS.vEgo, setpoint) - vehicle_friction_jerk_deadzone = IONIQ_6_FRICTION_JERK_DEADZONE if ioniq_6_active else 0.0 + if ioniq_6_active: + vehicle_friction_jerk_deadzone = ( + IONIQ_6_2025_FRICTION_JERK_DEADZONE if self.is_ioniq_6_2025 else IONIQ_6_FRICTION_JERK_DEADZONE + ) + elif prius_active: + vehicle_friction_jerk_deadzone = get_prius_friction_jerk_deadzone(CS.vEgo, setpoint) + else: + vehicle_friction_jerk_deadzone = 0.0 friction_jerk_deadzone = get_center_chatter_friction_jerk_deadzone( CS.vEgo, setpoint, vehicle_friction_jerk_deadzone ) @@ -486,6 +499,8 @@ class LatControlTorque(LatControl): if ioniq_6_active: output_torque *= get_ioniq_6_highway_output_taper_scale(setpoint, CS.vEgo) output_torque *= get_ioniq_6_highway_transition_output_taper_scale(setpoint, desired_lateral_jerk, CS.vEgo) + if self.is_ioniq_6_2025: + output_torque *= get_ioniq_6_2025_center_output_scale(setpoint, CS.vEgo) elif self.is_ram_1500 and output_torque * setpoint > 0.0: output_torque *= get_ram_1500_transition_output_scale(setpoint, desired_lateral_jerk, CS.vEgo) elif self.is_kona_non_scc: @@ -500,6 +515,7 @@ class LatControlTorque(LatControl): output_torque *= get_sienna_4th_gen_high_speed_output_taper_scale(CS.vEgo) elif prius_active: output_torque *= prius_center_taper + output_torque *= get_prius_high_speed_output_taper_scale(setpoint, CS.vEgo) elif volt_standard_test_active: output_torque *= volt_standard_center_taper elif volt_plexy_test_active: diff --git a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py index 586317ab5..878855c2c 100644 --- a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py @@ -104,6 +104,24 @@ IONIQ_EV_OLD_CARS = ( IONIQ_6_CARS = ( HYUNDAI_CAR.HYUNDAI_IONIQ_6, ) + + +def is_ioniq_6_2025_model(CP) -> bool: + """Identify the newer Ioniq 6 firmware without changing the legacy 2023 path.""" + if getattr(CP, "carFingerprint", None) not in IONIQ_6_CARS: + return False + + versions = [] + try: + for fw in CP.carFw: + value = fw.fwVersion + versions.append(value.decode("ascii", errors="ignore") if isinstance(value, bytes) else str(value)) + except (AttributeError, TypeError, ValueError): + return False + + return any("230915" in version for version in versions) and any("240206" in version for version in versions) + + SONATA_HYBRID_CARS = ( HYUNDAI_CAR.HYUNDAI_SONATA_HYBRID, ) @@ -722,6 +740,14 @@ IONIQ_6_FRICTION_CENTER_FADE_LAT = 0.15 IONIQ_6_FRICTION_CENTER_FADE_LAT_WIDTH = 0.06 IONIQ_6_FRICTION_CENTER_FADE_SPEED = 18.0 IONIQ_6_FRICTION_CENTER_FADE_SPEED_WIDTH = 2.5 +# Newer Ioniq 6 highway center-chatter correction; activation is firmware-gated. +IONIQ_6_2025_FRICTION_SCALE_MULT = 0.80 +IONIQ_6_2025_FRICTION_JERK_DEADZONE = 0.45 +IONIQ_6_2025_CENTER_OUTPUT_TAPER_MAX = 0.18 +IONIQ_6_2025_CENTER_OUTPUT_TAPER_LAT = 0.35 +IONIQ_6_2025_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.10 +IONIQ_6_2025_CENTER_OUTPUT_TAPER_SPEED = 22.0 +IONIQ_6_2025_CENTER_OUTPUT_TAPER_SPEED_WIDTH = 2.5 IONIQ_6_HEAVY_DIRECTIONAL_TAPER_LAT_START = 0.90 IONIQ_6_HEAVY_DIRECTIONAL_TAPER_LAT_WIDTH = 0.18 IONIQ_6_HEAVY_DIRECTIONAL_TAPER_BASE_LEFT = 0.03 @@ -774,7 +800,7 @@ KIA_EV6_CENTER_TAPER_LAT = 0.16 KIA_EV6_CENTER_TAPER_LAT_WIDTH = 0.04 KIA_EV6_CENTER_TAPER_SPEED = 17.0 KIA_EV6_CENTER_TAPER_SPEED_WIDTH = 2.8 -KIA_EV6_CENTER_FRICTION_THRESHOLD_GAIN = 0.08 +KIA_EV6_CENTER_FRICTION_THRESHOLD_GAIN = 0.14 KIA_EV6_CENTER_FRICTION_THRESHOLD_LAT = 0.30 KIA_EV6_CENTER_FRICTION_THRESHOLD_LAT_WIDTH = 0.07 KIA_EV6_CENTER_FRICTION_THRESHOLD_SPEED = 18.0 @@ -816,7 +842,7 @@ PRIUS_TURN_IN_FRICTION_BOOST_RIGHT = 0.06 PRIUS_UNWIND_FRICTION_REDUCTION_LEFT = 0.22 PRIUS_UNWIND_FRICTION_REDUCTION_RIGHT = 0.30 PRIUS_CENTER_TAPER_MAX = 0.15 -PRIUS_CENTER_TAPER_LAT = 0.16 +PRIUS_CENTER_TAPER_LAT = 0.24 PRIUS_CENTER_TAPER_LAT_WIDTH = 0.035 PRIUS_CENTER_TAPER_SPEED = 18.0 PRIUS_CENTER_TAPER_SPEED_WIDTH = 2.2 @@ -825,6 +851,16 @@ PRIUS_CENTER_FRICTION_THRESHOLD_LAT = 0.30 PRIUS_CENTER_FRICTION_THRESHOLD_LAT_WIDTH = 0.07 PRIUS_CENTER_FRICTION_THRESHOLD_SPEED = 18.0 PRIUS_CENTER_FRICTION_THRESHOLD_SPEED_WIDTH = 2.2 +PRIUS_FRICTION_JERK_DEADZONE_MAX = 0.24 +PRIUS_FRICTION_JERK_DEADZONE_LAT = 0.30 +PRIUS_FRICTION_JERK_DEADZONE_LAT_WIDTH = 0.07 +PRIUS_FRICTION_JERK_DEADZONE_SPEED = 18.0 +PRIUS_FRICTION_JERK_DEADZONE_SPEED_WIDTH = 2.2 +PRIUS_HIGH_SPEED_OUTPUT_TAPER_MAX = 0.06 +PRIUS_HIGH_SPEED_OUTPUT_TAPER_LAT = 0.30 +PRIUS_HIGH_SPEED_OUTPUT_TAPER_LAT_WIDTH = 0.35 +PRIUS_HIGH_SPEED_OUTPUT_TAPER_SPEED = 22.0 +PRIUS_HIGH_SPEED_OUTPUT_TAPER_SPEED_WIDTH = 2.5 CAMRY_CENTER_FRICTION_THRESHOLD_GAIN = 0.09 CAMRY_CENTER_FRICTION_THRESHOLD_LAT = 0.22 @@ -873,6 +909,9 @@ SIENNA_4TH_GEN_FRICTION_SPEED_ONSET = 3.0 SIENNA_4TH_GEN_FRICTION_SPEED_WIDTH = 1.5 SIENNA_4TH_GEN_FRICTION_SPEED_MAX = 14.0 SIENNA_4TH_GEN_FRICTION_SPEED_MAX_WIDTH = 2.0 +SIENNA_4TH_GEN_HIGH_SPEED_FRICTION_THRESHOLD_GAIN = 0.14 +SIENNA_4TH_GEN_HIGH_SPEED_FRICTION_SPEED_ONSET = 18.0 +SIENNA_4TH_GEN_HIGH_SPEED_FRICTION_SPEED_WIDTH = 2.5 SIENNA_4TH_GEN_CENTER_TAPER_MAX = 0.12 SIENNA_4TH_GEN_CENTER_TAPER_LAT = 0.20 SIENNA_4TH_GEN_CENTER_TAPER_LAT_WIDTH = 0.06 @@ -910,13 +949,14 @@ RAM_1500_TRANSITION_JERK_ONSET = 0.35 RAM_1500_TRANSITION_JERK_FULL = 1.10 RAM_1500_TRANSITION_LAT_FADE_START = 0.65 RAM_1500_TRANSITION_LAT_FADE_END = 1.85 +RAM_1500_MAX_LAT_JERK_UP = 2.10 RAM_1500_PHASE_SCALE = 0.12 RAM_1500_PHASE_SPEED_ONSET = 8.0 RAM_1500_PHASE_SPEED_FULL = 15.0 RAM_1500_PHASE_LAT_ONSET = 0.25 RAM_1500_PHASE_LAT_WIDTH = 0.12 RAM_1500_TURN_IN_FF_BOOST = 0.06 -RAM_1500_UNWIND_FF_REDUCTION = 0.10 +RAM_1500_UNWIND_FF_REDUCTION = 0.05 # The Kona route is exceptionally accurate below highway speed, but Pop V2 # reverses the requested lateral acceleration roughly once per second at @@ -934,6 +974,11 @@ KONA_NON_SCC_CENTER_TAPER_MAX = 0.14 KONA_NON_SCC_CENTER_TAPER_LAT = 0.28 KONA_NON_SCC_CENTER_TAPER_SPEED_ONSET = 12.0 KONA_NON_SCC_CENTER_TAPER_SPEED_FULL = 24.0 +KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_GAIN = 0.14 +KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_LAT = 0.28 +KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_LAT_WIDTH = 0.07 +KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_SPEED_ONSET = 11.0 +KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_SPEED_WIDTH = 2.5 TRAILER_LOAD_FULL_ASSIST_KG = 15000.0 * CV.LB_TO_KG TRAILER_LATERAL_MIN_SPEED = 15.0 * CV.MPH_TO_MS @@ -1194,6 +1239,22 @@ def get_prius_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> return 1.0 - reduction +def get_prius_friction_jerk_deadzone(v_ego: float, desired_lateral_accel: float) -> float: + speed_weight = _prius_sigmoid((v_ego - PRIUS_FRICTION_JERK_DEADZONE_SPEED) / + PRIUS_FRICTION_JERK_DEADZONE_SPEED_WIDTH) + center_weight = _prius_sigmoid((PRIUS_FRICTION_JERK_DEADZONE_LAT - abs(desired_lateral_accel)) / + PRIUS_FRICTION_JERK_DEADZONE_LAT_WIDTH) + return PRIUS_FRICTION_JERK_DEADZONE_MAX * speed_weight * center_weight + + +def get_prius_high_speed_output_taper_scale(desired_lateral_accel: float, v_ego: float) -> float: + speed_weight = _prius_sigmoid((v_ego - PRIUS_HIGH_SPEED_OUTPUT_TAPER_SPEED) / + PRIUS_HIGH_SPEED_OUTPUT_TAPER_SPEED_WIDTH) + curve_weight = _prius_sigmoid((abs(desired_lateral_accel) - PRIUS_HIGH_SPEED_OUTPUT_TAPER_LAT) / + PRIUS_HIGH_SPEED_OUTPUT_TAPER_LAT_WIDTH) + return 1.0 - PRIUS_HIGH_SPEED_OUTPUT_TAPER_MAX * speed_weight * curve_weight + + def get_camry_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0) -> float: del desired_lateral_jerk @@ -1312,8 +1373,13 @@ def get_sienna_4th_gen_friction_threshold(v_ego: float, desired_lateral_accel: f del desired_lateral_jerk center_weight = _sigmoid((SIENNA_4TH_GEN_FRICTION_CENTER_LAT - abs(desired_lateral_accel)) / SIENNA_4TH_GEN_FRICTION_CENTER_LAT_WIDTH) + high_speed_weight = _sigmoid((v_ego - SIENNA_4TH_GEN_HIGH_SPEED_FRICTION_SPEED_ONSET) / + SIENNA_4TH_GEN_HIGH_SPEED_FRICTION_SPEED_WIDTH) return get_standard_friction_threshold(v_ego) * ( - 1.0 + SIENNA_4TH_GEN_FRICTION_THRESHOLD_GAIN * center_weight * _sienna_4th_gen_friction_speed_weight(v_ego) + 1.0 + center_weight * ( + SIENNA_4TH_GEN_FRICTION_THRESHOLD_GAIN * _sienna_4th_gen_friction_speed_weight(v_ego) + + SIENNA_4TH_GEN_HIGH_SPEED_FRICTION_THRESHOLD_GAIN * high_speed_weight + ) ) @@ -1392,6 +1458,18 @@ def get_kona_non_scc_highway_transition_output_scale(desired_lateral_accel: floa return 1.0 - (taper_max * speed_weight * jerk_weight * lat_weight) +def get_kona_non_scc_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, + desired_lateral_jerk: float = 0.0) -> float: + del desired_lateral_jerk + speed_weight = _sigmoid((v_ego - KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_SPEED_ONSET) / + KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_SPEED_WIDTH) + center_weight = _sigmoid((KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_LAT - abs(desired_lateral_accel)) / + KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_LAT_WIDTH) + return get_standard_friction_threshold(v_ego) * ( + 1.0 + KONA_NON_SCC_CENTER_FRICTION_THRESHOLD_GAIN * speed_weight * center_weight + ) + + def get_kona_non_scc_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float: speed_weight = float(np.interp(v_ego, [KONA_NON_SCC_CENTER_TAPER_SPEED_ONSET, KONA_NON_SCC_CENTER_TAPER_SPEED_FULL], [0.0, 1.0])) center_weight = float(np.interp(abs(desired_lateral_accel), [0.0, KONA_NON_SCC_CENTER_TAPER_LAT], [1.0, 0.0])) @@ -2684,6 +2762,14 @@ def get_ioniq_6_friction_center_fade_scale(desired_lateral_accel: float, v_ego: return 1.0 - IONIQ_6_FRICTION_CENTER_FADE_MAX * speed_weight * center_weight +def get_ioniq_6_2025_center_output_scale(desired_lateral_accel: float, v_ego: float) -> float: + speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_2025_CENTER_OUTPUT_TAPER_SPEED) / + IONIQ_6_2025_CENTER_OUTPUT_TAPER_SPEED_WIDTH) + center_weight = _ioniq_6_sigmoid((IONIQ_6_2025_CENTER_OUTPUT_TAPER_LAT - abs(desired_lateral_accel)) / + IONIQ_6_2025_CENTER_OUTPUT_TAPER_LAT_WIDTH) + return 1.0 - IONIQ_6_2025_CENTER_OUTPUT_TAPER_MAX * speed_weight * center_weight + + def get_ioniq_6_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float: speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_CENTER_TAPER_SPEED) / IONIQ_6_CENTER_TAPER_SPEED_WIDTH) center_weight = _ioniq_6_sigmoid((IONIQ_6_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_CENTER_TAPER_LAT_WIDTH) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index a16337aff..2af335cb9 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -286,6 +286,9 @@ class LongControl: else: # LongCtrlState.pid a_target = self.vehicle_tuning.shape_gm_truck_accel_target(a_target, CS.vEgo, should_stop) + a_target = self.vehicle_tuning.shape_toyota_corolla_accel_target( + a_target, CS.vEgo, should_stop, self.last_output_accel, + ) a_target = self.vehicle_tuning.shape_toyota_sienna_accel_target( a_target, CS.vEgo, should_stop, leads=leads, ) diff --git a/selfdrive/controls/lib/longcontrol_vehicle_tunes.py b/selfdrive/controls/lib/longcontrol_vehicle_tunes.py index c77e28b01..9e56618f4 100644 --- a/selfdrive/controls/lib/longcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/longcontrol_vehicle_tunes.py @@ -34,6 +34,11 @@ TOYOTA_SIENNA_COMFORT_FILTER_MIN_TTC = 4.5 TOYOTA_SIENNA_COMFORT_FILTER_MAX_CLOSING_SPEED = 4.0 TOYOTA_SIENNA_COMFORT_FILTER_MAX_LEAD_BRAKE = 2.5 TOYOTA_SIENNA_COMFORT_FILTER_BRAKE_BYPASS = -2.5 +TOYOTA_COROLLA_TARGET_FILTER_MAX_SPEED = 3.0 +TOYOTA_COROLLA_TARGET_FILTER_UP_TAU = 0.30 +TOYOTA_COROLLA_TARGET_FILTER_DOWN_TAU = 0.18 +TOYOTA_COROLLA_TARGET_FILTER_BRAKE_BYPASS = -0.75 +TOYOTA_COROLLA_TARGET_FILTER_DROP_BYPASS = 0.45 VOLT_CRUISE_INTEGRATOR_MIN_SPEED = 8.0 VOLT_CRUISE_INTEGRATOR_TARGET_MAX = 0.12 VOLT_CRUISE_INTEGRATOR_ERROR_MAX = 0.12 @@ -106,6 +111,10 @@ class LongControlVehicleTuning: CP.brand == "toyota" and getattr(CP, "carFingerprint", None) == TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN ) + self.is_toyota_corolla_tss2 = bool( + CP.brand == "toyota" and + getattr(CP, "carFingerprint", None) == TOYOTA_CAR.TOYOTA_COROLLA_TSS2 + ) self.is_bolt_acc_pedal_friction_car = bool( CP.brand == "gm" and CP.enableGasInterceptorDEPRECATED and @@ -121,6 +130,8 @@ class LongControlVehicleTuning: self.gm_truck_target_filter_initialized = False self.toyota_sienna_filtered_a_target = 0.0 self.toyota_sienna_target_filter_initialized = False + self.toyota_corolla_filtered_a_target = 0.0 + self.toyota_corolla_target_filter_initialized = False self.bolt_start_handoff_frames = 0 def apply_bolt_start_handoff_floor(self, output_accel, last_output_accel, a_target, v_ego, @@ -226,6 +237,31 @@ class LongControlVehicleTuning: self.toyota_sienna_filtered_a_target += alpha * (float(a_target) - self.toyota_sienna_filtered_a_target) return self.toyota_sienna_filtered_a_target + def shape_toyota_corolla_accel_target(self, a_target, v_ego, should_stop, last_output_accel): + """Smooth low-speed Corolla TSS2 stop releases without delaying hard braking.""" + if not self.is_toyota_corolla_tss2 or should_stop or v_ego >= TOYOTA_COROLLA_TARGET_FILTER_MAX_SPEED: + self.toyota_corolla_target_filter_initialized = False + return a_target + + if not self.toyota_corolla_target_filter_initialized: + self.toyota_corolla_filtered_a_target = float(last_output_accel) + self.toyota_corolla_target_filter_initialized = True + + bypass_filter = ( + a_target <= TOYOTA_COROLLA_TARGET_FILTER_BRAKE_BYPASS or + a_target < self.toyota_corolla_filtered_a_target - TOYOTA_COROLLA_TARGET_FILTER_DROP_BYPASS + ) + if bypass_filter: + self.toyota_corolla_filtered_a_target = float(a_target) + return float(a_target) + + tau = (TOYOTA_COROLLA_TARGET_FILTER_DOWN_TAU + if a_target < self.toyota_corolla_filtered_a_target + else TOYOTA_COROLLA_TARGET_FILTER_UP_TAU) + alpha = DT_CTRL / (tau + DT_CTRL) + self.toyota_corolla_filtered_a_target += alpha * (float(a_target) - self.toyota_corolla_filtered_a_target) + return self.toyota_corolla_filtered_a_target + def get_integrator_freeze(self, last_output_accel, a_target, error, v_ego, accel_limits): volt_test_tune_handoff = self.is_volt and testing_ground.use_2 diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 37dde55ea..cbbce37e5 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -786,7 +786,7 @@ class LongitudinalMpc: def get_vision_follow_cruise_hold(self, prev_source, lead_one, lead_two, lead_0_obstacle, lead_1_obstacle, cruise_obstacle, - v_ego, t_follow, tracking_lead): + v_ego, t_follow, tracking_lead, *, early_follow=False): if not tracking_lead or prev_source not in ("lead0", "lead1"): return None @@ -795,7 +795,24 @@ class LongitudinalMpc: return None if float(getattr(prev_lead, "modelProb", 0.0)) < VISION_FOLLOW_CRUISE_HOLD_MIN_MODEL_PROB: return None - if self.get_stable_follow_cruise_hysteresis(prev_lead, v_ego, t_follow) <= 0.0: + if early_follow: + # Silverado admits a credible centered vision lead before it reaches the + # normal matched-follow window. Hold that lead through the harmless + # cruise/lead crossover, but never through a closing or braking lead. + relative_speed = float(v_ego) - float(prev_lead.vLead) + actual_headway = float(prev_lead.dRel) / max(float(v_ego), 1e-3) + if ( + float(v_ego) < 18.0 or + abs(relative_speed) > 2.5 or + actual_headway < 0.95 or + actual_headway > 2.35 or + float(prev_lead.dRel) > 130.0 or + abs(float(getattr(prev_lead, "yRel", 0.0))) > 1.2 or + max(0.0, -float(getattr(prev_lead, "aLeadK", 0.0))) > 0.35 or + float(getattr(prev_lead, "modelProb", 0.0)) < 0.95 + ): + return None + elif self.get_stable_follow_cruise_hysteresis(prev_lead, v_ego, t_follow) <= 0.0: return None prev_lead_obstacle = float(lead_0_obstacle if prev_source == "lead0" else lead_1_obstacle) @@ -845,7 +862,7 @@ class LongitudinalMpc: def update(self, radarstate, v_cruise, x, v, a, j, danger_factor, t_follow, personality=log.LongitudinalPersonality.standard, tracking_lead=True, optional_far_lead_comfort=True, smooth_duplicate_vision=False, - stop_x=None): + stop_x=None, silverado_early_follow=False): v_ego = self.x0[1] lead_one = radarstate.leadOne lead_two = radarstate.leadTwo @@ -939,6 +956,7 @@ class LongitudinalMpc: v_ego, t_follow, tracking_lead, + early_follow=silverado_early_follow, ) self.source = sticky_source or candidate_source diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 16f5c4f43..e3f67fcd9 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -20,6 +20,7 @@ from openpilot.selfdrive.controls.lib.lead_follow_policy import apply as apply_f from openpilot.selfdrive.controls.lib.lead_follow_policy import is_nonurgent_duplicate_vision_follow from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import ( get_far_follow_output_slew_rates, + get_follow_prebrake_min_headway, is_gm_silverado_early_follow_lead, get_toyota_sienna_post_departure_restop_cap, get_untracked_slow_lead_decel_scale, @@ -2183,7 +2184,8 @@ class LongitudinalPlanner: personality=personality, tracking_lead=lead_control_active, optional_far_lead_comfort=True, smooth_duplicate_vision=nonurgent_duplicate_vision_follow and not panic_bypass, - stop_x=force_stop_x) + stop_x=force_stop_x, + silverado_early_follow=early_truck_follow) self.a_desired_trajectory_full = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) @@ -2212,7 +2214,7 @@ class LongitudinalPlanner: if lead_one_active: rel_v = max(0.0, v_ego - self.lead_one.vLead) # dynamic time headway adds a small buffer when uncertainty is elevated - base_th = max(1.6, effective_t_follow) + base_th = get_follow_prebrake_min_headway(self.CP, effective_t_follow) th = base_th + 0.6 * max(0.0, uncertainty - 0.42) desired_gap = th * v_ego if (self.lead_dist_f is not None and self.lead_dist_f < desired_gap and rel_v > 0.5): diff --git a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py index 44d606391..077383a26 100644 --- a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py +++ b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py @@ -8,6 +8,7 @@ GM_SILVERADO_EARLY_FOLLOW_MIN_EGO_SPEED = 18.0 GM_SILVERADO_EARLY_FOLLOW_MAX_DISTANCE = 130.0 GM_SILVERADO_EARLY_FOLLOW_MIN_MODEL_PROB = 0.85 GM_SILVERADO_EARLY_FOLLOW_MAX_LATERAL_OFFSET = 1.2 +GM_SILVERADO_FOLLOW_PREBRAKE_MIN_HEADWAY = 1.25 TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_EGO_SPEED = 2.0 TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_LEAD_SPEED = 0.45 TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_LEAD_DELTA = 0.35 @@ -47,6 +48,13 @@ def is_gm_silverado_early_follow_lead(CP, lead, v_ego): return True +def get_follow_prebrake_min_headway(CP, t_follow): + """Return the comfort pre-brake floor without changing lead safety distance.""" + if CP.brand == "gm" and str(CP.carFingerprint) in ("CHEVROLET_SILVERADO", "CHEVROLET_SILVERADO_CC"): + return max(float(t_follow), GM_SILVERADO_FOLLOW_PREBRAKE_MIN_HEADWAY) + return max(float(t_follow), 1.6) + + def get_toyota_sienna_post_departure_restop_cap(CP, lead, v_ego, accel_min, stop_distance, now_t, departure_latch_until): """Re-arm a stop if a Sienna's lead twitches forward and stops again.""" diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index c3fec9921..a7cc4cd9b 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -28,9 +28,11 @@ from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import ( get_flm_runtime_overrides, get_hkg_canfd_base_friction_threshold, get_kona_non_scc_center_taper_scale, + get_kona_non_scc_friction_threshold, get_kona_non_scc_highway_transition_output_scale, KIA_FORTE_BASE_LAT_ACCEL_FACTOR_MULT, RAM_1500_BASE_LAT_ACCEL_FACTOR_MULT, + RAM_1500_MAX_LAT_JERK_UP, get_ram_1500_transition_output_scale, get_ram_1500_ff_scale, get_subaru_impreza_pid_output_scale, @@ -72,6 +74,8 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import ( get_prius_ff_scale, get_prius_friction_scale, get_prius_friction_threshold, + get_prius_friction_jerk_deadzone, + get_prius_high_speed_output_taper_scale, get_camry_friction_threshold, get_rav4_prime_ff_scale, get_rav4_prime_friction_scale, @@ -97,6 +101,8 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import ( get_ioniq_6_friction_scale, get_ioniq_6_friction_threshold, get_ioniq_6_low_speed_angle_assist_torque, + get_ioniq_6_2025_center_output_scale, + is_ioniq_6_2025_model, get_kia_forte_center_taper_scale, get_kia_forte_ff_scale, get_kia_carnival_center_taper_scale, @@ -702,6 +708,11 @@ class TestLatControl: assert right_turn_in_scale == left_turn_in_scale > base_scale assert base_scale > left_unwind_scale == right_unwind_scale + assert get_prius_friction_jerk_deadzone(30.0, 0.0) > get_prius_friction_jerk_deadzone(30.0, 0.8) + assert get_prius_friction_jerk_deadzone(8.0, 0.0) < 0.05 + assert get_prius_high_speed_output_taper_scale(30.0, 0.0) > get_prius_high_speed_output_taper_scale(30.0, 0.8) + assert get_prius_high_speed_output_taper_scale(15.0, 0.8) > 0.99 + def test_camry_friction_threshold_only_fades_in_for_calm_high_speed(self): low_speed_center = get_camry_friction_threshold(10.0, 0.0) high_speed_center = get_camry_friction_threshold(32.0, 0.0) @@ -811,7 +822,12 @@ class TestLatControl: base = get_standard_friction_threshold(9.0) center = get_sienna_4th_gen_friction_threshold(9.0, 0.0) turn = get_sienna_4th_gen_friction_threshold(9.0, 0.8) + highway_base = get_standard_friction_threshold(28.0) + highway_center = get_sienna_4th_gen_friction_threshold(28.0, 0.0) + highway_turn = get_sienna_4th_gen_friction_threshold(28.0, 0.8) assert center > turn >= base + assert highway_center > highway_base + assert highway_turn < highway_center calm = get_sienna_4th_gen_center_taper_scale(0.0, 8.0) turn_taper = get_sienna_4th_gen_center_taper_scale(0.8, 8.0) @@ -856,6 +872,18 @@ class TestLatControl: assert get_ram_1500_ff_scale(1.2, -1.1, 17.0) < 1.0 assert get_ram_1500_ff_scale(1.2, 1.1, 6.0) < get_ram_1500_ff_scale(1.2, 1.1, 17.0) + def test_ram_1500_jerk_limit_update_path(self): + controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(CHRYSLER.RAM_1500_5TH_GEN) + jerk_samples = [] + for i in range(100): + _, _, lac_log = controller.update( + True, CS, VM, params, False, 0.0025 * i, False, 0.2, None, None, starpilot_toggles, + ) + jerk_samples.append(abs(lac_log.desiredLateralJerk)) + + assert max(jerk_samples) <= RAM_1500_MAX_LAT_JERK_UP + 1e-6 + assert max(jerk_samples) > RAM_1500_MAX_LAT_JERK_UP - 0.05 + def test_ram_1500_transition_taper_update_path(self, monkeypatch): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(CHRYSLER.RAM_1500_5TH_GEN) base_output, _, lac_log = controller.update( @@ -910,6 +938,16 @@ class TestLatControl: assert get_kona_non_scc_center_taper_scale(0.28, 25.0) == pytest.approx(1.0) assert get_kona_non_scc_center_taper_scale(0.10, 25.0) < get_kona_non_scc_center_taper_scale(0.10, 15.0) + def test_kona_non_scc_center_friction_threshold_is_speed_and_center_gated(self): + low_speed = get_kona_non_scc_friction_threshold(3.0, 0.0) + highway_base = get_standard_friction_threshold(25.0) + highway_center = get_kona_non_scc_friction_threshold(25.0, 0.0) + highway_curve = get_kona_non_scc_friction_threshold(25.0, 0.8) + + assert low_speed == pytest.approx(get_standard_friction_threshold(3.0), abs=0.002) + assert highway_center > highway_base + assert highway_curve < highway_center + def test_kona_non_scc_highway_transition_taper_update_path(self, monkeypatch): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_KONA_NON_SCC) CS.vEgo = 30.0 @@ -1049,6 +1087,21 @@ class TestLatControl: assert get_ioniq_6_friction_center_fade_scale(-0.5, 30.0) > 0.95 assert get_ioniq_6_friction_center_fade_scale(0.0, 8.0) > 0.95 + def test_ioniq_6_2025_variant_is_firmware_gated(self): + old_cp = SimpleNamespace( + carFingerprint=HYUNDAI.HYUNDAI_IONIQ_6, + carFw=[SimpleNamespace(fwVersion=b"99211-KL000 221213"), SimpleNamespace(fwVersion=b"ADR 1.03 221205")], + ) + new_cp = SimpleNamespace( + carFingerprint=HYUNDAI.HYUNDAI_IONIQ_6, + carFw=[SimpleNamespace(fwVersion=b"99211-KL000 230915"), SimpleNamespace(fwVersion=b"ADR 1.05 240206")], + ) + + assert not is_ioniq_6_2025_model(old_cp) + assert is_ioniq_6_2025_model(new_cp) + assert get_ioniq_6_2025_center_output_scale(0.0, 28.0) < get_ioniq_6_2025_center_output_scale(0.5, 28.0) + assert get_ioniq_6_2025_center_output_scale(0.0, 15.0) > 0.98 + def test_ioniq_6_center_taper_curve(self): assert get_ioniq_6_center_taper_scale(0.0, 10.0) > get_ioniq_6_center_taper_scale(0.0, 30.0) assert get_ioniq_6_center_taper_scale(0.0, 30.0) < get_ioniq_6_center_taper_scale(0.2, 30.0) @@ -1079,7 +1132,7 @@ class TestLatControl: high_speed_center = get_kia_ev6_friction_threshold(34.0, 0.0, 0.0) high_speed_curve = get_kia_ev6_friction_threshold(34.0, 0.55, 0.0) - assert low_speed == pytest.approx(get_hkg_canfd_base_friction_threshold(10.0), abs=0.002) + assert low_speed == pytest.approx(get_hkg_canfd_base_friction_threshold(10.0), abs=0.004) assert high_speed_center > get_hkg_canfd_base_friction_threshold(34.0) assert high_speed_curve < high_speed_center diff --git a/selfdrive/controls/tests/test_longcontrol.py b/selfdrive/controls/tests/test_longcontrol.py index 068a91b7b..7be30e9e5 100644 --- a/selfdrive/controls/tests/test_longcontrol.py +++ b/selfdrive/controls/tests/test_longcontrol.py @@ -555,6 +555,63 @@ def test_update_releases_stopping_on_small_sustained_positive_target(): assert lc.long_control_state == LongCtrlState.starting +def test_corolla_tss2_stop_release_ramps_positive_target(): + CP = make_longcontrol_cp( + brand="toyota", + carFingerprint=TOYOTA_CAR.TOYOTA_COROLLA_TSS2, + ) + tuning = vehicle_tunes.LongControlVehicleTuning(CP) + tuning.reset() + + first_target = tuning.shape_toyota_corolla_accel_target(1.5, 0.0, False, -0.15) + assert first_target < 0.0 + assert first_target < 1.5 + + target = first_target + for _ in range(100): + target = tuning.shape_toyota_corolla_accel_target(1.5, 0.0, False, target) + assert target > 1.4 + + for _ in range(100): + target = tuning.shape_toyota_corolla_accel_target(1.5, 0.0, False, target) + assert target == pytest.approx(1.5, abs=0.01) + + +def test_corolla_tss2_target_filter_does_not_delay_hard_braking(): + CP = make_longcontrol_cp( + brand="toyota", + carFingerprint=TOYOTA_CAR.TOYOTA_COROLLA_TSS2, + ) + tuning = vehicle_tunes.LongControlVehicleTuning(CP) + tuning.shape_toyota_corolla_accel_target(1.0, 1.0, False, 0.0) + + assert tuning.shape_toyota_corolla_accel_target(-1.0, 1.0, False, 0.5) == -1.0 + + +def test_corolla_tss2_longcontrol_release_does_not_step_to_full_accel(): + CP = make_longcontrol_cp( + brand="toyota", + carFingerprint=TOYOTA_CAR.TOYOTA_COROLLA_TSS2, + ) + lc = LongControl(CP) + lc.long_control_state = LongCtrlState.stopping + lc.last_output_accel = -0.15 + CS = car.CarState.new_message(vEgo=0.0, aEgo=0.0, brakePressed=False) + CS.cruiseState.standstill = False + + output_accel = lc.update( + active=True, + CS=CS, + a_target=1.5, + should_stop=False, + accel_limits=(-3.0, 2.0), + starpilot_toggles=make_toggles(vEgoStarting=0.1), + ) + + assert lc.long_control_state == LongCtrlState.pid + assert output_accel < 0.0 + + def test_update_releases_stopping_immediately_after_confirmed_lead_departure(): CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5) CP.longitudinalTuning.kpBP = [0.0] diff --git a/selfdrive/controls/tests/test_longitudinal_planner.py b/selfdrive/controls/tests/test_longitudinal_planner.py index 7e6e0f603..af6869757 100644 --- a/selfdrive/controls/tests/test_longitudinal_planner.py +++ b/selfdrive/controls/tests/test_longitudinal_planner.py @@ -19,6 +19,7 @@ from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPl from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, soften_far_radar_lead_accel, should_trigger_planner_fcw from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import ( + get_follow_prebrake_min_headway, get_toyota_sienna_post_departure_restop_cap, is_gm_silverado_early_follow_lead, ) @@ -498,6 +499,31 @@ def test_gm_silverado_early_follow_requires_a_credible_centered_vision_lead(kwar assert not is_gm_silverado_early_follow_lead(CP, lead, 30.0) +def test_silverado_prebrake_floor_is_vehicle_specific(): + silverado = SimpleNamespace(brand="gm", carFingerprint=GM_CAR.CHEVROLET_SILVERADO) + honda = SimpleNamespace(brand="honda", carFingerprint=CAR.HONDA_CIVIC) + + assert get_follow_prebrake_min_headway(silverado, 1.0) == pytest.approx(1.25) + assert get_follow_prebrake_min_headway(honda, 1.0) == pytest.approx(1.6) + + +def test_silverado_vision_follow_hold_survives_nonurgent_far_lead_crossover(): + v_ego = 32.0 + t_follow = 1.0 + CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) + planner = LongitudinalPlanner(CP, init_v=v_ego) + lead_one = make_lead(status=True, d_rel=70.0, v_lead=31.2, a_lead=-0.02, radar=False, model_prob=1.0, y_rel=0.1) + lead_two = make_lead(status=False) + + assert planner.mpc.get_vision_follow_cruise_hold( + "lead0", lead_one, lead_two, 101.0, 200.0, 100.0, v_ego, t_follow, True, + ) is None + assert planner.mpc.get_vision_follow_cruise_hold( + "lead0", lead_one, lead_two, 101.0, 200.0, 100.0, v_ego, t_follow, True, + early_follow=True, + ) == "lead0" + + @pytest.mark.parametrize("model_version", ["v11", "v12", "v13", "v14", "v15"]) def test_acc_mode_uses_far_near_stopped_radar_lead_before_tracking(model_version): v_ego = 24.6 diff --git a/selfdrive/modeld/tests/test_usbgpu_helpers.py b/selfdrive/modeld/tests/test_usbgpu_helpers.py index 3966e89f9..5789c4e47 100644 --- a/selfdrive/modeld/tests/test_usbgpu_helpers.py +++ b/selfdrive/modeld/tests/test_usbgpu_helpers.py @@ -1,5 +1,4 @@ import io -from types import SimpleNamespace import numpy as np @@ -25,31 +24,27 @@ def test_out_of_band_artifact_round_trip(): def test_external_gpu_probe_retries_until_pcie_is_ready(monkeypatch): - results = [SimpleNamespace(returncode=1, stdout="", stderr="LTSSM=0x00"), - SimpleNamespace(returncode=0, stdout="", stderr="")] calls = [] + probe_count = 0 + def probe(): + nonlocal probe_count + probe_count += 1 + calls.append("probe") + return (False, "LTSSM=0x00") if probe_count < 3 else (True, "LTSSM=0x78") monkeypatch.setattr( - model_compiler.subprocess, - "run", - lambda *args, **kwargs: calls.append((args, kwargs)) or results.pop(0), + model_compiler, + "_probe_external_gpu_link_once", + probe, ) monkeypatch.setattr(model_compiler.time, "sleep", lambda seconds: calls.append(("sleep", seconds))) model_compiler.wait_for_external_gpu({"PYTHONPATH": "/tmp/openpilot"}) - assert len(calls) == 3 - assert calls[0][1]["env"]["DEV"] == "USB+AMD" - assert calls[1] == ("sleep", 1) + assert calls == ["probe", ("sleep", 1), "probe", ("sleep", 1), "probe"] def test_external_gpu_probe_reports_failure(monkeypatch): - result = SimpleNamespace(returncode=1, stdout="", stderr="link unavailable") - monkeypatch.setattr(model_compiler.subprocess, "run", lambda *args, **kwargs: result) + monkeypatch.setattr(model_compiler, "_probe_external_gpu_link_once", lambda: (False, "link unavailable")) monkeypatch.setattr(model_compiler.time, "sleep", lambda _: None) - try: - model_compiler.wait_for_external_gpu({}) - except RuntimeError as error: - assert "link unavailable" in str(error) - else: - raise AssertionError("external GPU probe unexpectedly succeeded") + assert model_compiler.wait_for_external_gpu({}) is False diff --git a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py index 1204cb807..810677e70 100644 --- a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py +++ b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py @@ -272,7 +272,7 @@ class ConditionalDriveModeView(AdjustorTogglesPanelView): "CESpeed": {"title": tr("Below Speed"), "subtitle": "", "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 20, 35, 55, 75], "labels": {}, "get": lambda: float(self._controller._params.get_int("CESpeed"))}, "CESpeedLead": {"title": tr("Speed w/ Lead"), "subtitle": "", "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 20, 35, 55, 75], "labels": {}, "get": lambda: float(self._controller._params.get_int("CESpeedLead"))}, "CESignalSpeed": {"title": tr("Turn Signal Below"), "subtitle": "", "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 20, 35, 55, 75], "labels": {0.0: tr("Off")}, "get": lambda: float(self._controller._params.get_int("CESignalSpeed"))}, - "CEModelStopTime": {"title": tr("Predicted Stop In"), "subtitle": "", "min": 0, "max": 10.0, "step": 1.0, "unit": "s", "presets": [0, 3, 5, 9, 10], "labels": {0.0: tr("Off")}, "get": lambda: float(self._controller._params.get_int("CEModelStopTime"))}, + "CEModelStopTime": {"title": tr("Predicted Stop In"), "subtitle": "", "min": 0, "max": 10.0, "step": 0.1, "unit": "s", "presets": [0, 3, 5, 7.7, 10], "labels": {0.0: tr("Off")}, "get": lambda: float(self._controller._params.get_float("CEModelStopTime"))}, "CCMSpeed": {"title": tr("Above Speed"), "subtitle": "", "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 35, 55, 65, 80], "labels": {}, "get": lambda: float(self._controller._params.get_int("CCMSpeed"))}, "CCMSpeedLead": {"title": tr("Speed w/ Lead"), "subtitle": "", "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 35, 55, 65, 80], "labels": {}, "get": lambda: float(self._controller._params.get_int("CCMSpeedLead"))}, "CCMSetSpeedMargin": {"title": tr("Set Speed Margin"), "subtitle": "", "min": 0, "max": 30.0 if is_metric else 15.0, "step": 1.0, "unit": speed_unit, "presets": [0, 5, 10, 15], "labels": {}, "get": lambda: float(self._controller._params.get_int("CCMSetSpeedMargin"))}, @@ -306,22 +306,26 @@ class ConditionalDriveModeView(AdjustorTogglesPanelView): "CESpeed": {"title": tr("Below Speed"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 20, 35, 55, 75]}, "CESpeedLead": {"title": tr("Speed w/ Lead"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 20, 35, 55, 75]}, "CESignalSpeed": {"title": tr("Turn Signal Below"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {0.0: tr("Off")}, "presets": [0, 20, 35, 55, 75]}, - "CEModelStopTime": {"title": tr("Predicted Stop In"), "min": 0, "max": 10.0, "unit": "s", "labels": {0.0: tr("Off")}, "presets": [0, 3, 5, 9, 10]}, + "CEModelStopTime": {"title": tr("Predicted Stop In"), "min": 0, "max": 10.0, "unit": "s", "labels": {0.0: tr("Off")}, "presets": [0, 3, 5, 7.7, 10]}, "CCMSpeed": {"title": tr("Above Speed"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 35, 55, 65, 80]}, "CCMSpeedLead": {"title": tr("Speed w/ Lead"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 35, 55, 65, 80]}, "CCMSetSpeedMargin": {"title": tr("Set Speed Margin"), "min": 0, "max": 30.0 if is_metric else 15.0, "unit": speed_unit, "labels": {}, "presets": [0, 5, 10, 15]}, } spec = specs[key] - original_val = float(self._controller._params.get_int(key)) + is_float = key == "CEModelStopTime" + original_val = float(self._controller._params.get_float(key) if is_float else self._controller._params.get_int(key)) def on_close(res, val): if res == DialogResult.CONFIRM: - self._controller._params.put_int(key, int(val)) + if is_float: + self._controller._params.put_float(key, float(val)) + else: + self._controller._params.put_int(key, int(val)) gui_app.push_widget(AetherSliderDialog( title=spec["title"], - min_val=float(spec["min"]), max_val=float(spec["max"]), step=1.0, + min_val=float(spec["min"]), max_val=float(spec["max"]), step=0.1 if is_float else 1.0, current_val=original_val, on_close=on_close, presets=[float(p) for p in spec["presets"]], unit=spec["unit"], labels=spec["labels"], color=PANEL_STYLE.accent diff --git a/starpilot/ui/qt/offroad/longitudinal_settings.cc b/starpilot/ui/qt/offroad/longitudinal_settings.cc index d7984dc32..28ea8eb3b 100644 --- a/starpilot/ui/qt/offroad/longitudinal_settings.cc +++ b/starpilot/ui/qt/offroad/longitudinal_settings.cc @@ -291,11 +291,8 @@ StarPilotLongitudinalPanel::StarPilotLongitudinalPanel(StarPilotSettingsWindow * } else if (param == "CCMSetSpeedMargin") { longitudinalToggle = new StarPilotParamValueControl(param, title, desc, icon, 0, 15, tr(" mph"), std::map(), 1, true, 175); } else if (param == "CEModelStopTime") { - std::map stopTimeLabels; - for (int i = 0; i <= 10; ++i) { - stopTimeLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" second") : QString::number(i) + tr(" seconds"); - } - longitudinalToggle = new StarPilotParamValueControl(param, title, desc, icon, 0, 9, QString(), stopTimeLabels); + std::map stopTimeLabels{{0.0f, tr("Off")}}; + longitudinalToggle = new StarPilotParamValueControl(param, title, desc, icon, 0, 9, tr(" seconds"), stopTimeLabels, 0.1); } else if (param == "CESignalSpeed") { std::vector ceSignalToggles{"CESignalLaneDetection"}; std::vector ceSignalToggleNames{tr("Not For Detected Lanes")}; diff --git a/system/manager/manager.py b/system/manager/manager.py index 8849df161..c9859ae17 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -68,7 +68,7 @@ STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG = Path("/data") / "starpilot_par STARPILOT_PC_ROOT_MIGRATION_FLAG = Path("/data") / "starpilot_pc_root_v1" STARPILOT_PARAMS_CACHE_MIGRATION_FLAG = Path("/data") / "starpilot_params_cache_v1" STARPILOT_DEFAULT_MODEL_MIGRATION_FLAG = Path("/data") / "starpilot_default_model_rdf_v1" -STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG = Path("/data") / "starpilot_ce_model_stop_time_v1" +STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG = Path("/data") / "starpilot_ce_model_stop_time_v2" STARPILOT_LEGACY_CACHE_MARKER_KEYS = ("RemapCancelToDistance",) STARPILOT_REMOVED_PARAM_KEYS = ("CoastUpToLeads", "HumanAcceleration", "HumanFollowing", "PrioritizeSmoothFollowing") LEGACY_CARMODEL_MIGRATIONS = { @@ -561,8 +561,8 @@ def migrate_starpilot_default_parity(params: Params, params_cache: Params) -> No seeded_keys.append(key) if not _has_persisted_param_file(params, "CEModelStopTime") and not _has_persisted_param_file(params_cache, "CEModelStopTime"): - params.put_float("CEModelStopTime", 9.0) - params_cache.put_float("CEModelStopTime", 9.0) + params.put_float("CEModelStopTime", 7.7) + params_cache.put_float("CEModelStopTime", 7.7) seeded_keys.append("CEModelStopTime") # Rebase default regression fix: @@ -628,7 +628,7 @@ def migrate_starpilot_default_model(params: Params, params_cache: Params) -> Non def migrate_starpilot_ce_model_stop_time(params: Params, params_cache: Params) -> None: - """Move the old persisted 7-second stop prediction default to 9 seconds once.""" + """Move persisted users of the old 9-second stop prediction threshold to 7.7 once.""" if STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG.exists(): return @@ -643,14 +643,14 @@ def migrate_starpilot_ce_model_stop_time(params: Params, params_cache: Params) - except Exception: continue - if abs(parsed_value - 7.0) < 1e-6: + if abs(parsed_value - 9.0) < 1e-6: legacy_default_detected = True break if legacy_default_detected: - params.put_float("CEModelStopTime", 9.0) - params_cache.put_float("CEModelStopTime", 9.0) - cloudlog.warning("Migrated CEModelStopTime from 7 seconds to 9 seconds") + params.put_float("CEModelStopTime", 7.7) + params_cache.put_float("CEModelStopTime", 7.7) + cloudlog.warning("Migrated CEModelStopTime from 9 seconds to 7.7 seconds") try: STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG.parent.mkdir(parents=True, exist_ok=True) diff --git a/system/manager/test/test_manager.py b/system/manager/test/test_manager.py index 9e8863d40..ac4647600 100644 --- a/system/manager/test/test_manager.py +++ b/system/manager/test/test_manager.py @@ -240,6 +240,17 @@ class TestManager: assert params.get("CEModelStopTime") == "3.5" assert params_cache.get_bool("NNFF") + def test_migrate_starpilot_default_parity_seeds_new_model_stop_time_default(self, tmp_path, monkeypatch): + monkeypatch.setattr(manager, "STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG", tmp_path / "starpilot_defaults_parity_v1") + + params = FileBackedFakeParams(tmp_path / "params") + params_cache = FileBackedFakeParams(tmp_path / "cache") + + manager.migrate_starpilot_default_parity(params, params_cache) + + assert params.get("CEModelStopTime") == "7.7" + assert params_cache.get("CEModelStopTime") == "7.7" + def test_migrate_starpilot_default_model(self, tmp_path, monkeypatch): monkeypatch.setattr(manager, "STARPILOT_DEFAULT_MODEL_MIGRATION_FLAG", tmp_path / "starpilot_default_model_rdf_v1") @@ -262,17 +273,28 @@ class TestManager: assert manager.STARPILOT_DEFAULT_MODEL_MIGRATION_FLAG.exists() def test_migrate_starpilot_ce_model_stop_time(self, tmp_path, monkeypatch): - monkeypatch.setattr(manager, "STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG", tmp_path / "starpilot_ce_model_stop_time_v1") + monkeypatch.setattr(manager, "STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG", tmp_path / "starpilot_ce_model_stop_time_v2") - params = FileBackedFakeParams(tmp_path / "params", {"CEModelStopTime": 7.0}) + params = FileBackedFakeParams(tmp_path / "params", {"CEModelStopTime": 9.0}) params_cache = FileBackedFakeParams(tmp_path / "cache") manager.migrate_starpilot_ce_model_stop_time(params, params_cache) - assert params.get("CEModelStopTime") == "9.0" - assert params_cache.get("CEModelStopTime") == "9.0" + assert params.get("CEModelStopTime") == "7.7" + assert params_cache.get("CEModelStopTime") == "7.7" assert manager.STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG.exists() + def test_migrate_starpilot_ce_model_stop_time_preserves_custom_value(self, tmp_path, monkeypatch): + monkeypatch.setattr(manager, "STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG", tmp_path / "starpilot_ce_model_stop_time_v2") + + params = FileBackedFakeParams(tmp_path / "params", {"CEModelStopTime": 8.0}) + params_cache = FileBackedFakeParams(tmp_path / "cache", {"CEModelStopTime": 8.0}) + + manager.migrate_starpilot_ce_model_stop_time(params, params_cache) + + assert params.get("CEModelStopTime") == "8.0" + assert params_cache.get("CEModelStopTime") == "8.0" + def test_migrate_disable_humanlike_defaults(self, tmp_path, monkeypatch): monkeypatch.setattr(manager, "STARPILOT_HUMANLIKE_DISABLE_MIGRATION_FLAG", tmp_path / "starpilot_humanlike_disable_v1")